mirror of
https://github.com/Xahau/xahau.js.git
synced 2026-04-29 15:37:50 +00:00
925 lines
24 KiB
JavaScript
925 lines
24 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Type definitions for binary format.
|
|
*
|
|
* This file should not be included directly. Instead, find the format you're
|
|
* trying to parse or serialize in binformat.js and pass that to
|
|
* SerializedObject.parse() or SerializedObject.serialize().
|
|
*/
|
|
|
|
var assert = require('assert');
|
|
var extend = require('extend');
|
|
var GlobalBigNumber = require('bignumber.js');
|
|
var Amount = require('./amount').Amount;
|
|
var Currency = require('./currency').Currency;
|
|
var binformat = require('./binformat');
|
|
var utils = require('./utils');
|
|
var sjcl = utils.sjcl;
|
|
var SJCL_BN = sjcl.bn;
|
|
|
|
var UInt128 = require('./uint128').UInt128;
|
|
var UInt160 = require('./uint160').UInt160;
|
|
var UInt256 = require('./uint256').UInt256;
|
|
var Base = require('./base').Base;
|
|
|
|
var BigNumber = GlobalBigNumber.another({
|
|
ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP,
|
|
DECIMAL_PLACES: 40
|
|
});
|
|
|
|
function SerializedType(methods) {
|
|
extend(this, methods);
|
|
}
|
|
|
|
function isNumber(val) {
|
|
return typeof val === 'number' && isFinite(val);
|
|
}
|
|
|
|
function isString(val) {
|
|
return typeof val === 'string';
|
|
}
|
|
|
|
function isHexInt64String(val) {
|
|
return isString(val) && /^[0-9A-F]{0,16}$/i.test(val);
|
|
}
|
|
|
|
function serializeBits(so, bits, noLength) {
|
|
var byteData = sjcl.codec.bytes.fromBits(bits);
|
|
if (!noLength) {
|
|
SerializedType.serialize_varint(so, byteData.length);
|
|
}
|
|
so.append(byteData);
|
|
}
|
|
|
|
function serializeHex(so, hexData, noLength) {
|
|
serializeBits(so, sjcl.codec.hex.toBits(hexData), noLength);
|
|
}
|
|
|
|
/**
|
|
* parses bytes as hex
|
|
*
|
|
* @param {Array} byte_array bytes
|
|
* @return {String} hex string
|
|
*/
|
|
function convertByteArrayToHex(byte_array) {
|
|
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array))
|
|
.toUpperCase();
|
|
}
|
|
|
|
function convertHexToString(hexString) {
|
|
var bits = sjcl.codec.hex.toBits(hexString);
|
|
return sjcl.codec.utf8String.fromBits(bits);
|
|
}
|
|
|
|
function sort_fields(keys) {
|
|
function sort_field_compare(a, b) {
|
|
var a_field_coordinates = binformat.fieldsInverseMap[a];
|
|
var a_type_bits = a_field_coordinates[0];
|
|
var a_field_bits = a_field_coordinates[1];
|
|
var b_field_coordinates = binformat.fieldsInverseMap[b];
|
|
var b_type_bits = b_field_coordinates[0];
|
|
var b_field_bits = b_field_coordinates[1];
|
|
|
|
// Sort by type id first, then by field id
|
|
return a_type_bits !== b_type_bits
|
|
? a_type_bits - b_type_bits
|
|
: a_field_bits - b_field_bits;
|
|
}
|
|
|
|
return keys.sort(sort_field_compare);
|
|
}
|
|
|
|
SerializedType.serialize_varint = function(so, val) {
|
|
if (val < 0) {
|
|
throw new Error('Variable integers are unsigned.');
|
|
}
|
|
|
|
if (val <= 192) {
|
|
so.append([val]);
|
|
} else if (val <= 12480) {
|
|
val -= 193;
|
|
so.append([193 + (val >>> 8), val & 0xff]);
|
|
} else if (val <= 918744) {
|
|
val -= 12481;
|
|
so.append([241 + (val >>> 16), val >>> 8 & 0xff, val & 0xff]);
|
|
} else {
|
|
throw new Error('Variable integer overflow.');
|
|
}
|
|
};
|
|
|
|
SerializedType.prototype.parse_varint = function(so) {
|
|
var b1 = so.read(1)[0], b2, b3;
|
|
var result;
|
|
|
|
if (b1 > 254) {
|
|
throw new Error('Invalid varint length indicator');
|
|
}
|
|
|
|
if (b1 <= 192) {
|
|
result = b1;
|
|
} else if (b1 <= 240) {
|
|
b2 = so.read(1)[0];
|
|
result = 193 + (b1 - 193) * 256 + b2;
|
|
} else if (b1 <= 254) {
|
|
b2 = so.read(1)[0];
|
|
b3 = so.read(1)[0];
|
|
result = 12481 + (b1 - 241) * 65536 + b2 * 256 + b3;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
// 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.
|
|
*
|
|
* The result is appended to the serialized object ('so').
|
|
*
|
|
* @param {Number} val value
|
|
* @param {Number} bytes byte size
|
|
* @return {Array} byte array
|
|
*/
|
|
function convertIntegerToByteArray(val, bytes) {
|
|
if (!isNumber(val)) {
|
|
throw new Error('Value is not a number', bytes);
|
|
}
|
|
|
|
if (val < 0 || val >= Math.pow(256, bytes)) {
|
|
throw new Error('Value out of bounds ');
|
|
}
|
|
|
|
var newBytes = [ ];
|
|
|
|
for (var i = 0; i < bytes; i++) {
|
|
newBytes.unshift(val >>> (i * 8) & 0xff);
|
|
}
|
|
|
|
return newBytes;
|
|
}
|
|
|
|
// Convert a certain number of bytes from the serialized object ('so') into an
|
|
// integer.
|
|
function readAndSum(so, bytes) {
|
|
var sum = 0;
|
|
|
|
if (bytes > 4) {
|
|
throw new Error('This function only supports up to four bytes.');
|
|
}
|
|
|
|
for (var i = 0; i < bytes; i++) {
|
|
var byte = so.read(1)[0];
|
|
sum += (byte << (8 * (bytes - i - 1)));
|
|
}
|
|
|
|
// Convert to unsigned integer
|
|
return sum >>> 0;
|
|
}
|
|
|
|
var STInt8 = exports.Int8 = new SerializedType({
|
|
serialize: function(so, val) {
|
|
so.append(convertIntegerToByteArray(val, 1));
|
|
},
|
|
parse: function(so) {
|
|
return readAndSum(so, 1);
|
|
}
|
|
});
|
|
|
|
STInt8.id = 16;
|
|
|
|
function serialize(so, field_name, value) {
|
|
// so: a byte-stream to serialize into.
|
|
// field_name: a string for the field name ('LedgerEntryType' etc.)
|
|
// value: the value of that field.
|
|
var field_coordinates = binformat.fieldsInverseMap[field_name];
|
|
var type_bits = field_coordinates[0];
|
|
var field_bits = field_coordinates[1];
|
|
var tag_byte = (type_bits < 16
|
|
? type_bits << 4
|
|
: 0) | (field_bits < 16
|
|
? field_bits
|
|
: 0);
|
|
|
|
if (field_name === 'LedgerEntryType' && typeof value === 'string') {
|
|
value = binformat.ledger[value][0];
|
|
}
|
|
|
|
if (field_name === 'TransactionResult' && typeof value === 'string') {
|
|
value = binformat.ter[value];
|
|
}
|
|
|
|
STInt8.serialize(so, tag_byte);
|
|
|
|
if (type_bits >= 16) {
|
|
STInt8.serialize(so, type_bits);
|
|
}
|
|
|
|
if (field_bits >= 16) {
|
|
STInt8.serialize(so, field_bits);
|
|
}
|
|
|
|
// Get the serializer class (ST...)
|
|
var serialized_object_type;
|
|
|
|
if (field_name === 'Memo' && typeof value === 'object') {
|
|
// for Memo we override the default behavior with our STMemo serializer
|
|
serialized_object_type = exports.STMemo;
|
|
} else {
|
|
// for a field based on the type bits.
|
|
serialized_object_type = exports[binformat.types[type_bits]];
|
|
}
|
|
|
|
try {
|
|
serialized_object_type.serialize(so, value);
|
|
} catch (e) {
|
|
e.message += ' (' + field_name + ')';
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
exports.serialize = exports.serialize_whatever = serialize;
|
|
|
|
// Take the serialized object, figure out what type/field it is, and return the
|
|
// parsing of that.
|
|
|
|
function parse(so) {
|
|
var tag_byte = so.read(1)[0];
|
|
var type_bits = tag_byte >> 4;
|
|
|
|
if (type_bits === 0) {
|
|
type_bits = so.read(1)[0];
|
|
}
|
|
|
|
var field_bits = tag_byte & 0x0f;
|
|
var field_name = (field_bits === 0)
|
|
? field_name = binformat.fields[type_bits][so.read(1)[0]]
|
|
: field_name = binformat.fields[type_bits][field_bits];
|
|
|
|
assert(field_name, 'Unknown field - header byte is 0x'
|
|
+ tag_byte.toString(16));
|
|
|
|
// Get the parser class (ST...) for a field based on the type bits.
|
|
var type = (field_name === 'Memo')
|
|
? exports.STMemo
|
|
: exports[binformat.types[type_bits]];
|
|
|
|
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
|
|
|
|
return [field_name, type.parse(so)]; // key, value
|
|
}
|
|
|
|
exports.parse = exports.parse_whatever = parse;
|
|
|
|
var STInt16 = exports.Int16 = new SerializedType({
|
|
serialize: function(so, val) {
|
|
so.append(convertIntegerToByteArray(val, 2));
|
|
},
|
|
parse: function(so) {
|
|
return readAndSum(so, 2);
|
|
}
|
|
});
|
|
|
|
STInt16.id = 1;
|
|
|
|
var STInt32 = exports.Int32 = new SerializedType({
|
|
serialize: function(so, val) {
|
|
so.append(convertIntegerToByteArray(val, 4));
|
|
},
|
|
parse: function(so) {
|
|
return readAndSum(so, 4);
|
|
}
|
|
});
|
|
|
|
STInt32.id = 2;
|
|
|
|
var STInt64 = exports.Int64 = new SerializedType({
|
|
serialize: function(so, val) {
|
|
var bigNumObject;
|
|
|
|
if (isNumber(val)) {
|
|
val = Math.floor(val);
|
|
if (val < 0) {
|
|
throw new Error('Negative value for unsigned Int64 is invalid.');
|
|
}
|
|
bigNumObject = new SJCL_BN(val, 10);
|
|
} else if (isString(val)) {
|
|
if (!isHexInt64String(val)) {
|
|
throw new Error('Not a valid hex Int64.');
|
|
}
|
|
bigNumObject = new SJCL_BN(val, 16);
|
|
} else if (val instanceof SJCL_BN) {
|
|
if (!val.greaterEquals(0)) {
|
|
throw new Error('Negative value for unsigned Int64 is invalid.');
|
|
}
|
|
bigNumObject = val;
|
|
} else {
|
|
throw new Error('Invalid type for Int64');
|
|
}
|
|
serializeBits(so, bigNumObject.toBits(64), true); // noLength = true
|
|
},
|
|
parse: function(so) {
|
|
var bytes = so.read(8);
|
|
return SJCL_BN.fromBits(sjcl.codec.bytes.toBits(bytes));
|
|
}
|
|
});
|
|
|
|
STInt64.id = 3;
|
|
|
|
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');
|
|
}
|
|
serializeBits(so, hash.to_bits(), true); // noLength = true
|
|
},
|
|
parse: function(so) {
|
|
return UInt128.from_bytes(so.read(16));
|
|
}
|
|
});
|
|
|
|
STHash128.id = 4;
|
|
|
|
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');
|
|
}
|
|
serializeBits(so, hash.to_bits(), true); // noLength = true
|
|
},
|
|
parse: function(so) {
|
|
return UInt256.from_bytes(so.read(32));
|
|
}
|
|
});
|
|
|
|
STHash256.id = 5;
|
|
|
|
var STHash160 = exports.Hash160 = new SerializedType({
|
|
serialize: function(so, val) {
|
|
var hash = UInt160.from_json(val);
|
|
if (!hash.is_valid()) {
|
|
throw new Error('Invalid Hash160');
|
|
}
|
|
serializeBits(so, hash.to_bits(), true); // noLength = true
|
|
},
|
|
parse: function(so) {
|
|
return UInt160.from_bytes(so.read(20));
|
|
}
|
|
});
|
|
|
|
STHash160.id = 17;
|
|
|
|
// Internal
|
|
var STCurrency = new SerializedType({
|
|
serialize: function(so, val) {
|
|
var currencyData = val.to_bytes();
|
|
|
|
if (!currencyData) {
|
|
throw new Error(
|
|
'Tried to serialize invalid/unimplemented currency type.');
|
|
}
|
|
|
|
so.append(currencyData);
|
|
},
|
|
parse: function(so) {
|
|
var bytes = so.read(20);
|
|
var currency = Currency.from_bytes(bytes);
|
|
// XXX Disabled check. Theoretically, the Currency class should support any
|
|
// UInt160 value and consider it valid. But it doesn't, so for the
|
|
// deserialization to be usable, we need to allow invalid results for
|
|
// now.
|
|
// if (!currency.is_valid()) {
|
|
// throw new Error('Invalid currency: '+convertByteArrayToHex(bytes));
|
|
// }
|
|
return currency;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Quality is encoded into 64 bits:
|
|
* (8 bits offset) (56 bits mantissa)
|
|
*
|
|
* Quality differs from Amount because it does not need the first two bits
|
|
* to represent non-native and non-negative
|
|
*/
|
|
exports.Quality = new SerializedType({
|
|
serialize: function(so, val) {
|
|
var amount = Amount.from_json(val);
|
|
|
|
if (!amount.is_valid()) {
|
|
throw new Error('Not a valid Amount object.');
|
|
}
|
|
|
|
var hi = 0, lo = 0;
|
|
var value = new BigNumber(amount.to_text());
|
|
var offset = value.e - 15;
|
|
|
|
if (!amount.is_zero()) {
|
|
// First eight bits: offset/exponent
|
|
hi |= ((100 + offset) & 0xff) << 24;
|
|
|
|
// Remaining 56 bits: mantissa
|
|
var mantissaDecimal = utils.getMantissaDecimalString(value.abs());
|
|
var mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
|
|
assert(mantissaHex.length <= 16,
|
|
'Mantissa hex representation ' + mantissaHex +
|
|
' exceeds the maximum length of 16');
|
|
hi |= parseInt(mantissaHex.slice(0, -8), 16) & 0xffffff;
|
|
lo = parseInt(mantissaHex.slice(-8), 16);
|
|
}
|
|
|
|
var valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
|
|
|
|
so.append(valueBytes);
|
|
}
|
|
});
|
|
|
|
/*
|
|
* Amount is encoded into 64 bits:
|
|
* (1 bit non-native) (1 bit non-negative) (8 bits offset) (54 bits mantissa)
|
|
*/
|
|
var STAmount = exports.Amount = new SerializedType({
|
|
serialize: function(so, val) {
|
|
var amount = Amount.from_json(val);
|
|
|
|
if (!amount.is_valid()) {
|
|
throw new Error('Not a valid Amount object.');
|
|
}
|
|
|
|
var value = new BigNumber(amount.to_text());
|
|
var offset = value.e - 15;
|
|
|
|
// Amount (64-bit integer)
|
|
var valueBytes = utils.arraySet(8, 0);
|
|
|
|
if (amount.is_native()) {
|
|
var valueHex = value.abs().toString(16);
|
|
|
|
if (Amount.strict_mode && value.abs().greaterThan(Amount.bi_xns_max)) {
|
|
throw new Error('Value out of bounds');
|
|
}
|
|
|
|
// Enforce correct length (64 bits)
|
|
if (Amount.strict_mode && valueHex.length > 16) {
|
|
throw new Error('Value out of bounds');
|
|
}
|
|
|
|
while (valueHex.length < 16) {
|
|
valueHex = '0' + valueHex;
|
|
}
|
|
|
|
valueBytes = sjcl.codec.bytes.fromBits(sjcl.codec.hex.toBits(valueHex));
|
|
// Clear most significant two bits - these bits should already be 0 if
|
|
// Amount enforces the range correctly, but we'll clear them anyway just
|
|
// so this code can make certain guarantees about the encoded value.
|
|
valueBytes[0] &= 0x3f;
|
|
|
|
if (!amount.is_negative()) {
|
|
valueBytes[0] |= 0x40;
|
|
}
|
|
} else {
|
|
var hi = 0, lo = 0;
|
|
|
|
// First bit: non-native
|
|
hi |= 1 << 31;
|
|
|
|
if (!amount.is_zero()) {
|
|
// Second bit: non-negative?
|
|
if (!amount.is_negative()) {
|
|
hi |= 1 << 30;
|
|
}
|
|
|
|
// Next eight bits: offset/exponent
|
|
hi |= ((97 + offset) & 0xff) << 22;
|
|
|
|
// Remaining 54 bits: mantissa
|
|
var mantissaDecimal = utils.getMantissaDecimalString(value.abs());
|
|
var mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
|
|
assert(mantissaHex.length <= 16,
|
|
'Mantissa hex representation ' + mantissaHex +
|
|
' exceeds the maximum length of 16');
|
|
hi |= parseInt(mantissaHex.slice(0, -8), 16) & 0x3fffff;
|
|
lo = parseInt(mantissaHex.slice(-8), 16);
|
|
}
|
|
|
|
valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
|
|
}
|
|
|
|
so.append(valueBytes);
|
|
|
|
if (!amount.is_native()) {
|
|
// Currency (160-bit hash)
|
|
var currency = amount.currency();
|
|
STCurrency.serialize(so, currency, true);
|
|
|
|
// Issuer (160-bit hash)
|
|
so.append(amount.issuer().to_bytes());
|
|
}
|
|
},
|
|
parse: function(so) {
|
|
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];
|
|
}
|
|
|
|
var is_negative = !is_zero && !(value_bytes[0] & 0x40);
|
|
|
|
if (value_bytes[0] & 0x80) {
|
|
// non-native
|
|
var currency = STCurrency.parse(so);
|
|
var issuer_bytes = so.read(20);
|
|
var issuer = UInt160.from_bytes(issuer_bytes);
|
|
issuer.set_version(Base.VER_ACCOUNT_ID);
|
|
var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97;
|
|
var mantissa_bytes = value_bytes.slice(1);
|
|
mantissa_bytes[0] &= 0x3f;
|
|
var mantissa = new BigNumber(utils.arrayToHex(mantissa_bytes), 16);
|
|
var sign = is_negative ? '-' : '';
|
|
var valueString = sign + mantissa.toString() + 'e' + offset.toString();
|
|
|
|
return Amount.from_json({
|
|
currency: currency,
|
|
issuer: issuer.to_json(),
|
|
value: valueString
|
|
});
|
|
}
|
|
|
|
// native
|
|
var integer_bytes = value_bytes.slice();
|
|
integer_bytes[0] &= 0x3f;
|
|
var integer_hex = utils.arrayToHex(integer_bytes);
|
|
var value = new BigNumber(integer_hex, 16);
|
|
return Amount.from_json((is_negative ? '-' : '') + value.toString());
|
|
}
|
|
});
|
|
|
|
STAmount.id = 6;
|
|
|
|
var STVL = exports.VariableLength = exports.VL = new SerializedType({
|
|
serialize: function(so, val) {
|
|
if (typeof val === 'string') {
|
|
serializeHex(so, val);
|
|
} else {
|
|
throw new Error('Unknown datatype.');
|
|
}
|
|
},
|
|
parse: function(so) {
|
|
var len = this.parse_varint(so);
|
|
return convertByteArrayToHex(so.read(len));
|
|
}
|
|
});
|
|
|
|
STVL.id = 7;
|
|
|
|
var STAccount = exports.Account = new SerializedType({
|
|
serialize: function(so, val) {
|
|
var account = UInt160.from_json(val);
|
|
if (!account.is_valid()) {
|
|
throw new Error('Invalid account!');
|
|
}
|
|
serializeBits(so, account.to_bits());
|
|
},
|
|
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));
|
|
result.set_version(Base.VER_ACCOUNT_ID);
|
|
|
|
if (false && !result.is_valid()) {
|
|
throw new Error('Invalid Account');
|
|
}
|
|
|
|
return result;
|
|
}
|
|
});
|
|
|
|
STAccount.id = 8;
|
|
|
|
var STPathSet = exports.PathSet = new SerializedType({
|
|
typeBoundary: 0xff,
|
|
typeEnd: 0x00,
|
|
typeAccount: 0x01,
|
|
typeCurrency: 0x10,
|
|
typeIssuer: 0x20,
|
|
serialize: function(so, val) {
|
|
for (var i = 0, l = val.length; i < l; i++) {
|
|
// Boundary
|
|
if (i) {
|
|
STInt8.serialize(so, this.typeBoundary);
|
|
}
|
|
|
|
for (var j = 0, l2 = val[i].length; j < l2; j++) {
|
|
var entry = val[i][j];
|
|
// if (entry.hasOwnProperty('_value')) {entry = entry._value;}
|
|
var type = 0;
|
|
|
|
if (entry.account) {
|
|
type |= this.typeAccount;
|
|
}
|
|
if (entry.currency) {
|
|
type |= this.typeCurrency;
|
|
}
|
|
if (entry.issuer) {
|
|
type |= this.typeIssuer;
|
|
}
|
|
|
|
STInt8.serialize(so, type);
|
|
|
|
if (entry.account) {
|
|
STHash160.serialize(so, entry.account);
|
|
}
|
|
|
|
if (entry.currency) {
|
|
var currency = Currency.from_json(entry.currency, entry.non_native);
|
|
STCurrency.serialize(so, currency);
|
|
}
|
|
|
|
if (entry.issuer) {
|
|
STHash160.serialize(so, entry.issuer);
|
|
}
|
|
}
|
|
}
|
|
|
|
STInt8.serialize(so, this.typeEnd);
|
|
},
|
|
parse: function(so) {
|
|
// should return a list of lists:
|
|
/*
|
|
[
|
|
[entry, entry],
|
|
[entry, entry, entry],
|
|
[entry],
|
|
[]
|
|
]
|
|
|
|
each entry has one or more of the following attributes:
|
|
amount, currency, issuer.
|
|
*/
|
|
|
|
var path_list = [];
|
|
var current_path = [];
|
|
var tag_byte;
|
|
|
|
/* eslint-disable no-cond-assign */
|
|
|
|
while ((tag_byte = so.read(1)[0]) !== this.typeEnd) {
|
|
// TODO: try/catch this loop, and catch when we run out of data without
|
|
// reaching the end of the data structure.
|
|
// Now determine: is this an end, boundary, or entry-begin-tag?
|
|
// console.log('Tag byte:', tag_byte);
|
|
if (tag_byte === this.typeBoundary) {
|
|
if (current_path) { // close the current path, if there is one,
|
|
path_list.push(current_path);
|
|
}
|
|
current_path = [ ]; // and start a new one.
|
|
continue;
|
|
}
|
|
|
|
// It's an entry-begin tag.
|
|
var entry = {};
|
|
var type = 0;
|
|
|
|
if (tag_byte & this.typeAccount) {
|
|
entry.account = STHash160.parse(so);
|
|
entry.account.set_version(Base.VER_ACCOUNT_ID);
|
|
type = type | this.typeAccount;
|
|
}
|
|
if (tag_byte & this.typeCurrency) {
|
|
entry.currency = STCurrency.parse(so);
|
|
if (entry.currency.to_json() === 'XRP' && !entry.currency.is_native()) {
|
|
entry.non_native = true;
|
|
}
|
|
type = type | this.typeCurrency;
|
|
}
|
|
if (tag_byte & this.typeIssuer) {
|
|
entry.issuer = STHash160.parse(so);
|
|
// Enable and set correct type of base-58 encoding
|
|
entry.issuer.set_version(Base.VER_ACCOUNT_ID);
|
|
type = type | this.typeIssuer;
|
|
}
|
|
|
|
if (entry.account || entry.currency || entry.issuer) {
|
|
entry.type = type;
|
|
entry.type_hex = ('000000000000000' + type.toString(16)).slice(-16);
|
|
current_path.push(entry);
|
|
} else {
|
|
// It must have at least something in it.
|
|
throw new Error('Invalid path entry');
|
|
}
|
|
}
|
|
|
|
if (current_path) {
|
|
// close the current path, if there is one,
|
|
path_list.push(current_path);
|
|
}
|
|
|
|
return path_list;
|
|
}
|
|
});
|
|
|
|
STPathSet.id = 18;
|
|
|
|
var STVector256 = exports.Vector256 = new SerializedType({
|
|
serialize: function(so, val) {
|
|
// Assume val is an array of STHash256 objects.
|
|
SerializedType.serialize_varint(so, val.length * 32);
|
|
for (var i = 0, l = val.length; i < l; i++) {
|
|
STHash256.serialize(so, val[i]);
|
|
}
|
|
},
|
|
parse: function(so) {
|
|
var length = this.parse_varint(so);
|
|
var output = [];
|
|
// length is number of bytes not number of Hash256
|
|
for (var i = 0; i < length / 32; i++) {
|
|
output.push(STHash256.parse(so));
|
|
}
|
|
return output;
|
|
}
|
|
});
|
|
|
|
STVector256.id = 19;
|
|
|
|
// Internal
|
|
exports.STMemo = new SerializedType({
|
|
serialize: function(so, val, no_marker) {
|
|
var keys = [];
|
|
|
|
Object.keys(val).forEach(function(key) {
|
|
// Ignore lowercase field names - they're non-serializable fields by
|
|
// convention.
|
|
if (key[0] === key[0].toLowerCase()) {
|
|
return;
|
|
}
|
|
|
|
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
|
|
throw new Error('JSON contains unknown field: "' + key + '"');
|
|
}
|
|
|
|
keys.push(key);
|
|
});
|
|
|
|
// Sort fields
|
|
keys = sort_fields(keys);
|
|
|
|
keys.forEach(function(key) {
|
|
serialize(so, key, val[key]);
|
|
});
|
|
|
|
if (!no_marker) {
|
|
// Object ending marker
|
|
STInt8.serialize(so, 0xe1);
|
|
}
|
|
},
|
|
parse: function(so) {
|
|
var output = {};
|
|
|
|
while (so.peek(1)[0] !== 0xe1) {
|
|
var keyval = parse(so);
|
|
output[keyval[0]] = keyval[1];
|
|
}
|
|
|
|
if (output.MemoType !== undefined) {
|
|
try {
|
|
var parsedType = convertHexToString(output.MemoType);
|
|
|
|
if (parsedType !== 'unformatted_memo') {
|
|
output.parsed_memo_type = parsedType;
|
|
}
|
|
} catch (e) {
|
|
// empty
|
|
// we don't know what's in the binary, apparently it's not a UTF-8
|
|
// string
|
|
// this is fine, we won't add the parsed_memo_type field
|
|
}
|
|
}
|
|
|
|
if (output.MemoFormat !== undefined) {
|
|
try {
|
|
output.parsed_memo_format = convertHexToString(output.MemoFormat);
|
|
} catch (e) {
|
|
// empty
|
|
// we don't know what's in the binary, apparently it's not a UTF-8
|
|
// string
|
|
// this is fine, we won't add the parsed_memo_format field
|
|
}
|
|
}
|
|
|
|
if (output.MemoData !== undefined) {
|
|
|
|
try {
|
|
if (output.parsed_memo_format === 'json') {
|
|
// see if we can parse JSON
|
|
output.parsed_memo_data =
|
|
JSON.parse(convertHexToString(output.MemoData));
|
|
|
|
} else if (output.parsed_memo_format === 'text') {
|
|
// otherwise see if we can parse text
|
|
output.parsed_memo_data = convertHexToString(output.MemoData);
|
|
}
|
|
} catch(e) {
|
|
// empty
|
|
// we'll fail in case the content does not match what the MemoFormat
|
|
// described
|
|
// this is fine, we won't add the parsed_memo_data, the user has to
|
|
// parse themselves
|
|
}
|
|
}
|
|
|
|
so.read(1);
|
|
return output;
|
|
}
|
|
|
|
});
|
|
|
|
var STObject = exports.Object = new SerializedType({
|
|
serialize: function(so, val, no_marker) {
|
|
var keys = [];
|
|
|
|
Object.keys(val).forEach(function(key) {
|
|
// Ignore lowercase field names - they're non-serializable fields by
|
|
// convention.
|
|
if (key[0] === key[0].toLowerCase()) {
|
|
return;
|
|
}
|
|
|
|
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
|
|
throw new Error('JSON contains unknown field: "' + key + '"');
|
|
}
|
|
|
|
keys.push(key);
|
|
});
|
|
|
|
// Sort fields
|
|
keys = sort_fields(keys);
|
|
|
|
for (var i = 0; i < keys.length; i++) {
|
|
serialize(so, keys[i], val[keys[i]]);
|
|
}
|
|
|
|
if (!no_marker) {
|
|
// Object ending marker
|
|
STInt8.serialize(so, 0xe1);
|
|
}
|
|
},
|
|
|
|
parse: function(so) {
|
|
var output = {};
|
|
while (so.peek(1)[0] !== 0xe1) {
|
|
var keyval = parse(so);
|
|
output[keyval[0]] = keyval[1];
|
|
}
|
|
so.read(1);
|
|
return output;
|
|
}
|
|
});
|
|
|
|
STObject.id = 14;
|
|
|
|
var STArray = exports.Array = new SerializedType({
|
|
serialize: function(so, val) {
|
|
for (var i = 0, l = val.length; i < l; i++) {
|
|
var keys = Object.keys(val[i]);
|
|
|
|
if (keys.length !== 1) {
|
|
throw new Error(
|
|
'Cannot serialize an array containing non-single-key objects');
|
|
}
|
|
|
|
var field_name = keys[0];
|
|
var value = val[i][field_name];
|
|
serialize(so, field_name, value);
|
|
}
|
|
|
|
// Array ending marker
|
|
STInt8.serialize(so, 0xf1);
|
|
},
|
|
|
|
parse: function(so) {
|
|
var output = [ ];
|
|
|
|
while (so.peek(1)[0] !== 0xf1) {
|
|
var keyval = parse(so);
|
|
var obj = { };
|
|
obj[keyval[0]] = keyval[1];
|
|
output.push(obj);
|
|
}
|
|
|
|
so.read(1);
|
|
|
|
return output;
|
|
}
|
|
});
|
|
|
|
STArray.id = 15;
|