Skip to content

Commit 1704ac4

Browse files
committed
[FEATURE] improve memo support
- add MemoFormat property for memo - MemoFormat and MemoType must be valid ASCII - Memo content is converted on the serialization level - add parsed_* version of Memo content if the parser understand the format - support `text` and `json` MemoFormat [FIX] double serialization overriding Memo contents The copy made in from_json wasn't a deep copy
1 parent 666e434 commit 1704ac4

6 files changed

+553
-67
lines changed

src/js/ripple/binformat.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
* Data type map.
33
*
44
* Mapping of type ids to data types. The type id is specified by the high
5+
*
6+
* For reference, see rippled's definition:
7+
* https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol/SField.cpp
58
*/
69
var TYPES_MAP = exports.types = [
710
void(0),
@@ -375,7 +378,7 @@ exports.ledger = {
375378
['Balance', REQUIRED],
376379
['LowLimit', REQUIRED],
377380
['HighLimit', REQUIRED]])
378-
}
381+
};
379382

380383
exports.metadata = [
381384
[ 'TransactionIndex' , REQUIRED ],

src/js/ripple/serializedobject.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function SerializedObject(buf) {
4242

4343
SerializedObject.from_json = function(obj) {
4444
// Create a copy of the object so we don't modify it
45-
var obj = extend({}, obj);
45+
var obj = extend(true, {}, obj);
4646
var so = new SerializedObject();
4747
var typedef;
4848

src/js/ripple/serializedtypes.js

+151-32
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var Currency = amount.Currency;
2424
// Shortcuts
2525
var hex = sjcl.codec.hex;
2626
var bytes = sjcl.codec.bytes;
27+
var utf8 = sjcl.codec.utf8String;
2728

2829
var BigInteger = utils.jsbn.BigInteger;
2930

@@ -52,7 +53,7 @@ function isBigInteger(val) {
5253
return val instanceof BigInteger;
5354
};
5455

55-
function serialize_hex(so, hexData, noLength) {
56+
function serializeHex(so, hexData, noLength) {
5657
var byteData = bytes.fromBits(hex.toBits(hexData));
5758
if (!noLength) {
5859
SerializedType.serialize_varint(so, byteData.length);
@@ -63,10 +64,18 @@ function serialize_hex(so, hexData, noLength) {
6364
/**
6465
* parses bytes as hex
6566
*/
66-
function convert_bytes_to_hex (byte_array) {
67+
function convertByteArrayToHex (byte_array) {
6768
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)).toUpperCase();
6869
};
6970

71+
function convertStringToHex(string) {
72+
return hex.fromBits(utf8.toBits(string)).toUpperCase();
73+
}
74+
75+
function convertHexToString(hexString) {
76+
return utf8.fromBits(hex.toBits(hexString));
77+
}
78+
7079
SerializedType.serialize_varint = function (so, val) {
7180
if (val < 0) {
7281
throw new Error('Variable integers are unsigned.');
@@ -115,7 +124,7 @@ SerializedType.prototype.parse_varint = function (so) {
115124
*
116125
* The result is appended to the serialized object ('so').
117126
*/
118-
function append_byte_array(so, val, bytes) {
127+
function convertIntegerToByteArray(val, bytes) {
119128
if (!isNumber(val)) {
120129
throw new Error('Value is not a number', bytes);
121130
}
@@ -130,7 +139,7 @@ function append_byte_array(so, val, bytes) {
130139
newBytes.unshift(val >>> (i * 8) & 0xff);
131140
}
132141

133-
so.append(newBytes);
142+
return newBytes;
134143
};
135144

136145
// Convert a certain number of bytes from the serialized object ('so') into an integer.
@@ -152,7 +161,7 @@ function readAndSum(so, bytes) {
152161

153162
var STInt8 = exports.Int8 = new SerializedType({
154163
serialize: function (so, val) {
155-
append_byte_array(so, val, 1);
164+
so.append(convertIntegerToByteArray(val, 1));
156165
},
157166
parse: function (so) {
158167
return readAndSum(so, 1);
@@ -163,7 +172,7 @@ STInt8.id = 16;
163172

164173
var STInt16 = exports.Int16 = new SerializedType({
165174
serialize: function (so, val) {
166-
append_byte_array(so, val, 2);
175+
so.append(convertIntegerToByteArray(val, 2));
167176
},
168177
parse: function (so) {
169178
return readAndSum(so, 2);
@@ -174,7 +183,7 @@ STInt16.id = 1;
174183

175184
var STInt32 = exports.Int32 = new SerializedType({
176185
serialize: function (so, val) {
177-
append_byte_array(so, val, 4);
186+
so.append(convertIntegerToByteArray(val, 4));
178187
},
179188
parse: function (so) {
180189
return readAndSum(so, 4);
@@ -217,7 +226,7 @@ var STInt64 = exports.Int64 = new SerializedType({
217226
hex = '0' + hex;
218227
}
219228

220-
serialize_hex(so, hex, true); //noLength = true
229+
serializeHex(so, hex, true); //noLength = true
221230
},
222231
parse: function (so) {
223232
var bytes = so.read(8);
@@ -237,7 +246,7 @@ var STHash128 = exports.Hash128 = new SerializedType({
237246
if (!hash.is_valid()) {
238247
throw new Error('Invalid Hash128');
239248
}
240-
serialize_hex(so, hash.to_hex(), true); //noLength = true
249+
serializeHex(so, hash.to_hex(), true); //noLength = true
241250
},
242251
parse: function (so) {
243252
return UInt128.from_bytes(so.read(16));
@@ -252,7 +261,7 @@ var STHash256 = exports.Hash256 = new SerializedType({
252261
if (!hash.is_valid()) {
253262
throw new Error('Invalid Hash256');
254263
}
255-
serialize_hex(so, hash.to_hex(), true); //noLength = true
264+
serializeHex(so, hash.to_hex(), true); //noLength = true
256265
},
257266
parse: function (so) {
258267
return UInt256.from_bytes(so.read(32));
@@ -267,7 +276,7 @@ var STHash160 = exports.Hash160 = new SerializedType({
267276
if (!hash.is_valid()) {
268277
throw new Error('Invalid Hash160');
269278
}
270-
serialize_hex(so, hash.to_hex(), true); //noLength = true
279+
serializeHex(so, hash.to_hex(), true); //noLength = true
271280
},
272281
parse: function (so) {
273282
return UInt160.from_bytes(so.read(20));
@@ -294,7 +303,7 @@ var STCurrency = new SerializedType({
294303
// UInt160 value and consider it valid. But it doesn't, so for the
295304
// deserialization to be usable, we need to allow invalid results for now.
296305
//if (!currency.is_valid()) {
297-
// throw new Error('Invalid currency: '+convert_bytes_to_hex(bytes));
306+
// throw new Error('Invalid currency: '+convertByteArrayToHex(bytes));
298307
//}
299308
return currency;
300309
}
@@ -409,15 +418,16 @@ STAmount.id = 6;
409418

410419
var STVL = exports.VariableLength = exports.VL = new SerializedType({
411420
serialize: function (so, val) {
421+
412422
if (typeof val === 'string') {
413-
serialize_hex(so, val);
423+
serializeHex(so, val);
414424
} else {
415425
throw new Error('Unknown datatype.');
416426
}
417427
},
418428
parse: function (so) {
419429
var len = this.parse_varint(so);
420-
return convert_bytes_to_hex(so.read(len));
430+
return convertByteArrayToHex(so.read(len));
421431
}
422432
});
423433

@@ -429,7 +439,7 @@ var STAccount = exports.Account = new SerializedType({
429439
if (!account.is_valid()) {
430440
throw new Error('Invalid account!');
431441
}
432-
serialize_hex(so, account.to_hex());
442+
serializeHex(so, account.to_hex());
433443
},
434444
parse: function (so) {
435445
var len = this.parse_varint(so);
@@ -441,7 +451,6 @@ var STAccount = exports.Account = new SerializedType({
441451
var result = UInt160.from_bytes(so.read(len));
442452
result.set_version(Base.VER_ACCOUNT_ID);
443453

444-
//console.log('PARSED 160:', result.to_json());
445454
if (false && !result.is_valid()) {
446455
throw new Error('Invalid Account');
447456
}
@@ -593,6 +602,105 @@ var STVector256 = exports.Vector256 = new SerializedType({
593602

594603
STVector256.id = 19;
595604

605+
// Internal
606+
var STMemo = exports.STMemo = new SerializedType({
607+
serialize: function(so, val, no_marker) {
608+
609+
var keys = [];
610+
611+
Object.keys(val).forEach(function (key) {
612+
// Ignore lowercase field names - they're non-serializable fields by
613+
// convention.
614+
if (key[0] === key[0].toLowerCase()) {
615+
return;
616+
}
617+
618+
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
619+
throw new Error('JSON contains unknown field: "' + key + '"');
620+
}
621+
622+
keys.push(key);
623+
});
624+
625+
// Sort fields
626+
keys = sort_fields(keys);
627+
628+
// store that we're dealing with json
629+
var isJson = val.MemoFormat === 'json';
630+
631+
for (var i=0; i<keys.length; i++) {
632+
var key = keys[i];
633+
var value = val[key];
634+
switch (key) {
635+
636+
// MemoType and MemoFormat are always ASCII strings
637+
case 'MemoType':
638+
case 'MemoFormat':
639+
value = convertStringToHex(value);
640+
break;
641+
642+
// MemoData can be a JSON object, otherwise it's a string
643+
case 'MemoData':
644+
if (typeof value !== 'string') {
645+
if (isJson) {
646+
try {
647+
value = convertStringToHex(JSON.stringify(value));
648+
} catch (e) {
649+
throw new Error('MemoFormat json with invalid JSON in MemoData field');
650+
}
651+
} else {
652+
throw new Error('MemoData can only be a JSON object with a valid json MemoFormat');
653+
}
654+
} else if (isString(value)) {
655+
value = convertStringToHex(value);
656+
}
657+
break;
658+
}
659+
660+
serialize(so, key, value);
661+
}
662+
663+
if (!no_marker) {
664+
//Object ending marker
665+
STInt8.serialize(so, 0xe1);
666+
}
667+
668+
},
669+
parse: function(so) {
670+
var output = {};
671+
while (so.peek(1)[0] !== 0xe1) {
672+
var keyval = parse(so);
673+
output[keyval[0]] = keyval[1];
674+
}
675+
676+
if (output['MemoType'] !== void(0)) {
677+
output['parsed_memo_type'] = convertHexToString(output['MemoType']);
678+
}
679+
680+
if (output['MemoFormat'] !== void(0)) {
681+
output['parsed_memo_format'] = convertHexToString(output['MemoFormat']);
682+
}
683+
684+
if (output['MemoData'] !== void(0)) {
685+
686+
// see if we can parse JSON
687+
if (output['parsed_memo_format'] === 'json') {
688+
try {
689+
output['parsed_memo_data'] = JSON.parse(convertHexToString(output['MemoData']));
690+
} catch(e) {
691+
// fail, which is fine, we just won't add the memo_data field
692+
}
693+
} else if(output['parsed_memo_format'] === 'text') {
694+
output['parsed_memo_data'] = convertHexToString(output['MemoData']);
695+
}
696+
}
697+
698+
so.read(1);
699+
return output;
700+
}
701+
702+
});
703+
596704
exports.serialize = exports.serialize_whatever = serialize;
597705

598706
function serialize(so, field_name, value) {
@@ -622,9 +730,15 @@ function serialize(so, field_name, value) {
622730
STInt8.serialize(so, field_bits);
623731
}
624732

625-
// Get the serializer class (ST...) for a field based on the type bits.
626-
var serialized_object_type = exports[binformat.types[type_bits]];
627-
//do something with val[keys] and val[keys[i]];
733+
// Get the serializer class (ST...)
734+
var serialized_object_type;
735+
if (field_name === 'Memo' && typeof value === 'object') {
736+
// for Memo we override the default behavior with our STMemo serializer
737+
serialized_object_type = exports.STMemo;
738+
} else {
739+
// for a field based on the type bits.
740+
serialized_object_type = exports[binformat.types[type_bits]];
741+
}
628742

629743
try {
630744
serialized_object_type.serialize(so, value);
@@ -645,18 +759,21 @@ function parse(so) {
645759
type_bits = so.read(1)[0];
646760
}
647761

648-
// Get the parser class (ST...) for a field based on the type bits.
649-
var type = exports[binformat.types[type_bits]];
650-
651-
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
652762

653763
var field_bits = tag_byte & 0x0f;
654764
var field_name = (field_bits === 0)
655-
? field_name = binformat.fields[type_bits][so.read(1)[0]]
656-
: field_name = binformat.fields[type_bits][field_bits];
765+
? field_name = binformat.fields[type_bits][so.read(1)[0]]
766+
: field_name = binformat.fields[type_bits][field_bits];
657767

658768
assert(field_name, 'Unknown field - header byte is 0x' + tag_byte.toString(16));
659769

770+
// Get the parser class (ST...) for a field based on the type bits.
771+
var type = (field_name === 'Memo')
772+
? exports.STMemo
773+
: exports[binformat.types[type_bits]];
774+
775+
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
776+
660777
return [ field_name, type.parse(so) ]; //key, value
661778
};
662779

@@ -678,18 +795,20 @@ function sort_fields(keys) {
678795

679796
var STObject = exports.Object = new SerializedType({
680797
serialize: function (so, val, no_marker) {
681-
var keys = Object.keys(val);
798+
var keys = [];
682799

683-
// Ignore lowercase field names - they're non-serializable fields by
684-
// convention.
685-
keys = keys.filter(function (key) {
686-
return key[0] !== key[0].toLowerCase();
687-
});
800+
Object.keys(val).forEach(function (key) {
801+
// Ignore lowercase field names - they're non-serializable fields by
802+
// convention.
803+
if (key[0] === key[0].toLowerCase()) {
804+
return;
805+
}
688806

689-
keys.forEach(function (key) {
690807
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
691808
throw new Error('JSON contains unknown field: "' + key + '"');
692809
}
810+
811+
keys.push(key);
693812
});
694813

695814
// Sort fields

0 commit comments

Comments
 (0)