diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 5844db0..0000000 --- a/babel.config.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = function (api) { - api.cache(true) - return { - presets: [ - [ - '@babel/preset-env', - { - targets: { - 'browsers': [ - '> 2%', - 'not ie 11', - 'not op_mini all' - ] - } - } - ] - ] - } -} \ No newline at end of file diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 0000000..e80fba5 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,15 @@ +{ + "comments": false, + "presets": [ + "@babel/preset-env" + ], + "targets": { + "node": 18, + "browsers": [ + "> 2%", + "not ie 11", + "not op_mini all" + ] + }, + "plugins": ["transform-jsbi-to-bigint"] +} \ No newline at end of file diff --git a/package.json b/package.json index 14f8fff..c70b566 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "js-xdr", "version": "3.0.0", "description": "Read/write XDR encoded data structures (RFC 4506)", - "main": "lib/xdr.js", - "browser": "dist/xdr.js", + "main": "lib/xdr.min.js", + "browser": "dist/xdr.min.js", "module": "src/index.js", "scripts": { "build": "yarn run build:browser && yarn run build:node", @@ -53,6 +53,7 @@ "@babel/preset-env": "^7.21.4", "@babel/register": "^7.14.5", "babel-loader": "^9.1.2", + "babel-plugin-transform-jsbi-to-bigint": "^1.4.0", "buffer": "^6.0.3", "chai": "^4.3.7", "eslint": "^8.43.0", @@ -77,5 +78,8 @@ "terser-webpack-plugin": "^5.3.7", "webpack": "^5.88.0", "webpack-cli": "^5.0.2" + }, + "dependencies": { + "jsbi": "^4.3.0" } } diff --git a/src/bigint-encoder.js b/src/bigint-encoder.js index 861096d..4480cc7 100644 --- a/src/bigint-encoder.js +++ b/src/bigint-encoder.js @@ -1,14 +1,17 @@ +import JSBI from 'jsbi'; + /** * Encode a native `bigint` value from a list of arbitrary integer-like values. * - * @param {Array} parts - Slices to encode in big-endian - * format (i.e. earlier elements are higher bits) - * @param {64|128|256} size - Number of bits in the target integer type - * @param {boolean} unsigned - Whether it's an unsigned integer + * @param {Array} parts slices to encode in + * big-endian format (i.e. earlier elements are higher bits) + * @param {64|128|256} size number of bits in the target integer type + * @param {boolean} unsigned whether it's an unsigned integer * - * @returns {bigint} + * @returns {JSBI.BigInt} */ export function encodeBigIntFromBits(parts, size, unsigned) { + /** @type {Array} */ if (!(parts instanceof Array)) { // allow a single parameter instead of an array parts = [parts]; @@ -28,7 +31,7 @@ export function encodeBigIntFromBits(parts, size, unsigned) { default: throw new RangeError( - `expected slices to fit in 32/64/128/256 bits, got ${parts}` + `expected slices to fit in 32/64/128/256 bits, got ${sliceSize}: ${parts}` ); } @@ -36,7 +39,7 @@ export function encodeBigIntFromBits(parts, size, unsigned) { try { for (let i = 0; i < parts.length; i++) { if (typeof parts[i] !== 'bigint') { - parts[i] = BigInt(parts[i].valueOf()); + parts[i] = JSBI.BigInt(parts[i].valueOf()); } } } catch (e) { @@ -46,19 +49,26 @@ export function encodeBigIntFromBits(parts, size, unsigned) { // check for sign mismatches for single inputs (this is a special case to // handle one parameter passed to e.g. UnsignedHyper et al.) // see https://github.com/stellar/js-xdr/pull/100#discussion_r1228770845 - if (unsigned && parts.length === 1 && parts[0] < 0n) { + if (unsigned && parts.length === 1 && JSBI.LT(parts[0], JSBI.BigInt(0))) { throw new RangeError(`expected a positive value, got: ${parts}`); } // encode in big-endian fashion, shifting each slice by the slice size - let result = BigInt.asUintN(sliceSize, parts[0]); // safe: len >= 1 + let result = JSBI.asUintN(sliceSize, parts[0]); // safe: len >= 1 for (let i = 1; i < parts.length; i++) { - result |= BigInt.asUintN(sliceSize, parts[i]) << BigInt(i * sliceSize); + // result |= parts[i] << (i * sliceSize) + result = JSBI.bitwiseOr( + result, + JSBI.leftShift( + JSBI.asUintN(sliceSize, parts[i]), + JSBI.BigInt(i * sliceSize) + ) + ); } // interpret value as signed if necessary and clamp it if (!unsigned) { - result = BigInt.asIntN(size, result); + result = JSBI.asIntN(size, result); } // check boundaries @@ -86,7 +96,7 @@ export function encodeBigIntFromBits(parts, size, unsigned) { * @return {bigint[]} */ export function sliceBigInt(value, iSize, sliceSize) { - if (typeof value !== 'bigint') { + if (!(value instanceof JSBI)) { throw new TypeError(`Expected bigint 'value', got ${typeof value}`); } @@ -105,17 +115,17 @@ export function sliceBigInt(value, iSize, sliceSize) { ); } - const shift = BigInt(sliceSize); + const shift = JSBI.BigInt(sliceSize); // iterate shift and mask application const result = new Array(total); for (let i = 0; i < total; i++) { // we force a signed interpretation to preserve sign in each slice value, // but downstream can convert to unsigned if it's appropriate - result[i] = BigInt.asIntN(sliceSize, value); // clamps to size + result[i] = JSBI.asIntN(sliceSize, value); // clamps to size // move on to the next chunk - value >>= shift; + value = JSBI.signedRightShift(value, shift); } return result; @@ -127,15 +137,24 @@ export function formatIntName(precision, unsigned) { /** * Get min|max boundaries for an integer with a specified bits size - * @param {64|128|256} size - Number of bits in the source integer type - * @param {Boolean} unsigned - Whether it's an unsigned integer - * @return {BigInt[]} + * @param {64|128|256} size number of bits in the source integer type + * @param {Boolean} unsigned whether it's an unsigned integer + * @return {JSBI.BigInt[]} */ export function calculateBigIntBoundaries(size, unsigned) { if (unsigned) { - return [0n, (1n << BigInt(size)) - 1n]; + return [ + JSBI.BigInt(0), + JSBI.subtract( + JSBI.leftShift(JSBI.BigInt(1), JSBI.BigInt(size)), + JSBI.BigInt(1) + ) + ]; } - const boundary = 1n << BigInt(size - 1); - return [0n - boundary, boundary - 1n]; + const boundary = JSBI.leftShift(JSBI.BigInt(1), JSBI.BigInt(size - 1)); + return [ + JSBI.subtract(JSBI.BigInt(0), boundary), + JSBI.subtract(boundary, JSBI.BigInt(1)) + ]; } diff --git a/src/int.js b/src/int.js index 84ce55c..8d63797 100644 --- a/src/int.js +++ b/src/int.js @@ -5,16 +5,12 @@ const MAX_VALUE = 2147483647; const MIN_VALUE = -2147483648; export class Int extends XdrPrimitiveType { - /** - * @inheritDoc - */ + /** @inheritDoc */ static read(reader) { return reader.readInt32BE(); } - /** - * @inheritDoc - */ + /** @inheritDoc */ static write(value, writer) { if (typeof value !== 'number') throw new XdrWriterError('not a number'); @@ -25,9 +21,7 @@ export class Int extends XdrPrimitiveType { writer.writeInt32BE(value); } - /** - * @inheritDoc - */ + /** @inheritDoc */ static isValid(value) { if (typeof value !== 'number' || (value | 0) !== value) { return false; diff --git a/src/large-int.js b/src/large-int.js index c106912..a76b9cf 100644 --- a/src/large-int.js +++ b/src/large-int.js @@ -1,10 +1,19 @@ +import JSBI from 'jsbi'; + import { XdrPrimitiveType } from './xdr-type'; -import { calculateBigIntBoundaries, encodeBigIntFromBits, sliceBigInt } from './bigint-encoder'; +import { + calculateBigIntBoundaries, + encodeBigIntFromBits, + sliceBigInt +} from './bigint-encoder'; import { XdrNotImplementedDefinitionError, XdrWriterError } from './errors'; export class LargeInt extends XdrPrimitiveType { + /** @type {JSBI.BigInt} */ + _value; + /** - * @param {Array} parts - Slices to encode + * @param {Array} parts slices to encode */ constructor(args) { super(); @@ -43,33 +52,37 @@ export class LargeInt extends XdrPrimitiveType { } toJSON() { - return {_value: this._value.toString()} + return { _value: this._value.toString() }; } toBigInt() { - return BigInt(this._value); + return JSBI.BigInt(this._value); } - /** - * @inheritDoc - */ + /** @inheritDoc */ static read(reader) { - const {size} = this.prototype; - if (size === 64) - return new this(reader.readBigUInt64BE()); - return new this(...Array.from({length: size / 64}, () => reader.readBigUInt64BE()).reverse()); + const { size } = this.prototype; + if (size === 64) return new this(reader.readBigUInt64BE()); + return new this( + ...Array.from({ length: size / 64 }, () => + reader.readBigUInt64BE() + ).reverse() + ); } - /** - * @inheritDoc - */ + /** @inheritDoc */ static write(value, writer) { if (value instanceof this) { value = value._value; - } else if (typeof value !== 'bigint' || value > this.MAX_VALUE || value < this.MIN_VALUE) + } else if ( + !(value instanceof JSBI) || + value > this.MAX_VALUE || + value < this.MIN_VALUE + ) { throw new XdrWriterError(`${value} is not a ${this.name}`); + } - const {unsigned, size} = this.prototype; + const { unsigned, size } = this.prototype; if (size === 64) { if (unsigned) { writer.writeBigUInt64BE(value); @@ -91,7 +104,10 @@ export class LargeInt extends XdrPrimitiveType { * @inheritDoc */ static isValid(value) { - return typeof value === 'bigint' || (value instanceof this); + return ( + value instanceof JSBI || + value instanceof this + ); } /** @@ -103,17 +119,19 @@ export class LargeInt extends XdrPrimitiveType { return new this(string); } - static MAX_VALUE = 0n; - - static MIN_VALUE = 0n; + static MAX_VALUE = JSBI.BigInt(0); + static MIN_VALUE = JSBI.BigInt(0); /** * @internal * @return {void} */ static defineIntBoundaries() { - const [min, max] = calculateBigIntBoundaries(this.prototype.size, this.prototype.unsigned); + const [min, max] = calculateBigIntBoundaries( + this.prototype.size, + this.prototype.unsigned + ); this.MIN_VALUE = min; this.MAX_VALUE = max; } -} \ No newline at end of file +} diff --git a/test/setup.js b/test/setup.js index 58c9141..889acd9 100644 --- a/test/setup.js +++ b/test/setup.js @@ -8,6 +8,7 @@ global.sinon = require('sinon'); global.chai.use(require('sinon-chai')); global.expect = global.chai.expect; +global.JSBI = require('jsbi'); exports.mochaHooks = { beforeEach: function () { diff --git a/webpack.config.js b/webpack.config.js index 02bdb98..d9f044f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,7 +2,7 @@ const path = require('path'); const webpack = require('webpack'); const TerserPlugin = require('terser-webpack-plugin'); -const browserBuild = !process.argv.includes('--mode=development') +const browserBuild = !process.argv.includes('--mode=development'); module.exports = function () { const mode = browserBuild ? 'production' : 'development'; @@ -10,7 +10,8 @@ module.exports = function () { mode, devtool: 'source-map', entry: { - 'xdr': [path.join(__dirname, '/src/browser.js')] + 'xdr': [path.join(__dirname, '/src/browser.js')], + 'xdr.min': [path.join(__dirname, '/src/browser.js')] }, output: { path: path.join(__dirname, browserBuild ? './dist' : './lib'), @@ -24,7 +25,7 @@ module.exports = function () { module: { rules: [ { - test: /\.js$/, + test: /\.m?js$/, loader: 'babel-loader', exclude: /node_modules/ } @@ -35,19 +36,29 @@ module.exports = function () { 'process.env.NODE_ENV': JSON.stringify(mode) }) ] - } + }; if (browserBuild) { config.optimization = { minimize: true, - minimizer: [new TerserPlugin({ - parallel: true - })] - } - config.plugins.push(new webpack.ProvidePlugin({ - Buffer: ['buffer', 'Buffer'], - })) + minimizer: [ + new TerserPlugin({ + include: /\.min\.js$/, + parallel: true, + terserOptions: { + format: { + ascii_only: true + } + } + }) + ] + }; + config.plugins.push( + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'] + }) + ); } else { config.target = 'node'; } - return config -} \ No newline at end of file + return config; +}; diff --git a/yarn.lock b/yarn.lock index 127701a..79e2641 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1625,6 +1625,11 @@ babel-plugin-polyfill-regenerator@^0.5.0: dependencies: "@babel/helper-define-polyfill-provider" "^0.4.0" +babel-plugin-transform-jsbi-to-bigint@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-jsbi-to-bigint/-/babel-plugin-transform-jsbi-to-bigint-1.4.0.tgz#04db5036c7821916c248b90ded792a1272bc9c8d" + integrity sha512-59f6ClwQBY/SKMVwKV/xoAqH/gnyZ6C5gSSPsEUCLPvvZULCDSE5Y3T62wjNTbpBcFzh0ReYz2mIe3u44mW9UQ== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -3102,6 +3107,11 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbi@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-4.3.0.tgz#b54ee074fb6fcbc00619559305c8f7e912b04741" + integrity sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"