Skip to content

Commit

Permalink
Handle null/empty memo hash. (#425)
Browse files Browse the repository at this point in the history
  • Loading branch information
vinamogit authored May 10, 2022
1 parent 374903a commit 2bca9e1
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 80 deletions.
5 changes: 1 addition & 4 deletions src/main/java/org/stellar/sdk/Memo.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.stellar.sdk;

import com.google.common.io.BaseEncoding;

/**
* <p>The memo contains optional extra information. It is the responsibility of the client to interpret this value. Memos can be one of the following types:</p>
* <ul>
Expand Down Expand Up @@ -76,8 +74,7 @@ public static MemoReturnHash returnHash(byte[] bytes) {
* @param hexString
*/
public static MemoReturnHash returnHash(String hexString) {
// We change to lowercase because we want to decode both: upper cased and lower cased alphabets.
return new MemoReturnHash(BaseEncoding.base16().lowerCase().decode(hexString.toLowerCase()));
return new MemoReturnHash(hexString);
}

public static Memo fromXdr(org.stellar.sdk.xdr.Memo memo) {
Expand Down
34 changes: 9 additions & 25 deletions src/main/java/org/stellar/sdk/MemoHashAbstract.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package org.stellar.sdk;

import com.google.common.io.BaseEncoding;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Arrays;

import com.google.common.io.BaseEncoding;

abstract class MemoHashAbstract extends Memo {
protected byte[] bytes;

public MemoHashAbstract(byte[] bytes) {
if (bytes.length < 32) {
bytes = Util.paddedByteArray(bytes, 32);
} else if (bytes.length > 32) {
throw new MemoTooLongException("MEMO_HASH can contain 32 bytes at max.");
}

checkNotNull(bytes, "bytes cannot be null");
checkArgument(bytes.length == 32, "bytes must be 32-bytes long.");
this.bytes = bytes;
}

Expand All @@ -34,29 +33,14 @@ public byte[] getBytes() {
*
* <p>Example:</p>
* <code>
* MemoHash memo = new MemoHash("4142434445");
* memo.getHexValue(); // 4142434445000000000000000000000000000000000000000000000000000000
* memo.getTrimmedHexValue(); // 4142434445
* MemoHash memo = new MemoHash("e98869bba8bce08c10b78406202127f3888c25454cd37b02600862452751f526");
* memo.getHexValue(); // e98869bba8bce08c10b78406202127f3888c25454cd37b02600862452751f526
* </code>
*/
public String getHexValue() {
return BaseEncoding.base16().lowerCase().encode(this.bytes);
}

/**
* <p>Returns hex representation of bytes contained in this memo until null byte (0x00) is found.</p>
*
* <p>Example:</p>
* <code>
* MemoHash memo = new MemoHash("4142434445");
* memo.getHexValue(); // 4142434445000000000000000000000000000000000000000000000000000000
* memo.getTrimmedHexValue(); // 4142434445
* </code>
*/
public String getTrimmedHexValue() {
return this.getHexValue().split("00")[0];
}

@Override
abstract org.stellar.sdk.xdr.Memo toXdr();

Expand All @@ -75,6 +59,6 @@ public boolean equals(Object o) {

@Override
public String toString() {
return bytes == null ? "" : Util.paddedByteArrayToString(bytes);
return bytes == null ? "" : getHexValue();
}
}
13 changes: 6 additions & 7 deletions src/main/java/org/stellar/sdk/MemoText.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package org.stellar.sdk;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;

import org.stellar.sdk.xdr.MemoType;
import org.stellar.sdk.xdr.XdrString;

import static com.google.common.base.Preconditions.checkNotNull;

/**
* Represents MEMO_TEXT.
*/
Expand All @@ -20,11 +21,9 @@ public MemoText(byte[] text) {
}

public MemoText(XdrString text) {
this.text = checkNotNull(text, "text cannot be null");
int length = this.text.getBytes().length;
if (length > 28) {
throw new MemoTooLongException("text must be <= 28 bytes. length=" + String.valueOf(length));
}
checkNotNull(text, "text cannot be null");
checkArgument(text.getBytes().length <= 28, "text cannot be more than 28-bytes long.");
this.text = text;
}

public String getText() {
Expand Down
15 changes: 0 additions & 15 deletions src/main/java/org/stellar/sdk/MemoTooLongException.java

This file was deleted.

6 changes: 5 additions & 1 deletion src/main/java/org/stellar/sdk/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ static byte[] paddedByteArray(String string, int length) {
* @param bytes
*/
static String paddedByteArrayToString(byte[] bytes) {
return new String(bytes).split("\0")[0];
String[] strArray = new String(bytes).split("\0");
if (strArray.length > 0) {
return strArray[0];
}
return "";
}

/**
Expand Down
67 changes: 44 additions & 23 deletions src/test/java/org/stellar/sdk/MemoTest.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package org.stellar.sdk;

import com.google.common.io.BaseEncoding;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.Arrays;

import org.junit.Test;
import org.stellar.sdk.xdr.MemoType;
import org.stellar.sdk.MemoId;
import org.stellar.sdk.responses.TransactionDeserializer;
import org.stellar.sdk.responses.TransactionResponse;
import org.stellar.sdk.xdr.MemoType;

import java.util.Arrays;

import static org.junit.Assert.*;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;

public class MemoTest {
@Test
Expand Down Expand Up @@ -43,7 +47,7 @@ public void testMemoTextTooLong() {
Memo.text("12345678901234567890123456789");
fail();
} catch (RuntimeException exception) {
assertTrue(exception.getMessage().contains("text must be <= 28 bytes."));
assertTrue(exception.getMessage().contains("text cannot be more than 28-bytes long."));
}
}

Expand All @@ -53,7 +57,7 @@ public void testMemoTextTooLongUtf8() {
Memo.text("价值交易的开源协议!!");
fail();
} catch (RuntimeException exception) {
assertTrue(exception.getMessage().contains("text must be <= 28 bytes."));
assertTrue(exception.getMessage().contains("text cannot be more than 28-bytes long."));
}
}

Expand All @@ -74,35 +78,43 @@ public void testParseMemoId() {
MemoId memoId = (MemoId)transactionResponse.getMemo();
assertEquals(longId, Long.toUnsignedString(memoId.getId()));
}


@Test
public void testMemoNullHexHashSuccess() {
MemoHash memo = Memo.hash("0000000000000000000000000000000000000000000000000000000000000000");
assertEquals(MemoType.MEMO_HASH, memo.toXdr().getDiscriminant());

assertEquals("", Util.paddedByteArrayToString(memo.getBytes()));
assertEquals("0000000000000000000000000000000000000000000000000000000000000000", memo.getHexValue());
assertEquals("0000000000000000000000000000000000000000000000000000000000000000", memo.toString());
}

@Test
public void testMemoHashSuccess() {
MemoHash memo = Memo.hash("4142434445464748494a4b4c");
MemoHash memo = Memo.hash(Strings.padEnd("4142434445464748494a4b4c", 64, '0'));
assertEquals(MemoType.MEMO_HASH, memo.toXdr().getDiscriminant());
String test = "ABCDEFGHIJKL";
assertEquals(test, Util.paddedByteArrayToString(memo.getBytes()));
assertEquals("4142434445464748494a4b4c", memo.getTrimmedHexValue());
assertEquals("ABCDEFGHIJKL", memo.toString());
assertEquals("ABCDEFGHIJKL", Util.paddedByteArrayToString(memo.getBytes()));
}

@Test
public void testMemoHashSuccessUppercase() {
MemoHash memo = Memo.hash("4142434445464748494a4b4c".toUpperCase());

MemoHash memo = Memo.hash(Strings.padEnd("4142434445464748494a4b4c".toUpperCase(), 64, '0'));
assertEquals(MemoType.MEMO_HASH, memo.toXdr().getDiscriminant());
String test = "ABCDEFGHIJKL";
assertEquals(test, Util.paddedByteArrayToString(memo.getBytes()));
assertEquals("4142434445464748494a4b4c", memo.getTrimmedHexValue());
}

@Test
public void testMemoHashBytesSuccess() {
byte[] bytes = new byte[10];
Arrays.fill(bytes, (byte) 'A');
MemoHash memo = Memo.hash(bytes);
MemoHash memo = Memo.hash(Util.paddedByteArray(bytes, 32));
assertEquals(MemoType.MEMO_HASH, memo.toXdr().getDiscriminant());
assertEquals("AAAAAAAAAA", Util.paddedByteArrayToString(memo.getBytes()));
assertEquals("4141414141414141414100000000000000000000000000000000000000000000", memo.getHexValue());
assertEquals("41414141414141414141", memo.getTrimmedHexValue());
}

@Test
Expand All @@ -112,8 +124,18 @@ public void testMemoHashTooLong() {
try {
Memo.hash(longer);
fail();
} catch (MemoTooLongException exception) {
assertTrue(exception.getMessage().contains("MEMO_HASH can contain 32 bytes at max."));
} catch (Exception exception) {
assertTrue(exception.getMessage().contains("bytes must be 32-bytes long."));
}
}

@Test
public void testMemoHashTooShort() {
try {
Memo.hash("");
fail();
} catch (Exception exception) {
assertTrue(exception.getMessage().contains("bytes must be 32-bytes long."));
}
}

Expand All @@ -123,17 +145,16 @@ public void testMemoHashInvalidHex() {
Memo.hash("test");
fail();
} catch (Exception e) {

// Success
}
}

@Test
public void testMemoReturnHashSuccess() {
MemoReturnHash memo = Memo.returnHash("4142434445464748494a4b4c");
MemoReturnHash memo = Memo.returnHash("4142434445464748494a4b4c0000000000000000000000000000000000000000");
org.stellar.sdk.xdr.Memo memoXdr = memo.toXdr();
assertEquals(MemoType.MEMO_RETURN, memoXdr.getDiscriminant());
assertNull(memoXdr.getHash());
assertEquals("4142434445464748494a4b4c0000000000000000000000000000000000000000", BaseEncoding.base16().lowerCase().encode(memoXdr.getRetHash().getHash()));
assertEquals("4142434445464748494a4b4c", memo.getTrimmedHexValue());
}
}
10 changes: 5 additions & 5 deletions src/test/java/org/stellar/sdk/TransactionBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public void testBuilderTimeBounds() throws FormatException, IOException {
Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET)
.addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build())
.addTimeBounds(new TimeBounds(42, 1337))
.addMemo(Memo.hash("abcdef"))
.addMemo(Memo.hash(Util.hash("abcdef".getBytes())))
.setBaseFee(Transaction.MIN_BASE_FEE)
.build();

Expand Down Expand Up @@ -184,7 +184,7 @@ public void testBuilderTimebounds() throws IOException {
Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET)
.addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build())
.addTimeBounds(new TimeBounds(42, 1337))
.addMemo(Memo.hash("abcdef"))
.addMemo(Memo.hash(Util.hash("abcdef".getBytes())))
.setBaseFee(Transaction.MIN_BASE_FEE)
.build();

Expand All @@ -208,7 +208,7 @@ public void testBuilderRequiresTimeoutOrTimeBounds() throws IOException {
try {
new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET)
.addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build())
.addMemo(Memo.hash("abcdef"))
.addMemo(Memo.hash(Util.hash("abcdef".getBytes())))
.setBaseFee(Transaction.MIN_BASE_FEE)
.build();
fail();
Expand All @@ -222,7 +222,7 @@ public void testBuilderTimeoutNegative() throws IOException {
try {
new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET)
.addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build())
.addMemo(Memo.hash("abcdef"))
.addMemo(Memo.hash(Util.hash("abcdef".getBytes())))
.setTimeout(-1)
.build();
fail();
Expand Down Expand Up @@ -623,7 +623,7 @@ public void testBuilderInfinteTimeoutAndMaxTimeNotSet() throws FormatException,
.addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build())
.addTimeBounds(new TimeBounds(42, TransactionPreconditions.TIMEOUT_INFINITE))
.setTimeout(TransactionPreconditions.TIMEOUT_INFINITE)
.addMemo(Memo.hash("abcdef"))
.addMemo(Memo.hash(Util.hash("abcdef".getBytes())))
.setBaseFee(100)
.build();

Expand Down
Loading

0 comments on commit 2bca9e1

Please sign in to comment.