From 9d863c4dfac6617f2bcbf090833c5dd3f782ae7b Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Sat, 27 Jul 2013 19:36:27 -0700 Subject: [PATCH 1/9] Bump version to 0.7.18. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 77dcb006..578dd90f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ripple-lib", - "version": "0.7.17", + "version": "0.7.18", "description": "Ripple JavaScript client library", "files": [ "src/js/ripple/*.js", From be2d647d487d61ca2f771a6b10cb32357d0e07a7 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Sat, 27 Jul 2013 19:46:32 -0700 Subject: [PATCH 2/9] Testutils is not used in this testsuite. --- test/testutils.js | 484 ---------------------------------------------- 1 file changed, 484 deletions(-) delete mode 100644 test/testutils.js diff --git a/test/testutils.js b/test/testutils.js deleted file mode 100644 index 4c970bdc..00000000 --- a/test/testutils.js +++ /dev/null @@ -1,484 +0,0 @@ -var async = require("async"); - -var Amount = require("../src/js/ripple/amount").Amount; -var Remote = require("../src/js/ripple/remote").Remote; -var Server = require("./server").Server; - -var config = require('../src/js/ripple/config').load(require('./config')); - -var account_dump = function (remote, account, callback) { - var self = this; - - async.waterfall([ - function (callback) { - self.what = "Get latest account_root"; - - remote - .request_ledger_entry('account_root') - .ledger_hash(remote.ledger_hash()) - .account_root("root") - .on('success', function (r) { - //console.log("account_root: %s", JSON.stringify(r, undefined, 2)); - - callback(); - }) - .on('error', function(m) { - console.log("error: %s", m); - - buster.assert(false); - callback(); - }) - .request(); - }, - ], function (error) { - callback(error); - }); - - // get closed ledger hash - // get account root - // construct a json result - // -}; - -/** - * Helper called by test cases to generate a setUp routine. - * - * By default you would call this without options, but it is useful to - * be able to plug options in during development for quick and easy - * debugging. - * - * @example - * buster.testCase("Foobar", { - * setUp: testutils.build_setup({verbose: true}), - * // ... - * }); - * - * @param opts {Object} These options allow quick-and-dirty test-specific - * customizations of your test environment. - * @param opts.verbose {Bool} Enable all debug output (then cover your ears - * and run) - * @param opts.verbose_ws {Bool} Enable tracing in the Remote class. Prints - * websocket traffic. - * @param opts.verbose_server {Bool} Set the -v option when running rippled. - * @param opts.no_server {Bool} Don't auto-run rippled. - * @param host {String} Identifier for the host configuration to be used. - */ -var build_setup = function (opts, host) { - opts = opts || {}; - - // Normalize options - if (opts.verbose) { - opts.verbose_ws = true; - opts.verbose_server = true; - }; - - return function (done) { - var self = this; - - host = host || config.server_default; - - this.store = this.store || {}; - - var data = this.store[host] = this.store[host] || {}; - - data.opts = opts; - - async.series([ - function runServerStep(callback) { - if (opts.no_server) return callback(); - - data.server = Server - .from_config(host, !!opts.verbose_server) - .on('started', callback) - .on('exited', function () { - // If know the remote, tell it server is gone. - if (self.remote) - self.remote.server_fatal(); - }) - .start(); - }, - function connectWebsocketStep(callback) { - self.remote = data.remote = - Remote - .from_config(host, !!opts.verbose_ws) - .once('ledger_closed', callback) - .connect(); - } - ], done); - }; -}; - -/** - * Generate tearDown routine. - * - * @param host {String} Identifier for the host configuration to be used. - */ -var build_teardown = function (host) { - - return function (done) { - host = host || config.server_default; - - var data = this.store[host]; - var opts = data.opts; - - async.series([ - function disconnectWebsocketStep(callback) { - - data.remote - .on('disconnected', callback) - .connect(false); - }, - function stopServerStep(callback) { - if (opts.no_server) - { - return callback(); - } - - data.server - .on('stopped', callback) - .stop(); - } - ], done); - }; -}; - -var create_accounts = function (remote, src, amount, accounts, callback) { - assert(5 === arguments.length); - - remote.set_account_seq(src, 1); - - async.forEach(accounts, function (account, callback) { - // Cache the seq as 1. - // Otherwise, when other operations attempt to opperate async against the account they may get confused. - remote.set_account_seq(account, 1); - - remote.transaction() - .payment(src, account, amount) - .on('proposed', function (m) { - // console.log("proposed: %s", JSON.stringify(m)); - - if (m.result != 'tesSUCCESS') { - callback(new Error("Transaction did not succeed.")); - } else callback(null); - }) - .on('error', function (m) { - // console.log("error: %s", JSON.stringify(m)); - - callback(m); - }) - .submit(); - }, callback); -}; - -var credit_limit = function (remote, src, amount, callback) { - assert(4 === arguments.length); - - var _m = amount.match(/^(\d+\/...\/[^\:]+)(?::(\d+)(?:,(\d+))?)?$/); - if (!_m) { - console.log("credit_limit: parse error: %s", amount); - - callback('parse_error'); - } - else - { - // console.log("credit_limit: parsed: %s", JSON.stringify(_m, undefined, 2)); - var _account_limit = _m[1]; - var _quality_in = _m[2]; - var _quality_out = _m[3]; - - remote.transaction() - .ripple_line_set(src, _account_limit, _quality_in, _quality_out) - .on('proposed', function (m) { - // console.log("proposed: %s", JSON.stringify(m)); - - callback(m.result != 'tesSUCCESS'); - }) - .on('error', function (m) { - // console.log("error: %s", JSON.stringify(m)); - - callback(m); - }) - .submit(); - } -}; - -var verify_limit = function (remote, src, amount, callback) { - assert(4 === arguments.length); - - var _m = amount.match(/^(\d+\/...\/[^\:]+)(?::(\d+)(?:,(\d+))?)?$/); - if (!_m) { - // console.log("credit_limit: parse error: %s", amount); - - callback('parse_error'); - } - else - { - // console.log("verify_limit: parsed: %s", JSON.stringify(_m, undefined, 2)); - var _account_limit = _m[1]; - var _quality_in = _m[2]; - var _quality_out = _m[3]; - - var _limit = Amount.from_json(_account_limit); - - remote.request_ripple_balance(src, _limit.issuer().to_json(), _limit.currency().to_json(), 'CURRENT') - .once('ripple_state', function (m) { - buster.assert(m.account_limit.equals(_limit)); - buster.assert('undefined' === _quality_in || m.account_quality_in == _quality_in); - buster.assert('undefined' === _quality_out || m.account_quality_out == _quality_out); - - callback(); - }) - .once('error', function (m) { - // console.log("error: %s", JSON.stringify(m)); - - callback(m); - }) - .request(); - } -}; - -var credit_limits = function (remote, balances, callback) { - assert(3 === arguments.length); - - var limits = []; - - for (var src in balances) { - var values_src = balances[src]; - var values = 'string' === typeof values_src ? [ values_src ] : values_src; - - for (var index in values) { - limits.push( { "source" : src, "amount" : values[index] } ); - } - } - - async.every(limits, - function (limit, callback) { - credit_limit(remote, limit.source, limit.amount, - function (mismatch) { callback(!mismatch); }); - }, - function (every) { - callback(!every); - }); -}; - -var ledger_close = function (remote, callback) { - remote.once('ledger_closed', function (m) { callback(); }).ledger_accept(); -} - -var payment = function (remote, src, dst, amount, callback) { - assert(5 === arguments.length); - - remote.transaction() - .payment(src, dst, amount) - .on('proposed', function (m) { - // console.log("proposed: %s", JSON.stringify(m)); - - callback(m.result != 'tesSUCCESS'); - }) - .on('error', function (m) { - // console.log("error: %s", JSON.stringify(m)); - - callback(m); - }) - .submit(); -}; - -var payments = function (remote, balances, callback) { - assert(3 === arguments.length); - - var sends = []; - - for (var src in balances) { - var values_src = balances[src]; - var values = 'string' === typeof values_src ? [ values_src ] : values_src; - - for (var index in values) { - var amount_json = values[index]; - var amount = Amount.from_json(amount_json); - - sends.push( { "source" : src, "destination" : amount.issuer().to_json(), "amount" : amount_json } ); - } - } - - async.every(sends, - function (send, callback) { - payment(remote, send.source, send.destination, send.amount, - function (mismatch) { callback(!mismatch); }); - }, - function (every) { - callback(!every); - }); -}; - -var transfer_rate = function (remote, src, billionths, callback) { - assert(4 === arguments.length); - - remote.transaction() - .account_set(src) - .transfer_rate(billionths) - .on('proposed', function (m) { - // console.log("proposed: %s", JSON.stringify(m)); - - callback(m.result != 'tesSUCCESS'); - }) - .on('error', function (m) { - // console.log("error: %s", JSON.stringify(m)); - - callback(m); - }) - .submit(); -}; - -var verify_balance = function (remote, src, amount_json, callback) { - assert(4 === arguments.length); - var amount_req = Amount.from_json(amount_json); - - if (amount_req.is_native()) { - remote.request_account_balance(src, 'CURRENT') - .once('account_balance', function (amount_act) { - if (!amount_act.equals(amount_req, true)) { - console.log("verify_balance: failed: %s / %s", - amount_act.to_text_full(), - amount_req.to_text_full()); - } - - callback(!amount_act.equals(amount_req, true)); - }) - .request(); - } - else { - remote.request_ripple_balance(src, amount_req.issuer().to_json(), amount_req.currency().to_json(), 'CURRENT') - .once('ripple_state', function (m) { - // console.log("BALANCE: %s", JSON.stringify(m)); - // console.log("account_balance: %s", m.account_balance.to_text_full()); - // console.log("account_limit: %s", m.account_limit.to_text_full()); - // console.log("issuer_balance: %s", m.issuer_balance.to_text_full()); - // console.log("issuer_limit: %s", m.issuer_limit.to_text_full()); - - var account_balance = Amount.from_json(m.account_balance); - - if (!account_balance.equals(amount_req, true)) { - console.log("verify_balance: failed: %s vs %s / %s: %s", - src, - account_balance.to_text_full(), - amount_req.to_text_full(), - account_balance.not_equals_why(amount_req, true)); - } - - callback(!account_balance.equals(amount_req, true)); - }) - .request(); - } -}; - -var verify_balances = function (remote, balances, callback) { - var tests = []; - - for (var src in balances) { - var values_src = balances[src]; - var values = 'string' === typeof values_src ? [ values_src ] : values_src; - - for (var index in values) { - tests.push( { "source" : src, "amount" : values[index] } ); - } - } - - async.every(tests, - function (check, callback) { - verify_balance(remote, check.source, check.amount, - function (mismatch) { callback(!mismatch); }); - }, - function (every) { - callback(!every); - }); -}; - -// --> owner: account -// --> seq: sequence number of creating transaction. -// --> taker_gets: json amount -// --> taker_pays: json amount -var verify_offer = function (remote, owner, seq, taker_pays, taker_gets, callback) { - assert(6 === arguments.length); - - remote.request_ledger_entry('offer') - .offer_id(owner, seq) - .on('success', function (m) { - var wrong = !Amount.from_json(m.node.TakerGets).equals(Amount.from_json(taker_gets), true) - || !Amount.from_json(m.node.TakerPays).equals(Amount.from_json(taker_pays), true); - - if (wrong) - console.log("verify_offer: failed: %s", JSON.stringify(m)); - - callback(wrong); - }) - .request(); -}; - -var verify_offer_not_found = function (remote, owner, seq, callback) { - assert(4 === arguments.length); - - remote.request_ledger_entry('offer') - .offer_id(owner, seq) - .on('success', function (m) { - console.log("verify_offer_not_found: found offer: %s", JSON.stringify(m)); - - callback('entryFound'); - }) - .on('error', function (m) { - // console.log("verify_offer_not_found: success: %s", JSON.stringify(m)); - - callback('remoteError' !== m.error - || 'entryNotFound' !== m.remote.error); - }) - .request(); -}; - -var verify_owner_count = function (remote, account, value, callback) { - assert(4 === arguments.length); - - remote.request_owner_count(account, 'CURRENT') - .once('owner_count', function (owner_count) { - if (owner_count !== value) - console.log("owner_count: %s/%d", owner_count, value); - - callback(owner_count !== value); - }) - .request(); -}; - -var verify_owner_counts = function (remote, counts, callback) { - var tests = []; - - for (var src in counts) { - tests.push( { "source" : src, "count" : counts[src] } ); - } - - async.every(tests, - function (check, callback) { - verify_owner_count(remote, check.source, check.count, - function (mismatch) { callback(!mismatch); }); - }, - function (every) { - callback(!every); - }); -}; - -exports.account_dump = account_dump; -exports.build_setup = build_setup; -exports.build_teardown = build_teardown; -exports.create_accounts = create_accounts; -exports.credit_limit = credit_limit; -exports.credit_limits = credit_limits; -exports.ledger_close = ledger_close; -exports.payment = payment; -exports.payments = payments; -exports.transfer_rate = transfer_rate; -exports.verify_balance = verify_balance; -exports.verify_balances = verify_balances; -exports.verify_limit = verify_limit; -exports.verify_offer = verify_offer; -exports.verify_offer_not_found = verify_offer_not_found; -exports.verify_owner_count = verify_owner_count; -exports.verify_owner_counts = verify_owner_counts; - -// vim:sw=2:sts=2:ts=8:et From a1206f6e3f61569e4428432e03e61bdb10ad9c76 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Mon, 29 Jul 2013 13:09:19 -0700 Subject: [PATCH 3/9] Expose some of the modules related to the binary format. --- src/js/ripple/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/ripple/index.js b/src/js/ripple/index.js index 47b3b759..1cbf2d1e 100644 --- a/src/js/ripple/index.js +++ b/src/js/ripple/index.js @@ -6,7 +6,9 @@ exports.UInt160 = require('./amount').UInt160; exports.Seed = require('./amount').Seed; exports.Transaction = require('./transaction').Transaction; exports.Meta = require('./meta').Meta; +exports.SerializedObject = require('./serializedobject').SerializedObject; +exports.binformat = require('./binformat'); exports.utils = require('./utils'); // Important: We do not guarantee any specific version of SJCL or for any From 006d1fadcec57a020057aafd605b8b64c26fcbe8 Mon Sep 17 00:00:00 2001 From: jatchili Date: Mon, 29 Jul 2013 17:49:46 -0700 Subject: [PATCH 4/9] Don't prepend length when you're serializing a Hash256 --- src/js/ripple/serializedtypes.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/js/ripple/serializedtypes.js b/src/js/ripple/serializedtypes.js index 5fe7383c..daabf063 100644 --- a/src/js/ripple/serializedtypes.js +++ b/src/js/ripple/serializedtypes.js @@ -24,9 +24,11 @@ var SerializedType = function (methods) { extend(this, methods); }; -SerializedType.prototype.serialize_hex = function (so, hexData) { +SerializedType.prototype.serialize_hex = function (so, hexData, noLength) { var byteData = bytes.fromBits(hex.toBits(hexData)); - this.serialize_varint(so, byteData.length); + if (!noLength) { + this.serialize_varint(so, byteData.length); + } so.append(byteData); }; @@ -111,7 +113,7 @@ var STHash128 = exports.Hash128 = new SerializedType({ var STHash256 = exports.Hash256 = new SerializedType({ serialize: function (so, val) { var hash = UInt256.from_json(val); - this.serialize_hex(so, hash.to_hex()); + this.serialize_hex(so, hash.to_hex(), true); //noLength = true }, parse: function (so) { // XXX From fd67ea3036dc7cd119b7babc50b07c29758a75d7 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Mon, 29 Jul 2013 18:28:25 -0700 Subject: [PATCH 5/9] Refactor: Currency parsing. --- src/js/ripple/amount.js | 8 +------- src/js/ripple/currency.js | 30 +++++++++++++++++++----------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/js/ripple/amount.js b/src/js/ripple/amount.js index 5e09e3eb..1a81369b 100644 --- a/src/js/ripple/amount.js +++ b/src/js/ripple/amount.js @@ -795,13 +795,7 @@ Amount.prototype.parse_value = function (j) { }; Amount.prototype.set_currency = function (c) { - if ('string' === typeof c) { - this._currency = Currency.from_json(c); - } - else - { - this._currency = c; - } + this._currency = Currency.from_json(c); this._is_native = this._currency.is_native(); return this; diff --git a/src/js/ripple/currency.js b/src/js/ripple/currency.js index 49bb5429..8bcb5445 100644 --- a/src/js/ripple/currency.js +++ b/src/js/ripple/currency.js @@ -22,9 +22,11 @@ Currency.json_rewrite = function (j) { }; Currency.from_json = function (j) { - if (j instanceof Currency) return j.clone(); - else if ('string' === typeof j || 'number' === typeof j) return (new Currency()).parse_json(j); - else return new Currency(); // NaN + if (j instanceof Currency) { + return j.clone(); + } else { + return new Currency().parse_json(j); + } }; Currency.is_valid = function (j) { @@ -49,17 +51,23 @@ Currency.prototype.equals = function (d) { // this._value = NaN on error. Currency.prototype.parse_json = function (j) { - if ("" === j || "0" === j || "XRP" === j) { - this._value = 0; - } - else if ('number' === typeof j) { + if (j instanceof Currency) { + this._value = j; + } else if ('string' === typeof j) { + if (j === "" || j === "0" || j === "XRP") { + // XRP is never allowed as a Currency object + this._value = 0; + } else if (j.length === 3) { + this._value = j; + } else { + this._value = NaN; + } + } else if ('number' === typeof j) { // XXX This is a hack this._value = j; - } - else if ('string' != typeof j || 3 !== j.length) { + } else if ('string' != typeof j || 3 !== j.length) { this._value = NaN; - } - else { + } else { this._value = j; } From c48bf4089ca1c9181628d7bf49caf01815defc2a Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Mon, 29 Jul 2013 18:28:37 -0700 Subject: [PATCH 6/9] Fix serialization of XRP in PathSets. --- src/js/ripple/serializedtypes.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/ripple/serializedtypes.js b/src/js/ripple/serializedtypes.js index daabf063..9aced3ed 100644 --- a/src/js/ripple/serializedtypes.js +++ b/src/js/ripple/serializedtypes.js @@ -136,7 +136,9 @@ var STHash160 = exports.Hash160 = new SerializedType({ var STCurrency = new SerializedType({ serialize: function (so, val) { var currency = val.to_json(); - if ("string" === typeof currency && currency.length === 3) { + if ("XRP" === currency) { + this.serialize_hex(so, UInt160.HEX_ZERO, true); + } else if ("string" === typeof currency && currency.length === 3) { var currencyCode = currency.toUpperCase(), currencyData = utils.arraySet(20, 0); From 5bc1603f437219aa86febefdaf00cc891b4c887b Mon Sep 17 00:00:00 2001 From: jatchili Date: Tue, 30 Jul 2013 20:02:30 -0700 Subject: [PATCH 7/9] begin testing serialization/parsing functions --- src/js/ripple/currency.js | 41 +++++- src/js/ripple/serializedobject.js | 17 +++ src/js/ripple/serializedtypes.js | 202 ++++++++++++++++++++++++------ src/js/ripple/uint128.js | 32 +++++ test/serializedtypes-test.js | 42 +++++++ 5 files changed, 295 insertions(+), 39 deletions(-) create mode 100644 src/js/ripple/uint128.js create mode 100644 test/serializedtypes-test.js diff --git a/src/js/ripple/currency.js b/src/js/ripple/currency.js index 8bcb5445..3aa33d2d 100644 --- a/src/js/ripple/currency.js +++ b/src/js/ripple/currency.js @@ -29,6 +29,14 @@ Currency.from_json = function (j) { } }; +Currency.from_bytes = function (j) { + if (j instanceof Currency) { + return j.clone(); + } else { + return new Currency().parse_bytes(j); + } +}; + Currency.is_valid = function (j) { return Currency.from_json(j).is_valid(); }; @@ -64,16 +72,43 @@ Currency.prototype.parse_json = function (j) { } } else if ('number' === typeof j) { // XXX This is a hack - this._value = j; + this._value = j; } else if ('string' != typeof j || 3 !== j.length) { - this._value = NaN; + this._value = NaN; } else { - this._value = j; + this._value = j; } return this; }; +Currency.prototype.parse_bytes = function (byteArray) { + if (Array.isArray(byteArray) && byteArray.length == 20) { + var result; + // is it 0 everywhere except 12, 13, 14? + var isZeroExceptInStandardPositions = true; + for (var i=0; i<20; i++) { + isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || byteArray[0]===0) + } + if (isZeroExceptInStandardPositions) { + var currencyCode = String.fromCharCode(currency_data[12]) + String.fromCharCode(currency_data[13]) + String.fromCharCode(currency_data[14]); + if (/^[A-Z]{3}$/.test(currencyCode) && currencyCode !== "XRP" ) { + this._value = currencyCode; + } else if (currencyCode === "\0\0\0") { + this._value = 0; + } else { + this._value = NaN; + } + } else { + // XXX Should support non-standard currency codes + this._value = NaN; + } + } else { + this._value = NaN; + } + +} + Currency.prototype.is_native = function () { return !isNaN(this._value) && !this._value; }; diff --git a/src/js/ripple/serializedobject.js b/src/js/ripple/serializedobject.js index fd96232b..63279abc 100644 --- a/src/js/ripple/serializedobject.js +++ b/src/js/ripple/serializedobject.js @@ -45,6 +45,23 @@ SerializedObject.prototype.append = function (bytes) { this.pointer += bytes.length; }; +SerializedObject.prototype.resetPointer = function () { + this.pointer = 0; +}; + +SerializedObject.prototype.read = function (numberOfBytes) { + var start = this.pointer; + var end = start+numberOfBytes; + if (end > this.buffer.length) { + throw new Error("There aren't that many bytes left to read."); + } else { + var result = this.buffer.slice(start,end); + this.pointer = end; + return result; + } +}; + + SerializedObject.prototype.to_bits = function () { return sjcl.codec.bytes.toBits(this.buffer); diff --git a/src/js/ripple/serializedtypes.js b/src/js/ripple/serializedtypes.js index 9aced3ed..8e4bff75 100644 --- a/src/js/ripple/serializedtypes.js +++ b/src/js/ripple/serializedtypes.js @@ -19,20 +19,33 @@ var amount = require('./amount'), // Shortcuts var hex = sjcl.codec.hex, bytes = sjcl.codec.bytes; + +var jsbn = require('./jsbn'); +var BigInteger = jsbn.BigInteger; + var SerializedType = function (methods) { extend(this, methods); }; -SerializedType.prototype.serialize_hex = function (so, hexData, noLength) { +SerializedType.serialize_hex = function (so, hexData, noLength) { var byteData = bytes.fromBits(hex.toBits(hexData)); if (!noLength) { - this.serialize_varint(so, byteData.length); + SerializedType.serialize_varint(so, byteData.length); } so.append(byteData); }; -SerializedType.prototype.serialize_varint = function (so, val) { + + +/** + * parses bytes as hex + */ +function convert_bytes_to_hex (byte_array) { + return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)); +} + +SerializedType.serialize_varint = function (so, val) { if (val < 0) { throw new Error("Variable integers are unsigned."); } @@ -51,84 +64,156 @@ SerializedType.prototype.serialize_varint = function (so, val) { } else throw new Error("Variable integer overflow."); }; + +SerializedType.parse_varint = function (so) { + var b1 = so.read(1)[0], b2, b3; + if (b1 <= 192) { + return b1; + } else if (b1 <= 240) { + b2 = so.read(1)[0]; + return 193 + (b1-193)*256 + b2; + } else if (b1 <= 254) { + b2 = so.read(1)[0]; + b3 = so.read(1)[0]; + return 12481 + (b1-241)*65536 + b2*256 + b3 + } + else { + throw new Error("Invalid varint length indicator"); + } +}; + +// In the following, we assume that the inputs are in the proper range. Is this correct? + +// Helper functions for 1-, 2-, and 4-byte integers. + +// Convert an integer value into an array of bytes and append it to the serialized object ("so") +function append_byte_array(so, val, bytes) { + if (val < 0 || val >= (Math.pow(256, bytes))) { + throw new Error("Integer out of bounds"); + } + var newBytes = []; + for (var i=0; i>> (i*8) & 0xff); + } + so.append(newBytes); +} + +// Convert a certain number of bytes from the serialized object ("so") into an integer. +function readAndSum(so, bytes) { + var sum = 0; + for (var i = 0; i>> 8 & 0xff, val & 0xff - ]); + ]);*/ }, parse: function (so) { - // XXX - throw new Error("Parsing Int16 not implemented"); + return readAndSum(so, 2); } }); var STInt32 = exports.Int32 = new SerializedType({ serialize: function (so, val) { - so.append([ + append_byte_array(so, val, 4) + /*so.append([ val >>> 24 & 0xff, val >>> 16 & 0xff, val >>> 8 & 0xff, val & 0xff - ]); + ]);*/ }, parse: function (so) { - // XXX - throw new Error("Parsing Int32 not implemented"); + return readAndSum(so, 4); } }); + var STInt64 = exports.Int64 = new SerializedType({ serialize: function (so, val) { - // XXX - throw new Error("Serializing Int64 not implemented"); + var bigNumObject; + if ("number" === typeof val) { + bigNumObject = new BigInteger(val); + } else if ("string" === typeof val) { + bigNumObject = new BigInteger(val, 16); + } else if (val instanceof BigInteger) { + bigNumObject = val; + } else { + throw new Error("Invalid type for Int64"); + } + + var hex = bigNumObject.toString(16); + if (hex.length > 16) { + throw new Error("Int64 is too large"); + } + while (hex.length < 16) { + hex = "0" + hex; + } + return this.serialize_hex(so, hash.to_hex(), true); //noLength = true }, parse: function (so) { - // XXX - throw new Error("Parsing Int64 not implemented"); + var hi = readAndSum(so, 4); + var lo = readAndSum(so, 4); + + var result = new BigInteger(hi); + result.shiftLeft(32); + result.add(lo); + return result; } }); var STHash128 = exports.Hash128 = new SerializedType({ serialize: function (so, val) { - // XXX - throw new Error("Serializing Hash128 not implemented"); + var hash = UInt128.from_json(val); + if (!hash.is_valid()) { + throw new Error("Invalid Hash128"); + } + this.serialize_hex(so, hash.to_hex(), true); //noLength = true }, parse: function (so) { - // XXX - throw new Error("Parsing Hash128 not implemented"); + return UInt128.from_bytes(so.read(16)); } }); var STHash256 = exports.Hash256 = new SerializedType({ serialize: function (so, val) { var hash = UInt256.from_json(val); + if (!hash.is_valid()) { + throw new Error("Invalid Hash256"); + } this.serialize_hex(so, hash.to_hex(), true); //noLength = true }, parse: function (so) { - // XXX - throw new Error("Parsing Hash256 not implemented"); + return UInt256.from_bytes(so.read(32)); } }); var STHash160 = exports.Hash160 = new SerializedType({ serialize: function (so, val) { - // XXX - throw new Error("Serializing Hash160 not implemented"); + var hash = UInt160.from_json(val); // XXX Will this work? + this.serialize_hex(so, hash.to_hex(), true); //noLength = true }, parse: function (so) { - // XXX - throw new Error("Parsing Hash160 not implemented"); + return UInt160.from_bytes(so.read(20)); } }); @@ -142,7 +227,7 @@ var STCurrency = new SerializedType({ var currencyCode = currency.toUpperCase(), currencyData = utils.arraySet(20, 0); - if (!/^[A-Z]{3}$/.test(currencyCode)) { + if (!/^[A-Z]{3}$/.test(currencyCode) || currencyCode === "XRP" ) { throw new Error('Invalid currency code'); } @@ -156,8 +241,11 @@ var STCurrency = new SerializedType({ } }, parse: function (so) { - // XXX - throw new Error("Parsing Currency not implemented"); + var currency = Currency.from_bytes(so.read(20)); + if (!currency.is_valid()) { + throw new Error("Invalid currency"); + } + return currency; } }); @@ -220,8 +308,43 @@ var STAmount = exports.Amount = new SerializedType({ } }, parse: function (so) { - // XXX - throw new Error("Parsing Amount not implemented"); + var amount = new Amount(); + var value_bytes = so.read(8); + var is_zero = !(value_bytes[0] & 0x7f); + for (var i=1; i<8; i++) { + is_zero = is_zero && !value_bytes[i]; + } + if (value_bytes[0] & 0x80) { + //non-native + var currency_bytes = so.read(20); + var issuer_bytes = so.read(20); + var currency = STCurrency.parse(currency_bytes); + var issuer = UInt160.from_bytes(issuer_bytes); + + var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6); + var mantissa_bytes = value_bytes.slice(1); + mantissa_bytes[0] &= 0x3f; + var value = new BigInteger(mantissa_bytes, 256); + + if (value.equals(BigInteger.ZERO) && !is_zero ) { + throw new Error("Invalid zero representation"); + } + + amount._value = value; + amount._offset = offset; + amount._currency = currency; + amount._issuer = issuer; + amount._is_native = false; + + } else { + //native + var integer_bytes = value_bytes.slice(); + integer_bytes[0] &= 0x3f; + amount._value = new BigInteger(integer_bytes, 256); + amount._is_native = true; + } + amount._is_negative = !is_zero && !(value_bytes[0] & 0x40); + return amount; } }); @@ -231,8 +354,8 @@ var STVL = exports.VariableLength = new SerializedType({ else throw new Error("Unknown datatype."); }, parse: function (so) { - // XXX - throw new Error("Parsing VL not implemented"); + var len = this.parse_varint(so); + return convert_bytes_to_hex(so.read(len)); } }); @@ -242,8 +365,15 @@ var STAccount = exports.Account = new SerializedType({ this.serialize_hex(so, account.to_hex()); }, parse: function (so) { - // XXX - throw new Error("Parsing Account not implemented"); + var len = this.parse_varint(so); + if (len !== 20) { + throw new Error("Non-standard-length account ID"); + } + var result = UInt160.from_bytes(so.read(len)); + if (!result.is_valid()) { + throw new Error("Invalid Account"); + } + return result; } }); diff --git a/src/js/ripple/uint128.js b/src/js/ripple/uint128.js new file mode 100644 index 00000000..844986d7 --- /dev/null +++ b/src/js/ripple/uint128.js @@ -0,0 +1,32 @@ + +var sjcl = require('../../../build/sjcl'); +var utils = require('./utils'); +var config = require('./config'); +var jsbn = require('./jsbn'); +var extend = require('extend'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var UInt = require('./uint').UInt, + Base = require('./base').Base; + +// +// UInt128 support +// + +var UInt128 = extend(function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}, UInt); + +UInt128.width = 16; +UInt128.prototype = extend({}, UInt.prototype); +UInt128.prototype.constructor = UInt128; + +var HEX_ZERO = UInt128.HEX_ZERO = "00000000000000000000000000000000"; +var HEX_ONE = UInt128.HEX_ONE = "00000000000000000000000000000000"; +var STR_ZERO = UInt128.STR_ZERO = utils.hexToString(HEX_ZERO); +var STR_ONE = UInt128.STR_ONE = utils.hexToString(HEX_ONE); + +exports.UInt128 = UInt128; diff --git a/test/serializedtypes-test.js b/test/serializedtypes-test.js new file mode 100644 index 00000000..c81986b2 --- /dev/null +++ b/test/serializedtypes-test.js @@ -0,0 +1,42 @@ +var buster = require("buster"); + +var SerializedObject = require("../src/js/ripple/serializedobject").SerializedObject; +var types = require("../src/js/ripple/serializedtypes"); + +try { + var conf = require('./config'); +} catch(exception) { + var conf = require('./config-example'); +} + +var config = require('../src/js/ripple/config').load(conf); + + + +buster.testCase("Serialized types", { + "Int8" : { + "Serialize 0" : function () { + var so = new SerializedObject(); + types.Int8.serialize(so, 0); + assert.equals(so.to_hex(), "00"); + }, + "Serialize 123" : function () { + var so = new SerializedObject(); + types.Int8.serialize(so, 123); + assert.equals(so.to_hex(), "7B"); + }, + "Serialize 255" : function () { + var so = new SerializedObject(); + types.Int8.serialize(so, 255); + assert.equals(so.to_hex(), "FF"); + }, + "Fail to serialize 256" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int8.serialize(so, 256); + }); + }, + } +}); + +// vim:sw=2:sts=2:ts=8:et From 815e2af3cf2c3825688626500bf8ba6d7f1bd24c Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Tue, 30 Jul 2013 21:32:02 -0700 Subject: [PATCH 8/9] Fix code indentation. --- src/js/ripple/serializedtypes.js | 200 +++++++++++++++---------------- test/serializedtypes-test.js | 28 ++--- 2 files changed, 113 insertions(+), 115 deletions(-) diff --git a/src/js/ripple/serializedtypes.js b/src/js/ripple/serializedtypes.js index 8e4bff75..0c3cfd71 100644 --- a/src/js/ripple/serializedtypes.js +++ b/src/js/ripple/serializedtypes.js @@ -19,7 +19,7 @@ var amount = require('./amount'), // Shortcuts var hex = sjcl.codec.hex, bytes = sjcl.codec.bytes; - + var jsbn = require('./jsbn'); var BigInteger = jsbn.BigInteger; @@ -42,7 +42,7 @@ SerializedType.serialize_hex = function (so, hexData, noLength) { * parses bytes as hex */ function convert_bytes_to_hex (byte_array) { - return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)); + return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)); } SerializedType.serialize_varint = function (so, val) { @@ -68,17 +68,17 @@ SerializedType.serialize_varint = function (so, val) { SerializedType.parse_varint = function (so) { var b1 = so.read(1)[0], b2, b3; if (b1 <= 192) { - return b1; + return b1; } else if (b1 <= 240) { - b2 = so.read(1)[0]; - return 193 + (b1-193)*256 + b2; + b2 = so.read(1)[0]; + return 193 + (b1-193)*256 + b2; } else if (b1 <= 254) { - b2 = so.read(1)[0]; - b3 = so.read(1)[0]; - return 12481 + (b1-241)*65536 + b2*256 + b3 + b2 = so.read(1)[0]; + b3 = so.read(1)[0]; + return 12481 + (b1-241)*65536 + b2*256 + b3 } else { - throw new Error("Invalid varint length indicator"); + throw new Error("Invalid varint length indicator"); } }; @@ -88,23 +88,23 @@ SerializedType.parse_varint = function (so) { // Convert an integer value into an array of bytes and append it to the serialized object ("so") function append_byte_array(so, val, bytes) { - if (val < 0 || val >= (Math.pow(256, bytes))) { - throw new Error("Integer out of bounds"); - } - var newBytes = []; - for (var i=0; i>> (i*8) & 0xff); - } - so.append(newBytes); + if (val < 0 || val >= (Math.pow(256, bytes))) { + throw new Error("Integer out of bounds"); + } + var newBytes = []; + for (var i=0; i>> (i*8) & 0xff); + } + so.append(newBytes); } // Convert a certain number of bytes from the serialized object ("so") into an integer. function readAndSum(so, bytes) { - var sum = 0; - for (var i = 0; i>> 8 & 0xff, - val & 0xff - ]);*/ + val >>> 8 & 0xff, + val & 0xff + ]);*/ }, parse: function (so) { return readAndSum(so, 2); @@ -136,11 +136,11 @@ var STInt32 = exports.Int32 = new SerializedType({ serialize: function (so, val) { append_byte_array(so, val, 4) /*so.append([ - val >>> 24 & 0xff, - val >>> 16 & 0xff, - val >>> 8 & 0xff, - val & 0xff - ]);*/ + val >>> 24 & 0xff, + val >>> 16 & 0xff, + val >>> 8 & 0xff, + val & 0xff + ]);*/ }, parse: function (so) { return readAndSum(so, 4); @@ -152,32 +152,32 @@ var STInt64 = exports.Int64 = new SerializedType({ serialize: function (so, val) { var bigNumObject; if ("number" === typeof val) { - bigNumObject = new BigInteger(val); + bigNumObject = new BigInteger(val); } else if ("string" === typeof val) { - bigNumObject = new BigInteger(val, 16); + bigNumObject = new BigInteger(val, 16); } else if (val instanceof BigInteger) { - bigNumObject = val; + bigNumObject = val; } else { - throw new Error("Invalid type for Int64"); + throw new Error("Invalid type for Int64"); } - var hex = bigNumObject.toString(16); - if (hex.length > 16) { - throw new Error("Int64 is too large"); - } - while (hex.length < 16) { - hex = "0" + hex; - } - return this.serialize_hex(so, hash.to_hex(), true); //noLength = true + var hex = bigNumObject.toString(16); + if (hex.length > 16) { + throw new Error("Int64 is too large"); + } + while (hex.length < 16) { + hex = "0" + hex; + } + return this.serialize_hex(so, hash.to_hex(), true); //noLength = true }, parse: function (so) { - var hi = readAndSum(so, 4); - var lo = readAndSum(so, 4); - - var result = new BigInteger(hi); - result.shiftLeft(32); - result.add(lo); - return result; + var hi = readAndSum(so, 4); + var lo = readAndSum(so, 4); + + var result = new BigInteger(hi); + result.shiftLeft(32); + result.add(lo); + return result; } }); @@ -185,7 +185,7 @@ var STHash128 = exports.Hash128 = new SerializedType({ serialize: function (so, val) { var hash = UInt128.from_json(val); if (!hash.is_valid()) { - throw new Error("Invalid Hash128"); + throw new Error("Invalid Hash128"); } this.serialize_hex(so, hash.to_hex(), true); //noLength = true }, @@ -198,7 +198,7 @@ var STHash256 = exports.Hash256 = new SerializedType({ serialize: function (so, val) { var hash = UInt256.from_json(val); if (!hash.is_valid()) { - throw new Error("Invalid Hash256"); + throw new Error("Invalid Hash256"); } this.serialize_hex(so, hash.to_hex(), true); //noLength = true }, @@ -241,11 +241,11 @@ var STCurrency = new SerializedType({ } }, parse: function (so) { - var currency = Currency.from_bytes(so.read(20)); - if (!currency.is_valid()) { - throw new Error("Invalid currency"); - } - return currency; + var currency = Currency.from_bytes(so.read(20)); + if (!currency.is_valid()) { + throw new Error("Invalid currency"); + } + return currency; } }); @@ -309,42 +309,42 @@ var STAmount = exports.Amount = new SerializedType({ }, parse: function (so) { var amount = new Amount(); - var value_bytes = so.read(8); - var is_zero = !(value_bytes[0] & 0x7f); - for (var i=1; i<8; i++) { - is_zero = is_zero && !value_bytes[i]; - } - if (value_bytes[0] & 0x80) { - //non-native - var currency_bytes = so.read(20); - var issuer_bytes = so.read(20); - var currency = STCurrency.parse(currency_bytes); - var issuer = UInt160.from_bytes(issuer_bytes); - - var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6); - var mantissa_bytes = value_bytes.slice(1); - mantissa_bytes[0] &= 0x3f; - var value = new BigInteger(mantissa_bytes, 256); - - if (value.equals(BigInteger.ZERO) && !is_zero ) { - throw new Error("Invalid zero representation"); - } - - amount._value = value; - amount._offset = offset; - amount._currency = currency; - amount._issuer = issuer; - amount._is_native = false; - - } else { - //native - var integer_bytes = value_bytes.slice(); - integer_bytes[0] &= 0x3f; - amount._value = new BigInteger(integer_bytes, 256); - amount._is_native = true; - } - amount._is_negative = !is_zero && !(value_bytes[0] & 0x40); - return amount; + var value_bytes = so.read(8); + var is_zero = !(value_bytes[0] & 0x7f); + for (var i=1; i<8; i++) { + is_zero = is_zero && !value_bytes[i]; + } + if (value_bytes[0] & 0x80) { + //non-native + var currency_bytes = so.read(20); + var issuer_bytes = so.read(20); + var currency = STCurrency.parse(currency_bytes); + var issuer = UInt160.from_bytes(issuer_bytes); + + var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6); + var mantissa_bytes = value_bytes.slice(1); + mantissa_bytes[0] &= 0x3f; + var value = new BigInteger(mantissa_bytes, 256); + + if (value.equals(BigInteger.ZERO) && !is_zero ) { + throw new Error("Invalid zero representation"); + } + + amount._value = value; + amount._offset = offset; + amount._currency = currency; + amount._issuer = issuer; + amount._is_native = false; + + } else { + //native + var integer_bytes = value_bytes.slice(); + integer_bytes[0] &= 0x3f; + amount._value = new BigInteger(integer_bytes, 256); + amount._is_native = true; + } + amount._is_negative = !is_zero && !(value_bytes[0] & 0x40); + return amount; } }); @@ -355,7 +355,7 @@ var STVL = exports.VariableLength = new SerializedType({ }, parse: function (so) { var len = this.parse_varint(so); - return convert_bytes_to_hex(so.read(len)); + return convert_bytes_to_hex(so.read(len)); } }); @@ -366,13 +366,13 @@ var STAccount = exports.Account = new SerializedType({ }, parse: function (so) { var len = this.parse_varint(so); - if (len !== 20) { - throw new Error("Non-standard-length account ID"); - } - var result = UInt160.from_bytes(so.read(len)); - if (!result.is_valid()) { - throw new Error("Invalid Account"); - } + if (len !== 20) { + throw new Error("Non-standard-length account ID"); + } + var result = UInt160.from_bytes(so.read(len)); + if (!result.is_valid()) { + throw new Error("Invalid Account"); + } return result; } }); diff --git a/test/serializedtypes-test.js b/test/serializedtypes-test.js index c81986b2..0e9fc82d 100644 --- a/test/serializedtypes-test.js +++ b/test/serializedtypes-test.js @@ -11,30 +11,28 @@ try { var config = require('../src/js/ripple/config').load(conf); - - buster.testCase("Serialized types", { "Int8" : { "Serialize 0" : function () { - var so = new SerializedObject(); - types.Int8.serialize(so, 0); - assert.equals(so.to_hex(), "00"); + var so = new SerializedObject(); + types.Int8.serialize(so, 0); + assert.equals(so.to_hex(), "00"); }, "Serialize 123" : function () { - var so = new SerializedObject(); - types.Int8.serialize(so, 123); - assert.equals(so.to_hex(), "7B"); + var so = new SerializedObject(); + types.Int8.serialize(so, 123); + assert.equals(so.to_hex(), "7B"); }, "Serialize 255" : function () { - var so = new SerializedObject(); - types.Int8.serialize(so, 255); - assert.equals(so.to_hex(), "FF"); + var so = new SerializedObject(); + types.Int8.serialize(so, 255); + assert.equals(so.to_hex(), "FF"); }, "Fail to serialize 256" : function () { - var so = new SerializedObject(); - assert.exception(function () { - types.Int8.serialize(so, 256); - }); + var so = new SerializedObject(); + assert.exception(function () { + types.Int8.serialize(so, 256); + }); }, } }); From 78b1e4f570844c6ea1980b7b92149392243c82ac Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Tue, 30 Jul 2013 22:48:07 -0700 Subject: [PATCH 9/9] More work on new serialization/parsing functions. Added more tests. --- src/js/ripple/amount.js | 2 +- src/js/ripple/currency.js | 50 ++-- src/js/ripple/serializedobject.js | 12 +- src/js/ripple/serializedtypes.js | 66 +++-- src/js/ripple/uint.js | 21 +- test/serializedtypes-test.js | 399 ++++++++++++++++++++++++++++++ 6 files changed, 497 insertions(+), 53 deletions(-) diff --git a/src/js/ripple/amount.js b/src/js/ripple/amount.js index 1a81369b..2f3c671e 100644 --- a/src/js/ripple/amount.js +++ b/src/js/ripple/amount.js @@ -739,7 +739,7 @@ Amount.prototype.parse_value = function (j) { if ('number' === typeof j) { this._is_negative = j < 0; - this._value = new BigInteger(this._is_negative ? -j : j); + this._value = new BigInteger(Math.abs(j)); this._offset = 0; this.canonicalize(); diff --git a/src/js/ripple/currency.js b/src/js/ripple/currency.js index 3aa33d2d..54ca01ac 100644 --- a/src/js/ripple/currency.js +++ b/src/js/ripple/currency.js @@ -82,32 +82,32 @@ Currency.prototype.parse_json = function (j) { return this; }; -Currency.prototype.parse_bytes = function (byteArray) { - if (Array.isArray(byteArray) && byteArray.length == 20) { - var result; - // is it 0 everywhere except 12, 13, 14? - var isZeroExceptInStandardPositions = true; - for (var i=0; i<20; i++) { - isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || byteArray[0]===0) - } - if (isZeroExceptInStandardPositions) { - var currencyCode = String.fromCharCode(currency_data[12]) + String.fromCharCode(currency_data[13]) + String.fromCharCode(currency_data[14]); - if (/^[A-Z]{3}$/.test(currencyCode) && currencyCode !== "XRP" ) { - this._value = currencyCode; - } else if (currencyCode === "\0\0\0") { - this._value = 0; - } else { - this._value = NaN; - } - } else { - // XXX Should support non-standard currency codes - this._value = NaN; - } - } else { - this._value = NaN; +Currency.prototype.parse_bytes = function (byte_array) { + if (Array.isArray(byte_array) && byte_array.length == 20) { + var result; + // is it 0 everywhere except 12, 13, 14? + var isZeroExceptInStandardPositions = true; + for (var i=0; i<20; i++) { + isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || byte_array[0]===0) } - -} + if (isZeroExceptInStandardPositions) { + var currencyCode = String.fromCharCode(byte_array[12]) + String.fromCharCode(byte_array[13]) + String.fromCharCode(byte_array[14]); + if (/^[A-Z]{3}$/.test(currencyCode) && currencyCode !== "XRP" ) { + this._value = currencyCode; + } else if (currencyCode === "\0\0\0") { + this._value = 0; + } else { + this._value = NaN; + } + } else { + // XXX Should support non-standard currency codes + this._value = NaN; + } + } else { + this._value = NaN; + } + return this; +}; Currency.prototype.is_native = function () { return !isNaN(this._value) && !this._value; diff --git a/src/js/ripple/serializedobject.js b/src/js/ripple/serializedobject.js index 63279abc..75904f41 100644 --- a/src/js/ripple/serializedobject.js +++ b/src/js/ripple/serializedobject.js @@ -5,8 +5,16 @@ var binformat = require('./binformat'), var UInt256 = require('./uint256').UInt256; -var SerializedObject = function () { - this.buffer = []; +var SerializedObject = function (buf) { + if (Array.isArray(buf)) { + this.buffer = buf; + } else if ("string" === typeof buf) { + this.buffer = sjcl.codec.bytes.fromBits(sjcl.codec.hex.toBits(buf)); + } else if (!buf) { + this.buffer = []; + } else { + throw new Error("Invalid buffer passed."); + } this.pointer = 0; }; diff --git a/src/js/ripple/serializedtypes.js b/src/js/ripple/serializedtypes.js index 0c3cfd71..2b3f0860 100644 --- a/src/js/ripple/serializedtypes.js +++ b/src/js/ripple/serializedtypes.js @@ -11,7 +11,8 @@ var extend = require('extend'), sjcl = require('../../../build/sjcl'); var amount = require('./amount'), - UInt160 = amount.UInt160, + UInt128 = require('./uint128').UInt128, + UInt160 = require('./uint160').UInt160, UInt256 = require('./uint256').UInt256, Amount = amount.Amount, Currency= amount.Currency; @@ -28,7 +29,7 @@ var SerializedType = function (methods) { extend(this, methods); }; -SerializedType.serialize_hex = function (so, hexData, noLength) { +function serialize_hex(so, hexData, noLength) { var byteData = bytes.fromBits(hex.toBits(hexData)); if (!noLength) { SerializedType.serialize_varint(so, byteData.length); @@ -75,7 +76,7 @@ SerializedType.parse_varint = function (so) { } else if (b1 <= 254) { b2 = so.read(1)[0]; b3 = so.read(1)[0]; - return 12481 + (b1-241)*65536 + b2*256 + b3 + return 12481 + (b1-241)*65536 + b2*256 + b3 } else { throw new Error("Invalid varint length indicator"); @@ -86,8 +87,15 @@ SerializedType.parse_varint = function (so) { // Helper functions for 1-, 2-, and 4-byte integers. -// Convert an integer value into an array of bytes and append it to the serialized object ("so") +/** + * Convert an integer value into an array of bytes. + * + * The result is appended to the serialized object ("so"). + */ function append_byte_array(so, val, bytes) { + if ("number" !== typeof val) { + throw new Error("Integer is not a number"); + } if (val < 0 || val >= (Math.pow(256, bytes))) { throw new Error("Integer out of bounds"); } @@ -95,7 +103,7 @@ function append_byte_array(so, val, bytes) { for (var i=0; i>> (i*8) & 0xff); } - so.append(newBytes); + so.append(newBytes); } // Convert a certain number of bytes from the serialized object ("so") into an integer. @@ -111,11 +119,9 @@ function readAndSum(so, bytes) { var STInt8 = exports.Int8 = new SerializedType({ serialize: function (so, val) { append_byte_array(so, val, 1); - //so.append([val & 0xff]); }, parse: function (so) { return readAndSum(so, 1); - //return so.read(1)[0]; } }); @@ -152,10 +158,20 @@ var STInt64 = exports.Int64 = new SerializedType({ serialize: function (so, val) { var bigNumObject; if ("number" === typeof val) { - bigNumObject = new BigInteger(val); + val = Math.floor(val); + if (val < 0) { + throw new Error("Negative value for unsigned Int64 is invalid."); + } + bigNumObject = new BigInteger(""+val, 10); } else if ("string" === typeof val) { + if (!/^[0-9A-F]{0,16}$/i.test(val)) { + throw new Error("Not a valid hex Int64."); + } bigNumObject = new BigInteger(val, 16); } else if (val instanceof BigInteger) { + if (val.compareTo(BigInteger.ZERO) < 0) { + throw new Error("Negative value for unsigned Int64 is invalid."); + } bigNumObject = val; } else { throw new Error("Invalid type for Int64"); @@ -168,12 +184,12 @@ var STInt64 = exports.Int64 = new SerializedType({ while (hex.length < 16) { hex = "0" + hex; } - return this.serialize_hex(so, hash.to_hex(), true); //noLength = true + return serialize_hex(so, hex, true); //noLength = true }, parse: function (so) { var hi = readAndSum(so, 4); var lo = readAndSum(so, 4); - + var result = new BigInteger(hi); result.shiftLeft(32); result.add(lo); @@ -187,7 +203,7 @@ var STHash128 = exports.Hash128 = new SerializedType({ if (!hash.is_valid()) { throw new Error("Invalid Hash128"); } - this.serialize_hex(so, hash.to_hex(), true); //noLength = true + serialize_hex(so, hash.to_hex(), true); //noLength = true }, parse: function (so) { return UInt128.from_bytes(so.read(16)); @@ -200,7 +216,7 @@ var STHash256 = exports.Hash256 = new SerializedType({ if (!hash.is_valid()) { throw new Error("Invalid Hash256"); } - this.serialize_hex(so, hash.to_hex(), true); //noLength = true + serialize_hex(so, hash.to_hex(), true); //noLength = true }, parse: function (so) { return UInt256.from_bytes(so.read(32)); @@ -209,8 +225,11 @@ var STHash256 = exports.Hash256 = new SerializedType({ var STHash160 = exports.Hash160 = new SerializedType({ serialize: function (so, val) { - var hash = UInt160.from_json(val); // XXX Will this work? - this.serialize_hex(so, hash.to_hex(), true); //noLength = true + var hash = UInt160.from_json(val); + if (!hash.is_valid()) { + throw new Error("Invalid Hash160"); + } + serialize_hex(so, hash.to_hex(), true); //noLength = true }, parse: function (so) { return UInt160.from_bytes(so.read(20)); @@ -222,7 +241,7 @@ var STCurrency = new SerializedType({ serialize: function (so, val) { var currency = val.to_json(); if ("XRP" === currency) { - this.serialize_hex(so, UInt160.HEX_ZERO, true); + serialize_hex(so, UInt160.HEX_ZERO, true); } else if ("string" === typeof currency && currency.length === 3) { var currencyCode = currency.toUpperCase(), currencyData = utils.arraySet(20, 0); @@ -316,26 +335,25 @@ var STAmount = exports.Amount = new SerializedType({ } if (value_bytes[0] & 0x80) { //non-native - var currency_bytes = so.read(20); + var currency = STCurrency.parse(so); var issuer_bytes = so.read(20); - var currency = STCurrency.parse(currency_bytes); var issuer = UInt160.from_bytes(issuer_bytes); - - var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6); + + var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97; var mantissa_bytes = value_bytes.slice(1); mantissa_bytes[0] &= 0x3f; var value = new BigInteger(mantissa_bytes, 256); - + if (value.equals(BigInteger.ZERO) && !is_zero ) { throw new Error("Invalid zero representation"); } - + amount._value = value; amount._offset = offset; amount._currency = currency; amount._issuer = issuer; amount._is_native = false; - + } else { //native var integer_bytes = value_bytes.slice(); @@ -350,7 +368,7 @@ var STAmount = exports.Amount = new SerializedType({ var STVL = exports.VariableLength = new SerializedType({ serialize: function (so, val) { - if ("string" === typeof val) this.serialize_hex(so, val); + if ("string" === typeof val) serialize_hex(so, val); else throw new Error("Unknown datatype."); }, parse: function (so) { @@ -362,7 +380,7 @@ var STVL = exports.VariableLength = new SerializedType({ var STAccount = exports.Account = new SerializedType({ serialize: function (so, val) { var account = UInt160.from_json(val); - this.serialize_hex(so, account.to_hex()); + serialize_hex(so, account.to_hex()); }, parse: function (so) { var len = this.parse_varint(so); diff --git a/src/js/ripple/uint.js b/src/js/ripple/uint.js index a3220163..46eacc41 100644 --- a/src/js/ripple/uint.js +++ b/src/js/ripple/uint.js @@ -60,6 +60,15 @@ UInt.from_bits = function (j) { } }; +// Return a new UInt from j. +UInt.from_bytes = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_bytes(j); + } +}; + // Return a new UInt from j. UInt.from_bn = function (j) { if (j instanceof this) { @@ -157,7 +166,17 @@ UInt.prototype.parse_bits = function (j) { this._value = NaN; } else { var bytes = sjcl.codec.bytes.fromBits(j); - this._value = new BigInteger(bytes, 256); + this.parse_bytes(bytes); + } + + return this; +}; + +UInt.prototype.parse_bytes = function (j) { + if (!Array.isArray(j) || j.length !== this.constructor.width) { + this._value = NaN; + } else { + this._value = new BigInteger(j, 256); } return this; diff --git a/test/serializedtypes-test.js b/test/serializedtypes-test.js index 0e9fc82d..5d180fd0 100644 --- a/test/serializedtypes-test.js +++ b/test/serializedtypes-test.js @@ -3,6 +3,9 @@ var buster = require("buster"); var SerializedObject = require("../src/js/ripple/serializedobject").SerializedObject; var types = require("../src/js/ripple/serializedtypes"); +var jsbn = require('../src/js/ripple/jsbn'); +var BigInteger = jsbn.BigInteger; + try { var conf = require('./config'); } catch(exception) { @@ -34,6 +37,402 @@ buster.testCase("Serialized types", { types.Int8.serialize(so, 256); }); }, + "Fail to serialize -1" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int8.serialize(so, -1); + }); + }, + "Serialize 5.5 (should floor)" : function () { + var so = new SerializedObject(); + types.Int8.serialize(so, 5.5); + assert.equals(so.to_hex(), "05"); + }, + "Serialize 255.9 (should floor)" : function () { + var so = new SerializedObject(); + types.Int8.serialize(so, 255.9); + assert.equals(so.to_hex(), "FF"); + }, + "Fail to serialize null" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int8.serialize(so, null); + }); + }, + "Fail to serialize 'bla'" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int8.serialize(so, 'bla'); + }); + }, + "Fail to serialize {}" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int8.serialize(so, {}); + }); + } + }, + "Int16" : { + "Serialize 0" : function () { + var so = new SerializedObject(); + types.Int16.serialize(so, 0); + assert.equals(so.to_hex(), "0000"); + }, + "Serialize 123" : function () { + var so = new SerializedObject(); + types.Int16.serialize(so, 123); + assert.equals(so.to_hex(), "007B"); + }, + "Serialize 255" : function () { + var so = new SerializedObject(); + types.Int16.serialize(so, 255); + assert.equals(so.to_hex(), "00FF"); + }, + "Serialize 256" : function () { + var so = new SerializedObject(); + types.Int16.serialize(so, 256); + assert.equals(so.to_hex(), "0100"); + }, + "Serialize 65535" : function () { + var so = new SerializedObject(); + types.Int16.serialize(so, 65535); + assert.equals(so.to_hex(), "FFFF"); + }, + "Fail to serialize 65536" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int8.serialize(so, 65536); + }); + }, + "Fail to serialize -1" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int16.serialize(so, -1); + }); + }, + "Serialize 123.5 (should floor)" : function () { + var so = new SerializedObject(); + types.Int16.serialize(so, 123.5); + assert.equals(so.to_hex(), "007B"); + }, + "Serialize 65535.5 (should floor)" : function () { + var so = new SerializedObject(); + types.Int16.serialize(so, 65535.5); + assert.equals(so.to_hex(), "FFFF"); + }, + "Fail to serialize null" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int16.serialize(so, null); + }); + }, + "Fail to serialize 'bla'" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int16.serialize(so, 'bla'); + }); + }, + "Fail to serialize {}" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int16.serialize(so, {}); + }); + } + }, + "Int32" : { + "Serialize 0" : function () { + var so = new SerializedObject(); + types.Int32.serialize(so, 0); + assert.equals(so.to_hex(), "00000000"); + }, + "Serialize 123" : function () { + var so = new SerializedObject(); + types.Int32.serialize(so, 123); + assert.equals(so.to_hex(), "0000007B"); + }, + "Serialize 255" : function () { + var so = new SerializedObject(); + types.Int32.serialize(so, 255); + assert.equals(so.to_hex(), "000000FF"); + }, + "Serialize 256" : function () { + var so = new SerializedObject(); + types.Int32.serialize(so, 256); + assert.equals(so.to_hex(), "00000100"); + }, + "Serialize 0xF0F0F0F0" : function () { + var so = new SerializedObject(); + types.Int32.serialize(so, 0xF0F0F0F0); + assert.equals(so.to_hex(), "F0F0F0F0"); + }, + "Serialize 0xFFFFFFFF" : function () { + var so = new SerializedObject(); + types.Int32.serialize(so, 0xFFFFFFFF); + assert.equals(so.to_hex(), "FFFFFFFF"); + }, + "Fail to serialize 0x100000000" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int8.serialize(so, 0x100000000); + }); + }, + "Fail to serialize -1" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int32.serialize(so, -1); + }); + }, + "Serialize 123.5 (should floor)" : function () { + var so = new SerializedObject(); + types.Int32.serialize(so, 123.5); + assert.equals(so.to_hex(), "0000007B"); + }, + "Serialize 4294967295.5 (should floor)" : function () { + var so = new SerializedObject(); + types.Int32.serialize(so, 4294967295.5); + assert.equals(so.to_hex(), "FFFFFFFF"); + }, + "Fail to serialize null" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int32.serialize(so, null); + }); + }, + "Fail to serialize 'bla'" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int32.serialize(so, 'bla'); + }); + }, + "Fail to serialize {}" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int32.serialize(so, {}); + }); + } + }, + "Int64" : { + "Serialize 0" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, 0); + assert.equals(so.to_hex(), "0000000000000000"); + }, + "Serialize 123" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, 123); + assert.equals(so.to_hex(), "000000000000007B"); + }, + "Serialize 255" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, 255); + assert.equals(so.to_hex(), "00000000000000FF"); + }, + "Serialize 256" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, 256); + assert.equals(so.to_hex(), "0000000000000100"); + }, + "Serialize 0xF0F0F0F0" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, 0xF0F0F0F0); + assert.equals(so.to_hex(), "00000000F0F0F0F0"); + }, + "Serialize 0xFFFFFFFF" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, 0xFFFFFFFF); + assert.equals(so.to_hex(), "00000000FFFFFFFF"); + }, + "Serialize 0x100000000" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, 0x100000000); + assert.equals(so.to_hex(), "0000000100000000"); + }, + "Fail to serialize 0x100000000" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int8.serialize(so, 0x100000000); + }); + }, + "Fail to serialize -1" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int64.serialize(so, -1); + }); + }, + "Serialize 123.5 (should floor)" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, 123.5); + assert.equals(so.to_hex(), "000000000000007B"); + }, + "Serialize 4294967295.5 (should floor)" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, 4294967295.5); + assert.equals(so.to_hex(), "00000000FFFFFFFF"); + }, + "Serialize '0123456789ABCDEF'" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, "0123456789ABCDEF"); + assert.equals(so.to_hex(), "0123456789ABCDEF"); + }, + "Serialize 'F0E1D2C3B4A59687'" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, "F0E1D2C3B4A59687"); + assert.equals(so.to_hex(), "F0E1D2C3B4A59687"); + }, + "Serialize BigInteger('FFEEDDCCBBAA9988')" : function () { + var so = new SerializedObject(); + types.Int64.serialize(so, new BigInteger("FFEEDDCCBBAA9988", 16)); + assert.equals(so.to_hex(), "FFEEDDCCBBAA9988"); + }, + "Fail to serialize BigInteger('-1')" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int64.serialize(so, new BigInteger("-1", 10)); + }); + }, + "Fail to serialize '10000000000000000'" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int64.serialize(so, "10000000000000000"); + }); + }, + "Fail to serialize '110000000000000000'" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int64.serialize(so, "110000000000000000"); + }); + }, + "Fail to serialize null" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int64.serialize(so, null); + }); + }, + "Fail to serialize 'bla'" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int64.serialize(so, 'bla'); + }); + }, + "Fail to serialize {}" : function () { + var so = new SerializedObject(); + assert.exception(function () { + types.Int64.serialize(so, {}); + }); + } + }, + "Hash128" : { + "Serialize 0" : function () { + var so = new SerializedObject(); + types.Hash128.serialize(so, "00000000000000000000000000000000"); + assert.equals(so.to_hex(), "00000000000000000000000000000000"); + }, + "Serialize 102030405060708090A0B0C0D0E0F000" : function () { + var so = new SerializedObject(); + types.Hash128.serialize(so, "102030405060708090A0B0C0D0E0F000"); + assert.equals(so.to_hex(), "102030405060708090A0B0C0D0E0F000"); + }, + "Serialize FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" : function () { + var so = new SerializedObject(); + types.Hash128.serialize(so, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + assert.equals(so.to_hex(), "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + }, + }, + "Hash160" : { + "Serialize 0" : function () { + var so = new SerializedObject(); + types.Hash160.serialize(so, "rrrrrrrrrrrrrrrrrrrrrhoLvTp"); + assert.equals(so.to_hex(), "0000000000000000000000000000000000000000"); + }, + "Serialize 1" : function () { + var so = new SerializedObject(); + types.Hash160.serialize(so, "rrrrrrrrrrrrrrrrrrrrBZbvji"); + assert.equals(so.to_hex(), "0000000000000000000000000000000000000001"); + }, + "Serialize FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" : function () { + var so = new SerializedObject(); + types.Hash160.serialize(so, "rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi"); + assert.equals(so.to_hex(), "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + }, + }, + "Amount" : { + "Serialize 0 XRP" : function () { + var so = new SerializedObject(); + types.Amount.serialize(so, "0"); + assert.equals(so.to_hex(), "4000000000000000"); + }, + "Serialize 1 XRP" : function () { + var so = new SerializedObject(); + types.Amount.serialize(so, "1"); + assert.equals(so.to_hex(), "4000000000000001"); + }, + "Serialize -1 XRP" : function () { + var so = new SerializedObject(); + types.Amount.serialize(so, "-1"); + assert.equals(so.to_hex(), "0000000000000001"); + }, + "Serialize 213 XRP" : function () { + var so = new SerializedObject(); + types.Amount.serialize(so, "213"); + assert.equals(so.to_hex(), "40000000000000D5"); + }, + "Serialize 270544960 XRP" : function () { + var so = new SerializedObject(); + types.Amount.serialize(so, "270544960"); + assert.equals(so.to_hex(), "4000000010203040"); + }, + "Serialize 1161981756646125568 XRP" : function () { + var so = new SerializedObject(); + types.Amount.serialize(so, "1161981756646125696"); + assert.equals(so.to_hex(), "5020304050607080"); + }, + "Serialize 1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () { + var so = new SerializedObject(); + types.Amount.serialize(so, "1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"); + assert.equals(so.to_hex(), "D4838D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8"); + }, + "Serialize 87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () { + var so = new SerializedObject(); + types.Amount.serialize(so, "87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"); + assert.equals(so.to_hex(), "D65F241D335BF24E0000000000000000000000004555520000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8"); + }, + "Serialize -1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () { + var so = new SerializedObject(); + types.Amount.serialize(so, "-1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"); + assert.equals(so.to_hex(), "94838D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8"); + }, + "Parse 1 XRP" : function () { + var so = new SerializedObject("4000000000000001"); + assert.equals(types.Amount.parse(so).to_json(), "1"); + }, + "Parse -1 XRP" : function () { + var so = new SerializedObject("0000000000000001"); + assert.equals(types.Amount.parse(so).to_json(), "-1"); + }, + "Parse 213 XRP" : function () { + var so = new SerializedObject("40000000000000D5"); + assert.equals(types.Amount.parse(so).to_json(), "213"); + }, + "Parse 270544960 XRP" : function () { + var so = new SerializedObject("4000000010203040"); + assert.equals(types.Amount.parse(so).to_json(), "270544960"); + }, + "Parse 1161981756646125568 XRP" : function () { + var so = new SerializedObject("5020304050607080"); + assert.equals(types.Amount.parse(so).to_json(), "1161981756646125696"); + }, + "Parse 1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () { + var so = new SerializedObject("D4838D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8"); + assert.equals(types.Amount.parse(so).to_text_full(), "1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"); + }, + "Parse 87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () { + var so = new SerializedObject("D65F241D335BF24E0000000000000000000000004555520000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8"); + assert.equals(types.Amount.parse(so).to_text_full(), "87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"); + }, + "Parse -1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () { + var so = new SerializedObject("94838D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8"); + assert.equals(types.Amount.parse(so).to_text_full(), "-1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"); + }, } });