Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions xdr-generator/generator/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ def render_enum_json(enum, out)

def render_struct(struct, out)
struct.members.each do |m|
render_field_javadoc(out, m.name)
out.puts "private #{decl_string(m.declaration)} #{m.name};"
end

Expand Down Expand Up @@ -416,6 +417,7 @@ def render_struct_json(struct, out)
# ============================================================================

def render_typedef(typedef, out)
render_field_javadoc(out, typedef.name)
out.puts "private #{decl_string typedef.declaration} #{typedef.name};"
out.puts "public void encode(XdrDataOutputStream stream) throws IOException {"
out.indent do
Expand Down Expand Up @@ -486,9 +488,11 @@ def render_typedef_json(typedef, out)
# ============================================================================

def render_union(union, out)
render_field_javadoc(out, "discriminant")
out.puts "private #{type_string union.discriminant.type} discriminant;"
union.arms.each do |arm|
next if arm.void?
render_field_javadoc(out, arm.name)
out.puts "private #{decl_string(arm.declaration)} #{arm.name};"
end
out.break
Expand Down Expand Up @@ -809,6 +813,15 @@ def render_source_comment(out, defn)
out.puts " */"
end

def render_field_javadoc(out, field_name)
out.puts "/**"
out.puts " * Value of the {@code #{field_name}} field."
out.puts " *"
out.puts " * @param #{field_name} the {@code #{field_name}} field value"
out.puts " * @return the {@code #{field_name}} field value"
out.puts " */"
end

def render_base64(return_type, out)
out.puts <<-EOS.strip_heredoc
public static #{return_type} fromXdrBase64(String xdr) throws IOException {
Expand Down
11 changes: 10 additions & 1 deletion xdr-generator/generator/templates/XdrDataInputStream.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import java.io.InputStream;
import java.nio.charset.Charset;
import lombok.Setter;

/** XDR-aware input stream with padding handling and decode safety checks. */
public class XdrDataInputStream extends DataInputStream {

/** Default maximum decoding depth to prevent stack overflow from deeply nested structures. */
Expand All @@ -19,12 +20,14 @@ public class XdrDataInputStream extends DataInputStream {
* Maximum input length, -1 if unknown.
* This is used to validate that the declared size of variable-length
* arrays/opaques doesn't exceed the remaining input length, preventing DoS attacks.
*
* @param maxInputLen the maximum input length, or -1 if unknown
*/
@Setter
private int maxInputLen = -1;

/**
* Creates a XdrDataInputStream that uses the specified
* Creates an XdrDataInputStream that uses the specified
* underlying InputStream.
*
* @param in the specified input stream
Expand Down Expand Up @@ -69,6 +72,8 @@ public class XdrDataInputStream extends DataInputStream {
* @deprecated This method does not validate the array length and may cause
* OutOfMemoryError or NegativeArraySizeException with untrusted input.
* Use generated XDR type decoders instead which include proper validation.
* @return the decoded integer array
* @throws IOException if an I/O error occurs while reading the array
*/
@Deprecated
public int[] readIntArray() throws IOException {
Expand All @@ -88,6 +93,8 @@ public class XdrDataInputStream extends DataInputStream {
* @deprecated This method does not validate the array length and may cause
* OutOfMemoryError or NegativeArraySizeException with untrusted input.
* Use generated XDR type decoders instead which include proper validation.
* @return the decoded float array
* @throws IOException if an I/O error occurs while reading the array
*/
@Deprecated
public float[] readFloatArray() throws IOException {
Expand All @@ -107,6 +114,8 @@ public class XdrDataInputStream extends DataInputStream {
* @deprecated This method does not validate the array length and may cause
* OutOfMemoryError or NegativeArraySizeException with untrusted input.
* Use generated XDR type decoders instead which include proper validation.
* @return the decoded double array
* @throws IOException if an I/O error occurs while reading the array
*/
@Deprecated
public double[] readDoubleArray() throws IOException {
Expand Down
24 changes: 24 additions & 0 deletions xdr-generator/generator/templates/XdrDataOutputStream.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,27 @@ import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;

/** XDR-aware output stream that writes variable-length values with the required padding. */
public class XdrDataOutputStream extends DataOutputStream {

private final XdrOutputStream mOut;

/**
* Creates an XdrDataOutputStream that uses the specified underlying OutputStream.
*
* @param out the specified output stream
*/
public XdrDataOutputStream(OutputStream out) {
super(new XdrOutputStream(out));
mOut = (XdrOutputStream) super.out;
}

/**
* Writes an XDR variable-length array of integers.
*
* @param a the array to write
* @throws IOException if an I/O error occurs while writing the array
*/
public void writeIntArray(int[] a) throws IOException {
writeInt(a.length);
writeIntArray(a, a.length);
Expand All @@ -25,6 +37,12 @@ public class XdrDataOutputStream extends DataOutputStream {
}
}

/**
* Writes an XDR variable-length array of floats.
*
* @param a the array to write
* @throws IOException if an I/O error occurs while writing the array
*/
public void writeFloatArray(float[] a) throws IOException {
writeInt(a.length);
writeFloatArray(a, a.length);
Expand All @@ -36,6 +54,12 @@ public class XdrDataOutputStream extends DataOutputStream {
}
}

/**
* Writes an XDR variable-length array of doubles.
*
* @param a the array to write
* @throws IOException if an I/O error occurs while writing the array
*/
public void writeDoubleArray(double[] a) throws IOException {
writeInt(a.length);
writeDoubleArray(a, a.length);
Expand Down
81 changes: 81 additions & 0 deletions xdr-generator/generator/templates/XdrElement.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,58 @@ import org.stellar.sdk.Base64Factory;

/** Common parent interface for all generated classes. */
public interface XdrElement {
/** Shared Gson instance used by generated XDR classes for JSON serialization. */
Gson gson =
new GsonBuilder()
.disableHtmlEscaping()
.serializeNulls()
.setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL)
.create();

/**
* Encodes this value to XDR and writes it to the provided stream.
*
* @param stream the destination XDR output stream
* @throws IOException if an I/O error occurs while writing the value
*/
void encode(XdrDataOutputStream stream) throws IOException;

/**
* Encodes this value to XDR and returns the base64-encoded result.
*
* @return the base64-encoded XDR representation
* @throws IOException if an I/O error occurs while encoding the value
*/
default String toXdrBase64() throws IOException {
return Base64Factory.getInstance().encodeToString(toXdrByteArray());
}

/**
* Encodes this value to XDR and returns the raw bytes.
*
* @return the raw XDR byte representation
* @throws IOException if an I/O error occurs while encoding the value
*/
default byte[] toXdrByteArray() throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
XdrDataOutputStream xdrDataOutputStream = new XdrDataOutputStream(byteArrayOutputStream);
encode(xdrDataOutputStream);
return byteArrayOutputStream.toByteArray();
}

/**
* Serializes this value to JSON.
*
* @return the JSON representation of this value
*/
String toJson();

/**
* Returns the lowercase hexadecimal representation of a byte array.
*
* @param bytes the bytes to encode
* @return the lowercase hexadecimal representation
*/
static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
Expand All @@ -46,6 +76,13 @@ public interface XdrElement {
return sb.toString();
}

/**
* Decodes a hexadecimal string into bytes.
*
* @param hex the hexadecimal string to decode
* @return the decoded bytes
* @throws IllegalArgumentException if the input length is odd or contains non-hex characters
*/
static byte[] hexToBytes(String hex) {
if (hex.length() % 2 != 0) {
throw new IllegalArgumentException("Hex string must have an even length");
Expand All @@ -63,6 +100,12 @@ public interface XdrElement {
return data;
}

/**
* Converts a byte array to an escaped ASCII string suitable for JSON serialization.
*
* @param data the bytes to encode
* @return the escaped ASCII representation
*/
static String bytesToEscapedAscii(byte[] data) {
StringBuilder sb = new StringBuilder();
for (byte b : data) {
Expand Down Expand Up @@ -95,6 +138,13 @@ public interface XdrElement {
return sb.toString();
}

/**
* Decodes an escaped ASCII string produced by {@link #bytesToEscapedAscii(byte[])}.
*
* @param s the escaped ASCII string to decode
* @return the decoded bytes
* @throws IllegalArgumentException if the input contains invalid escape sequences or characters
*/
static byte[] escapedAsciiToBytes(String s) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
int i = 0;
Expand Down Expand Up @@ -155,6 +205,13 @@ public interface XdrElement {
return out.toByteArray();
}

/**
* Converts a JSON scalar into a Java {@code long}.
*
* @param json the JSON value to convert
* @return the converted {@code long} value
* @throws IllegalArgumentException if the JSON value is not a string or number
*/
static long jsonToLong(Object json) {
if (json instanceof String) {
return Long.parseLong((String) json);
Expand All @@ -168,6 +225,13 @@ public interface XdrElement {
throw new IllegalArgumentException("Expected JSON string or number, got: " + json);
}

/**
* Converts a JSON scalar into a {@link BigInteger}.
*
* @param json the JSON value to convert
* @return the converted {@link BigInteger} value
* @throws IllegalArgumentException if the JSON value is not a string or number
*/
static BigInteger jsonToBigInteger(Object json) {
if (json instanceof String) {
return new BigInteger((String) json);
Expand All @@ -181,6 +245,14 @@ public interface XdrElement {
throw new IllegalArgumentException("Expected JSON string or number, got: " + json);
}

/**
* Converts a Java array into a JSON array using the provided mapper.
*
* @param array the array to convert
* @param mapper maps each element index to a JSON-compatible value
* @param <T> the Java element type
* @return the converted JSON array
*/
@SuppressWarnings("unchecked")
static <T> List<Object> arrayToJsonArray(T[] array, IntFunction<Object> mapper) {
List<Object> list = new ArrayList<>(array.length);
Expand All @@ -190,6 +262,15 @@ public interface XdrElement {
return list;
}

/**
* Converts a JSON array into a Java array using the provided mapper.
*
* @param list the JSON array to convert
* @param clazz the Java array component type
* @param mapper maps each JSON value to the target Java type
* @param <T> the Java element type
* @return the converted Java array
*/
@SuppressWarnings("unchecked")
static <T> T[] jsonArrayToArray(List<Object> list, Class<T> clazz, Function<Object, T> mapper) {
T[] array = (T[]) Array.newInstance(clazz, list.size());
Expand Down
Loading
Loading