Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use bytes to represent memo text #259

Merged
merged 13 commits into from
Dec 13, 2019
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

As this project is pre 1.0, breaking changes may happen for minor version bumps. A breaking change will get clearly notified in this log.

## 0.12.0

* Represent memo text contents as bytes because a memo text may not be valid UTF-8 string (https://github.com/stellar/java-stellar-sdk/issues/257).
* Validate name length when constructing org.stellar.sdk.ManageDataOperation instances.
* Validate home domain length when constructing org.stellar.sdk.SetOptionsOperation instances.

## 0.11.0

* Fix bug in `org.stellar.sdk.requests.OperationsRequestBuilder.operation(long operationId)`. The method submitted an HTTP request to Horizon with the following path, /operation/<id> , but the correct path is /operations/<id>
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ apply plugin: 'com.github.ben-manes.versions' // gradle dependencyUpdates -Drevi
apply plugin: 'project-report' // gradle htmlDependencyReport

sourceCompatibility = 1.6
version = '0.11.0'
version = '0.12.0'
group = 'stellar'

jar {
Expand Down
13 changes: 7 additions & 6 deletions src/main/java/org/stellar/sdk/ManageDataOperation.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package org.stellar.sdk;

import com.google.common.base.Objects;
import org.stellar.sdk.xdr.DataValue;
import org.stellar.sdk.xdr.ManageDataOp;
import org.stellar.sdk.xdr.OperationType;
import org.stellar.sdk.xdr.String64;
import org.stellar.sdk.xdr.*;

import java.util.Arrays;

Expand All @@ -21,6 +18,10 @@ public class ManageDataOperation extends Operation {
private ManageDataOperation(String name, byte[] value) {
this.name = checkNotNull(name, "name cannot be null");
this.value = value;

if (new XdrString(this.name).getBytes().length > 64) {
throw new IllegalArgumentException("name cannot exceed 64 bytes");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems to be unrelated to the rest of this change. The rest of this change is specifically about memo text, but this change is to do with manage data operations. It seems out of scope and like it belongs in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I separated this change in a new PR it would depend on this branch because it requires XdrString. Lets keep both changes in the same PR for now. It shouldn't be too burdensome to review the extra changes

}

/**
Expand All @@ -41,7 +42,7 @@ public byte[] getValue() {
org.stellar.sdk.xdr.Operation.OperationBody toOperationBody() {
ManageDataOp op = new ManageDataOp();
String64 name = new String64();
name.setString64(this.name);
name.setString64(new XdrString(this.name));
op.setDataName(name);

if (value != null) {
Expand All @@ -68,7 +69,7 @@ public static class Builder {
* @param op {@link ManageDataOp}
*/
Builder(ManageDataOp op) {
name = op.getDataName().getString64();
name = op.getDataName().getString64().toString();
if (op.getDataValue() != null) {
value = op.getDataValue().getDataValue();
} else {
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/org/stellar/sdk/Memo.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ public static MemoText text(String text) {
return new MemoText(text);
}

/**
* Creates new {@link MemoText} instance.
* @param text
*/
public static MemoText text(byte[] text) {
return new MemoText(text);
}


/**
* Creates new {@link MemoId} instance.
* @param id
Expand Down Expand Up @@ -78,7 +87,7 @@ public static Memo fromXdr(org.stellar.sdk.xdr.Memo memo) {
case MEMO_ID:
return id(memo.getId().getUint64().longValue());
case MEMO_TEXT:
return text(memo.getText());
return text(memo.getText().getBytes());
case MEMO_HASH:
return hash(memo.getHash().getHash());
case MEMO_RETURN:
Expand Down
31 changes: 20 additions & 11 deletions src/main/java/org/stellar/sdk/MemoText.java
Original file line number Diff line number Diff line change
@@ -1,54 +1,63 @@
package org.stellar.sdk;

import com.google.common.base.Objects;
import org.stellar.sdk.xdr.MemoType;

import java.nio.charset.Charset;
import org.stellar.sdk.xdr.XdrString;

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

/**
* Represents MEMO_TEXT.
*/
public class MemoText extends Memo {
private String text;
private XdrString text;

public MemoText(String text) {
this.text = checkNotNull(text, "text cannot be null");
this(new XdrString(checkNotNull(text, "text cannot be null")));
}

public MemoText(byte[] text) {
this(new XdrString(checkNotNull(text, "text cannot be null")));
}

int length = text.getBytes((Charset.forName("UTF-8"))).length;
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));
}
}

public String getText() {
return text;
return this.text.toString();
}

public byte[] getBytes() {
return this.text.getBytes();
}

@Override
org.stellar.sdk.xdr.Memo toXdr() {
org.stellar.sdk.xdr.Memo memo = new org.stellar.sdk.xdr.Memo();
memo.setDiscriminant(MemoType.MEMO_TEXT);
memo.setText(text);
memo.setText(this.text);
return memo;
}

@Override
public int hashCode() {
return Objects.hashCode(this.text);
return this.text.hashCode();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MemoText memoText = (MemoText) o;
return Objects.equal(this.text, memoText.text);
return this.text.equals(memoText.text);
}

@Override
public String toString() {
return text == null ? "" : text;
return text == null ? "" : this.getText();
}
}
9 changes: 7 additions & 2 deletions src/main/java/org/stellar/sdk/SetOptionsOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ private SetOptionsOperation(String inflationDestination, Integer clearFlags, Int
this.homeDomain = homeDomain;
this.signer = signer;
this.signerWeight = signerWeight;

if (this.homeDomain != null && new XdrString(this.homeDomain).getBytes().length > 32) {
throw new IllegalArgumentException("home domain cannot exceed 32 bytes");
}

}

/**
Expand Down Expand Up @@ -148,7 +153,7 @@ org.stellar.sdk.xdr.Operation.OperationBody toOperationBody() {
}
if (homeDomain != null) {
String32 homeDomain = new String32();
homeDomain.setString32(this.homeDomain);
homeDomain.setString32(new XdrString(this.homeDomain));
op.setHomeDomain(homeDomain);
}
if (signer != null) {
Expand Down Expand Up @@ -206,7 +211,7 @@ public static class Builder {
highThreshold = op.getHighThreshold().getUint32().intValue();
}
if (op.getHomeDomain() != null) {
homeDomain = op.getHomeDomain().getString32();
homeDomain = op.getHomeDomain().getString32().toString();
}
if (op.getSigner() != null) {
signer = op.getSigner().getKey();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
import com.google.gson.JsonParseException;

import org.stellar.sdk.Memo;
import org.stellar.sdk.xdr.TransactionEnvelope;
import org.stellar.sdk.xdr.XdrDataInputStream;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Type;

public class TransactionDeserializer implements JsonDeserializer<TransactionResponse> {
Expand All @@ -31,12 +35,19 @@ public TransactionResponse deserialize(JsonElement json, Type typeOfT, JsonDeser
// representation of a transaction. That's why we need to handle a special case
// here.
if (memoType.equals("text")) {
JsonElement memoField = json.getAsJsonObject().get("memo");
if (memoField != null) {
memo = Memo.text(memoField.getAsString());
} else {
memo = Memo.text("");
// we obtain the memo text from the xdr because the bytes may not be valid utf8
String envelopeXdr = json.getAsJsonObject().get("envelope_xdr").getAsString();
BaseEncoding base64Encoding = BaseEncoding.base64();
byte[] bytes = base64Encoding.decode(envelopeXdr);
TransactionEnvelope transactionEnvelope = null;
try {
transactionEnvelope = TransactionEnvelope.decode(new XdrDataInputStream(new ByteArrayInputStream(bytes)));
} catch (IOException e) {
// JsonDeserializer<TransactionResponse> cannot throw IOExceptions
// so we must throw it as a runtime exception
throw new RuntimeException(e);
}
memo = Memo.text(transactionEnvelope.getTx().getMemo().getText().getBytes());
} else {
String memoValue = json.getAsJsonObject().get("memo").getAsString();
BaseEncoding base64Encoding = BaseEncoding.base64();
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/org/stellar/sdk/xdr/Error.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,24 @@ public ErrorCode getCode() {
public void setCode(ErrorCode value) {
this.code = value;
}
private String msg;
public String getMsg() {
private XdrString msg;
public XdrString getMsg() {
return this.msg;
}
public void setMsg(String value) {
public void setMsg(XdrString value) {
this.msg = value;
}
public static void encode(XdrDataOutputStream stream, Error encodedError) throws IOException{
ErrorCode.encode(stream, encodedError.code);
stream.writeString(encodedError.msg);
encodedError.msg.encode(stream);
}
public void encode(XdrDataOutputStream stream) throws IOException {
encode(stream, this);
}
public static Error decode(XdrDataInputStream stream) throws IOException {
Error decodedError = new Error();
decodedError.code = ErrorCode.decode(stream);
decodedError.msg = stream.readString();
decodedError.msg = XdrString.decode(stream, 100);
return decodedError;
}
@Override
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/org/stellar/sdk/xdr/Hello.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ public Hash getNetworkID() {
public void setNetworkID(Hash value) {
this.networkID = value;
}
private String versionStr;
public String getVersionStr() {
private XdrString versionStr;
public XdrString getVersionStr() {
return this.versionStr;
}
public void setVersionStr(String value) {
public void setVersionStr(XdrString value) {
this.versionStr = value;
}
private Integer listeningPort;
Expand Down Expand Up @@ -94,7 +94,7 @@ public static void encode(XdrDataOutputStream stream, Hello encodedHello) throws
Uint32.encode(stream, encodedHello.overlayVersion);
Uint32.encode(stream, encodedHello.overlayMinVersion);
Hash.encode(stream, encodedHello.networkID);
stream.writeString(encodedHello.versionStr);
encodedHello.versionStr.encode(stream);
stream.writeInt(encodedHello.listeningPort);
NodeID.encode(stream, encodedHello.peerID);
AuthCert.encode(stream, encodedHello.cert);
Expand All @@ -109,7 +109,7 @@ public static Hello decode(XdrDataInputStream stream) throws IOException {
decodedHello.overlayVersion = Uint32.decode(stream);
decodedHello.overlayMinVersion = Uint32.decode(stream);
decodedHello.networkID = Hash.decode(stream);
decodedHello.versionStr = stream.readString();
decodedHello.versionStr = XdrString.decode(stream, 100);
decodedHello.listeningPort = stream.readInt();
decodedHello.peerID = NodeID.decode(stream);
decodedHello.cert = AuthCert.decode(stream);
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/org/stellar/sdk/xdr/Memo.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ public MemoType getDiscriminant() {
public void setDiscriminant(MemoType value) {
this.type = value;
}
private String text;
public String getText() {
private XdrString text;
public XdrString getText() {
return this.text;
}
public void setText(String value) {
public void setText(XdrString value) {
this.text = value;
}
private Uint64 id;
Expand Down Expand Up @@ -70,7 +70,7 @@ public static void encode(XdrDataOutputStream stream, Memo encodedMemo) throws I
case MEMO_NONE:
break;
case MEMO_TEXT:
stream.writeString(encodedMemo.text);
encodedMemo.text.encode(stream);
break;
case MEMO_ID:
Uint64.encode(stream, encodedMemo.id);
Expand All @@ -94,7 +94,7 @@ public static Memo decode(XdrDataInputStream stream) throws IOException {
case MEMO_NONE:
break;
case MEMO_TEXT:
decodedMemo.text = stream.readString();
decodedMemo.text = XdrString.decode(stream, 28);
break;
case MEMO_ID:
decodedMemo.id = Uint64.decode(stream);
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/org/stellar/sdk/xdr/String32.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,22 @@

// ===========================================================================
public class String32 implements XdrElement {
private String string32;
public String getString32() {
private XdrString string32;
public XdrString getString32() {
return this.string32;
}
public void setString32(String value) {
public void setString32(XdrString value) {
this.string32 = value;
}
public static void encode(XdrDataOutputStream stream, String32 encodedString32) throws IOException {
stream.writeString(encodedString32.string32);
encodedString32.string32.encode(stream);
}
public void encode(XdrDataOutputStream stream) throws IOException {
encode(stream, this);
}
public static String32 decode(XdrDataInputStream stream) throws IOException {
String32 decodedString32 = new String32();
decodedString32.string32 = stream.readString();
decodedString32.string32 = XdrString.decode(stream, 32);
return decodedString32;
}
@Override
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/org/stellar/sdk/xdr/String64.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,22 @@

// ===========================================================================
public class String64 implements XdrElement {
private String string64;
public String getString64() {
private XdrString string64;
public XdrString getString64() {
return this.string64;
}
public void setString64(String value) {
public void setString64(XdrString value) {
this.string64 = value;
}
public static void encode(XdrDataOutputStream stream, String64 encodedString64) throws IOException {
stream.writeString(encodedString64.string64);
encodedString64.string64.encode(stream);
}
public void encode(XdrDataOutputStream stream) throws IOException {
encode(stream, this);
}
public static String64 decode(XdrDataInputStream stream) throws IOException {
String64 decodedString64 = new String64();
decodedString64.string64 = stream.readString();
decodedString64.string64 = XdrString.decode(stream, 64);
return decodedString64;
}
@Override
Expand Down
7 changes: 0 additions & 7 deletions src/main/java/org/stellar/sdk/xdr/XdrDataInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@ public XdrDataInputStream(InputStream in) {
mIn = (XdrInputStream) super.in;
}

public String readString() throws IOException {
int l = readInt();
byte[] bytes = new byte[l];
read(bytes);
return new String(bytes, Charset.forName("UTF-8"));
}

public int[] readIntArray() throws IOException {
int l = readInt();
return readIntArray(l);
Expand Down
Loading