Skip to content

Commit

Permalink
Merge branch 'modularize-bignum'
Browse files Browse the repository at this point in the history
  • Loading branch information
clayzermk1 committed Aug 26, 2016
2 parents b5c791f + 3e99f00 commit e5408c6
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 77 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 0.2.1
* Modularized bignum.

# 0.2.0
* Added bignum parsing (#4).
* Switched testing frameworks from `tap` to `tape`.
Expand Down
99 changes: 99 additions & 0 deletions lib/bignum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
var debug = require('debug')('lib:bignum');

// Copied directly from https://github.com/MikeMcl/decimal.js/blob/fb37ca6bde56676d6d5a98df2bf8721e8fa81085/decimal.js#L27
// Base conversion alphabet.
var NUMERALS = '0123456789abcdef';

module.exports = (function () {
function Bignum(rawString, encoding) {
this.buffer = new Buffer(rawString);
this.encoding = encoding;
return this;
}

Bignum.prototype._getWordLength = function () {
// the word length appears to always be positive but still follows the Marshal length logic
var wordLength = this.buffer.readInt8(1); // read the number of "machine words" - 16bit integers
if (wordLength === 0) {
wordLength = 0;
}
else if (wordLength >= 6) {
wordLength = wordLength - 5;
}
else if (wordLength <= -6) {
wordLength = wordLength + 5;
}

debug('wordLength: %s', wordLength);

return wordLength;
}

Bignum.prototype._readByteString = function () {
// it appears that words are always zero padded - no odd number of bytes
var byteLength = this._getWordLength() * 2;
var byteString = '';
var i, index;
for (i = 0, index = 2; i < byteLength; i++, index++) {
byteString = this.buffer.toString('hex', index, index + 1) + byteString;
}

debug('byteString: %s', byteString);

return byteString;
}

Bignum.prototype._readBase10String = function () {
var byteString = this._readByteString();

// Modified from https://github.com/MikeMcl/decimal.js/blob/fb37ca6bde56676d6d5a98df2bf8721e8fa81085/decimal.js#L2597
var base10Array = [0];
var j, base10ArrayLength;
for (i = 0; i < byteString.length; i++) {
for (base10ArrayLength = base10Array.length; base10ArrayLength--;) {
base10Array[base10ArrayLength] *= 16;
}
base10Array[0] += NUMERALS.indexOf(byteString.charAt(i));
for (j = 0; j < base10Array.length; j++) {
if (base10Array[j] > 10 - 1) {
if (base10Array[j + 1] === void 0) {
base10Array[j + 1] = 0;
}
base10Array[j + 1] += base10Array[j] / 10 | 0;
base10Array[j] %= 10;
}
}
}

// trim leading zeros
while (base10Array[base10Array.length] === 0) {
base10Array.pop();
}

var base10String = base10Array.reverse().join('');
debug('base10String: %s', base10String);

return base10String;
}

Bignum.prototype.isNegative = function () {
return (this.buffer.readInt8(0) === 0x2d); // read the sign byte and check for '-'
}

Bignum.prototype.toString = function () {
var bignum = this._readBase10String();

if (this.isNegative()) {
bignum = '-' + bignum;
}
debug('bignum: %s', bignum);

return bignum;
};

Bignum.prototype.toJSON = function () {
return this.toString();
};

return Bignum;
})();
2 changes: 1 addition & 1 deletion lib/ivar.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Ivar = (function () {
this.string = this.buffer.toString('utf8');

return this;
};
}

Ivar.prototype.toString = function () {
return this.buffer.toString(this.encoding || 'utf8');
Expand Down
97 changes: 23 additions & 74 deletions lib/marshal.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
var debug = require('debug')('marshal');
var MarshalError = require('./marshalError');
var Ivar = require('./ivar');

// Copied directly from https://github.com/MikeMcl/decimal.js/blob/fb37ca6bde56676d6d5a98df2bf8721e8fa81085/decimal.js#L27
// Base conversion alphabet.
var NUMERALS = '0123456789abcdef';
var Bignum = require('./bignum.js');
var Ivar = require('./ivar.js');
var MarshalError = require('./marshalError.js');

var Marshal;

Marshal = (function () {
function Marshal (buffer, encoding) {
if (buffer !== void 0) this.load(buffer, encoding);
if (buffer !== void 0) {
this.load(buffer, encoding);
}

return this;
};
}

Marshal.prototype._parse = function () {
var typeCode = this.buffer.readUInt8(this._index++);
Expand Down Expand Up @@ -45,9 +45,9 @@ Marshal = (function () {
return this._parseHash();
case 0x6C: // l - bignum
return this._parseBignum();
case 0x2F: // / - regexp
case 0x63: // c - class
case 0x6D: // m - module
// case 0x2F: // / - regexp
// case 0x63: // c - class
// case 0x6D: // m - module
default:
throw new MarshalError('unsupported typecode ' + typeCode, this);
}
Expand All @@ -57,69 +57,18 @@ Marshal = (function () {
* @return The decoded bignum.
*/
Marshal.prototype._parseBignum = function () {
var isNegative = (this.buffer.readInt8(this._index++) === 0x2d); // read the sign byte and check for '-'
debug('isNegative: %s', isNegative);

// the word length appears to always be positive but still follows the Marshal length logic
var wordLength = this.buffer.readInt8(this._index++); // read the number of "machine words" - 16bit integers
if (wordLength === 0) {
wordLength = 0;
}
else if (wordLength >= 6) {
wordLength = wordLength - 5;
}
else if (wordLength <= -6) {
wordLength = wordLength + 5;
}
debug('wordLength: %s', wordLength);

// it appears that words are always zero padded - no odd number of bytes
var byteLength = wordLength * 2;
var byteString = '';
for (var i = 0; i < byteLength; i++, this._index++) {
byteString = this.buffer.toString('hex', this._index, this._index + 1) + byteString;
}
debug('byteString: %j', byteString);

// Modified from https://github.com/MikeMcl/decimal.js/blob/fb37ca6bde56676d6d5a98df2bf8721e8fa81085/decimal.js#L2597
var base10Array = [0];
var i, j, base10ArrayLength;
for (i = 0; i < byteString.length; i++) {
for (base10ArrayLength = base10Array.length; base10ArrayLength--;) base10Array[base10ArrayLength] *= 16;
base10Array[0] += NUMERALS.indexOf(byteString.charAt(i));
for (j = 0; j < base10Array.length; j++) {
if (base10Array[j] > 10 - 1) {
if (base10Array[j + 1] === void 0) base10Array[j + 1] = 0;
base10Array[j + 1] += base10Array[j] / 10 | 0;
base10Array[j] %= 10;
}
}
}

// trim leading zeros
while (base10Array[base10Array.length] === 0) {
base10Array.pop();
}

base10Array.reverse();
debug('base10Array: %j', base10Array);
var bignum = new Bignum(this.buffer.slice(this._index), 'hex');

var bignum = base10Array.join('');

if (isNegative) {
bignum = '-' + bignum;
}
debug('bignum: %s', bignum);

return bignum;
}
return bignum.toString();
};

/** Parse an integer.
* Used for reading of integer types and array lengths.
* @return The decoded integer.
*/
Marshal.prototype._parseInteger = function () {
var small = this.buffer.readInt8(this._index++); // read a signed byte
var large;
if (small === 0) {
return 0;
}
Expand All @@ -130,42 +79,42 @@ Marshal = (function () {
return small + 5;
}
else if (small === 1) {
var large = this.buffer.readUInt8(this._index);
large = this.buffer.readUInt8(this._index);
this._index += 1;
return large;
}
else if (small === 2) {
var large = this.buffer.readUInt16LE(this._index);
large = this.buffer.readUInt16LE(this._index);
this._index += 2;
return large;
}
else if (small === 3) {
var large = new Buffer(this.buffer.toString('hex', this._index, this._index + 3) + '00', 'hex').readUInt32LE(0);
large = new Buffer(this.buffer.toString('hex', this._index, this._index + 3) + '00', 'hex').readUInt32LE(0);
this._index += 3;
return large;
}
else if (small === 4) {
var large = this.buffer.readUInt32LE(this._index);
large = this.buffer.readUInt32LE(this._index);
this._index += 4;
return large;
}
else if (small === -1) {
var large = -(~(0xffffff00 + this.buffer.readUInt8(this._index)) + 1);
large = -(~(0xffffff00 + this.buffer.readUInt8(this._index)) + 1);
this._index += 1;
return large;
}
else if (small === -2) {
var large = this.buffer.readInt16LE(this._index);
large = this.buffer.readInt16LE(this._index);
this._index += 2;
return large;
}
else if (small === -3) {
var large = -(~(((0xffff0000 + this.buffer.readUInt16LE(this._index + 1)) << 8) + this.buffer.readUInt8(this._index)) + 1);
large = -(~(((0xffff0000 + this.buffer.readUInt16LE(this._index + 1)) << 8) + this.buffer.readUInt8(this._index)) + 1);
this._index += 3;
return large;
}
else if (small === -4) {
var large = this.buffer.readInt32LE(this._index);
large = this.buffer.readInt32LE(this._index);
this._index += 4;
return large;
}
Expand Down Expand Up @@ -205,7 +154,7 @@ Marshal = (function () {
var object = this._parseHash();

// attach name
object['_name'] = name;
object._name = name;

return object;
};
Expand Down
2 changes: 1 addition & 1 deletion lib/marshalError.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ MarshalError = (function () {
instance.buffer.toString('utf8', instance._index - 1, instance._index) + ' ' +
instance.buffer.toString('utf8', instance._index));
Error.captureStackTrace(this, this.constructor);
};
}

util.inherits(MarshalError, Error);

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "marshal",
"version": "0.2.0",
"version": "0.2.1",
"description": "Parse Ruby's Marshal strings into JavaScript objects/JSON.",
"main": "index.js",
"scripts": {
Expand Down

0 comments on commit e5408c6

Please sign in to comment.