var binformat = require('./binformat'); var extend = require('extend'); var stypes = require('./serializedtypes'); var UInt256 = require('./uint256').UInt256; var assert = require('assert'); var utils = require('./utils'); var sjcl = utils.sjcl; var BigInteger = utils.jsbn.BigInteger; var TRANSACTION_TYPES = { }; Object.keys(binformat.tx).forEach(function(key) { TRANSACTION_TYPES[binformat.tx[key][0]] = key; }); var LEDGER_ENTRY_TYPES = {}; Object.keys(binformat.ledger).forEach(function(key) { LEDGER_ENTRY_TYPES[binformat.ledger[key][0]] = key; }); var TRANSACTION_RESULTS = {}; Object.keys(binformat.ter).forEach(function(key) { TRANSACTION_RESULTS[binformat.ter[key]] = key; }); function SerializedObject(buf) { if (Array.isArray(buf) || (Buffer && Buffer.isBuffer(buf)) ) { this.buffer = buf; } else if (typeof buf === 'string') { 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; }; SerializedObject.from_json = function (obj) { // Create a copy of the object so we don't modify it var obj = extend({}, obj); var so = new SerializedObject; var typedef; if ("number" === typeof obj.TransactionType) { obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType); if (!obj.TransactionType) { throw new Error('Transaction type ID is invalid.'); } } if ("string" === typeof obj.TransactionType) { typedef = binformat.tx[obj.TransactionType]; if (!Array.isArray(typedef)) { throw new Error('Transaction type is invalid'); } typedef = typedef.slice(); obj.TransactionType = typedef.shift(); } else if ("undefined" !== typeof obj.LedgerEntryType) { // XXX: TODO throw new Error('Ledger entry binary format not yet implemented.'); } else if ("object" === typeof obj.AffectedNodes) { typedef = binformat.metadata; } else { throw new Error('Object to be serialized must contain either' + ' TransactionType, LedgerEntryType or AffectedNodes.'); } // ND: This from_*json* seems a reasonable place to put validation of `json` SerializedObject.check_no_missing_fields(typedef, obj); so.serialize(typedef, obj); return so; }; SerializedObject.check_no_missing_fields = function (typedef, obj) { var missing_fields = []; for (var i = typedef.length - 1; i >= 0; i--) { var spec = typedef[i]; var field = spec[0] var requirement = spec[1]; if (binformat.REQUIRED === requirement && obj[field] == null) { missing_fields.push(field); }; }; if (missing_fields.length > 0) { var object_name; if (obj.TransactionType != null) { object_name = SerializedObject.lookup_type_tx(obj.TransactionType); } else { object_name = "TransactionMetaData"; } /*else { TODO: LedgerEntryType ... }*/ throw new Error(object_name + " is missing fields: " + JSON.stringify(missing_fields)); }; } SerializedObject.prototype.append = function (bytes) { if (bytes instanceof SerializedObject) { bytes = bytes.buffer; } this.buffer = this.buffer.concat(bytes); this.pointer += bytes.length; }; SerializedObject.prototype.resetPointer = function () { this.pointer = 0; }; function readOrPeek(advance) { return function(bytes) { var start = this.pointer; var end = start + bytes; if (end > this.buffer.length) { throw new Error('Buffer length exceeded'); } var result = this.buffer.slice(start, end); if (advance) { this.pointer = end; } return result; } }; SerializedObject.prototype.read = readOrPeek(true); SerializedObject.prototype.peek = readOrPeek(false); SerializedObject.prototype.to_bits = function () { return sjcl.codec.bytes.toBits(this.buffer); }; SerializedObject.prototype.to_hex = function () { return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase(); }; SerializedObject.prototype.to_json = function() { var old_pointer = this.pointer; this.resetPointer(); var output = { }; while (this.pointer < this.buffer.length) { var key_and_value = stypes.parse(this); var key = key_and_value[0]; var value = key_and_value[1]; output[key] = SerializedObject.jsonify_structure(value, key); } this.pointer = old_pointer; return output; } SerializedObject.jsonify_structure = function(structure, field_name) { var output; switch (typeof structure) { case 'number': switch (field_name) { case 'LedgerEntryType': output = LEDGER_ENTRY_TYPES[structure]; break; case 'TransactionResult': output = TRANSACTION_RESULTS[structure]; break; case 'TransactionType': output = TRANSACTION_TYPES[structure]; break; default: output = structure; } break; case 'object': if (!structure) break; //null if (typeof structure.to_json === 'function') { output = structure.to_json(); } else if (structure instanceof BigInteger) { output = structure.toString(16).toUpperCase(); } else { output = new structure.constructor; //new Array or Object var keys = Object.keys(structure); for (var i=0, l=keys.length; i 0xF) { buffer.push(type_id & 0xFF); } else { buffer[0] += (type_id & 0xF) << 4; } if (field_id > 0xF) { buffer.push(field_id & 0xFF); } else { buffer[0] += field_id & 0xF; } return buffer; }; SerializedObject.sort_typedef = function (typedef) { assert(Array.isArray(typedef)); function sort_field_compare(a, b) { // Sort by type id first, then by field id return a[3] !== b[3] ? stypes[a[3]].id - stypes[b[3]].id : a[2] - b[2]; }; return typedef.sort(sort_field_compare); }; SerializedObject.lookup_type_tx = function (id) { assert(typeof id === 'number'); return TRANSACTION_TYPES[id]; }; exports.SerializedObject = SerializedObject;