Skip to content

Commit

Permalink
[FIX] unsymmetric memo serializing
Browse files Browse the repository at this point in the history
treat memos as unknown binary, with optionally parsing unsynthesized hint fields
  • Loading branch information
geertweening committed Feb 5, 2015
1 parent 8dc40ee commit 1ed36fa
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 175 deletions.
72 changes: 26 additions & 46 deletions src/js/ripple/serializedtypes.js
Expand Up @@ -57,13 +57,9 @@ function convertByteArrayToHex (byte_array) {
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)).toUpperCase();
}

function convertStringToHex(string) {
var utf8String = sjcl.codec.utf8String.toBits(string);
return sjcl.codec.hex.fromBits(utf8String).toUpperCase();
}

function convertHexToString(hexString) {
return sjcl.codec.utf8String.fromBits(sjcl.codec.hex.toBits(hexString));
var bits = sjcl.codec.hex.toBits(hexString);
return sjcl.codec.utf8String.fromBits(bits);
}

SerializedType.serialize_varint = function (so, val) {
Expand Down Expand Up @@ -619,38 +615,9 @@ exports.STMemo = new SerializedType({
// Sort fields
keys = sort_fields(keys);

// store that we're dealing with json
var isJson = val.MemoFormat === 'json';

for (var i=0; i<keys.length; i++) {
var key = keys[i];
var value = val[key];
switch (key) {

// MemoType and MemoFormat are always ASCII strings
case 'MemoType':
case 'MemoFormat':
value = convertStringToHex(value);
break;

// MemoData can be a JSON object, otherwise it's a string
case 'MemoData':
if (typeof value !== 'string') {
if (isJson) {
try {
value = convertStringToHex(JSON.stringify(value));
} catch (e) {
throw new Error('MemoFormat json with invalid JSON in MemoData field');
}
} else {
throw new Error('MemoData can only be a JSON object with a valid json MemoFormat');
}
} else if (isString(value)) {
value = convertStringToHex(value);
}
break;
}

serialize(so, key, value);
}

Expand All @@ -668,28 +635,41 @@ exports.STMemo = new SerializedType({
}

if (output.MemoType !== void(0)) {
var parsedType = convertHexToString(output.MemoType);
try {
var parsedType = convertHexToString(output.MemoType);

if (parsedType !== 'unformatted_memo') {
output.parsed_memo_type = convertHexToString(output.MemoType);
if (parsedType !== 'unformatted_memo') {
output.parsed_memo_type = parsedType;
}
} catch (e) {
// 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 !== void(0)) {
output.parsed_memo_format = convertHexToString(output.MemoFormat);
try {
output.parsed_memo_format = convertHexToString(output.MemoFormat);
} catch (e) {
// 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 !== void(0)) {

// see if we can parse JSON
if (output.parsed_memo_format === 'json') {
try {
try {
if (output.parsed_memo_format === 'json') {
// see if we can parse JSON
output.parsed_memo_data = JSON.parse(convertHexToString(output.MemoData));
} catch(e) {
// fail, which is fine, we just won't add the memo_data field

} else if(output.parsed_memo_format === 'text') {
// otherwise see if we can parse text
output.parsed_memo_data = convertHexToString(output.MemoData);
}
} else if(output.parsed_memo_format === 'text') {
output.parsed_memo_data = convertHexToString(output.MemoData);
} catch(e) {
// 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
}
}

Expand Down
26 changes: 21 additions & 5 deletions src/js/ripple/transaction.js
Expand Up @@ -835,24 +835,40 @@ Transaction.prototype.addMemo = function(memoType, memoFormat, memoData) {
throw new Error('MemoFormat must be valid ASCII');
}

function convertStringToHex(string) {
var utf8String = sjcl.codec.utf8String.toBits(string);
return sjcl.codec.hex.fromBits(utf8String).toUpperCase();
}

var memo = {};

if (memoType) {
if (Transaction.MEMO_TYPES[memoType]) {
//XXX Maybe in the future we want a schema validator for
//memo types
memo.MemoType = Transaction.MEMO_TYPES[memoType];
} else {
memo.MemoType = memoType;
memoType = Transaction.MEMO_TYPES[memoType];
}
memo.MemoType = convertStringToHex(memoType);
}

if (memoFormat) {
memo.MemoFormat = memoFormat;
memo.MemoFormat = convertStringToHex(memoFormat);
}

if (memoData) {
memo.MemoData = memoData;
if (typeof memoData !== 'string') {
if (memoFormat === 'json') {
try {
memoData = JSON.stringify(memoData);
} catch (e) {
throw new Error('MemoFormat json with invalid JSON in MemoData field');
}
} else {
throw new Error('MemoData can only be a JSON object with a valid json MemoFormat');
}
}

memo.MemoData = convertStringToHex(memoData);
}

this.tx_json.Memos = (this.tx_json.Memos || []).concat({ Memo: memo });
Expand Down
115 changes: 20 additions & 95 deletions test/serializedobject-test.js
Expand Up @@ -130,10 +130,10 @@ describe('Serialized object', function() {
it('should serialize and parse - full memo, all strings text/plain ', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "text",
"MemoData": "some data"
Memo: {
MemoType: '74657374',
MemoFormat: '74657874',
MemoData: '736F6D652064617461'
}
}
];
Expand All @@ -152,10 +152,11 @@ describe('Serialized object', function() {
it('should serialize and parse - full memo, all strings, invalid MemoFormat', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "application/json",
"MemoData": "some data"
"Memo":
{
MemoType: '74657374',
MemoFormat: '6170706C69636174696F6E2F6A736F6E',
MemoData: '736F6D652064617461'
}
}
];
Expand All @@ -171,36 +172,14 @@ describe('Serialized object', function() {
assert.strictEqual(input_json.Memos[0].Memo.parsed_memo_data, void(0));
});

it('should throw an error - full memo, json data, invalid MemoFormat', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "text",
"MemoData": {
"string" : "some_string",
"boolean" : true
}
}
}
];

assert.throws(function() {
SerializedObject.from_json(input_json);
}, /^Error: MemoData can only be a JSON object with a valid json MemoFormat \(Memo\) \(Memos\)/);
});

it('should serialize and parse - full memo, json data, valid MemoFormat, ignored field', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "json",
"ignored" : "ignored",
"MemoData": {
"string" : "some_string",
"boolean" : true
}
Memo: {
MemoType: '74657374',
MemoFormat: '6A736F6E',
ignored : 'ignored',
MemoData: '7B22737472696E67223A22736F6D655F737472696E67222C22626F6F6C65616E223A747275657D'
}
}
];
Expand All @@ -225,74 +204,20 @@ describe('Serialized object', function() {
assert.deepEqual(so, input_json);
});

it('should serialize and parse - full memo, json data, valid MemoFormat', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "json",
"MemoData": {
"string" : "some_string",
"boolean" : true
}
}
}
];

var so = SerializedObject.from_json(input_json).to_json();
input_json.Memos[0].Memo.parsed_memo_type = 'test';
input_json.Memos[0].Memo.parsed_memo_format = 'json';
input_json.Memos[0].Memo.parsed_memo_data = {
"string" : "some_string",
"boolean" : true
};
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('json');
input_json.Memos[0].Memo.MemoData = convertStringToHex(JSON.stringify(
{
"string" : "some_string",
"boolean" : true
}
));

assert.deepEqual(so, input_json);
});

it('should serialize and parse - full memo, json data, valid MemoFormat, integer', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "json",
"MemoData": 3
}
}
];

var so = SerializedObject.from_json(input_json).to_json();
input_json.Memos[0].Memo.parsed_memo_type = 'test';
input_json.Memos[0].Memo.parsed_memo_format = 'json';
input_json.Memos[0].Memo.parsed_memo_data = 3;
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('json');
input_json.Memos[0].Memo.MemoData = convertStringToHex(JSON.parse(3));
assert.deepEqual(so, input_json);
});

it('should throw an error - invalid Memo field', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoParty": "json",
"MemoData": 3
Memo: {
MemoType: '74657374',
MemoField: '6A736F6E',
MemoData: '7B22737472696E67223A22736F6D655F737472696E67222C22626F6F6C65616E223A747275657D'
}
}
];

assert.throws(function() {
SerializedObject.from_json(input_json);
}, /^Error: JSON contains unknown field: "MemoParty" \(Memo\) \(Memos\)/);
}, /^Error: JSON contains unknown field: "MemoField" \(Memo\) \(Memos\)/);
});


Expand All @@ -306,7 +231,7 @@ describe('Serialized object', function() {
Memos: [
{
Memo: {
MemoType: 'image'
MemoType: '696D616765'
}
}
],
Expand Down

0 comments on commit 1ed36fa

Please sign in to comment.