"BinaryParser.readVariableLengthLength()" appears to decode XRPL variable-length fields incorrectly when the encoded length uses the 2-byte form.
According to the XRPL binary format, lengths from "193" to "12480" bytes should decode as:
193 + ((byte1 - 193) * 256) + byte2
In "xrpl4j", the 2-byte branch currently uses a formula shaped like the 3-byte branch:
return MAX_SECOND_BYTE_VALUE - 1
+ (firstByte - MAX_SECOND_BYTE_VALUE - 1) * MAX_BYTE_VALUE
+ b2;
This evaluates as:
239 + ((byte1 - 241) * 256) + byte2
That creates a constant "-12242" byte error for every 2-byte variable-length prefix.
Affected code
File:
xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/serdes/BinaryParser.java
Method:
readVariableLengthLength()
The affected path is used here:
if (field.isVariableLengthEncoded()) {
int sizeHint = this.readVariableLengthLength();
return type.fromParser(this, sizeHint);
}
Impact
Valid XRPL binary containing a variable-length field between "193" and "12480" bytes can be misparsed by "xrpl4j".
For most of the range, "readVariableLengthLength()" returns a negative "sizeHint". Since "BinaryParser.read(int bytesToRead)" does not reject negative lengths, it returns an empty value and leaves the cursor at the start of the field content. The parser then interprets the remaining content bytes as later field headers, which can desynchronize the stream and cause decode failure.
For the upper part of the range, the returned value is positive but still too small, so the field is truncated and the parser desynchronizes afterward.
This affects ordinary XRPL fields that can exceed 192 bytes, for example:
- "MemoData"
- "NFTokenMint.URI"
- "AccountSet.Domain"
- "MPTokenMetadata"
A Java service that decodes ledger or transaction binary with "xrpl4j" can therefore fail on valid XRPL data containing such fields.
Exact range behavior
There are "12,288" possible 2-byte length prefixes for content lengths "193..12480".
Current behavior:
193..12241 -> negative sizeHint, empty read, parser desync
12242..12480 -> wrong positive sizeHint, truncated read, parser desync
Correct values -> 0
Example:
Correct length 193:
expected: 193
actual: -12049
Correct length 12480:
expected: 12480
actual: 238
Why this looks unintended
The "xrpl4j" encoder uses the correct base-193 formula for the same range:
length -= 193;
int byte1 = 193 + (length >>> 8);
int byte2 = length & 0xff;
So "xrpl4j" can encode a valid 2-byte variable-length field and then decode that same length prefix incorrectly.
The 1-byte and 3-byte branches appear to follow the XRPL length-prefix rules correctly. Only the 2-byte branch seems affected.
Suggested fix
Replace the 2-byte branch with the base-193 formula:
} else if (firstByte <= MAX_SECOND_BYTE_VALUE) {
int b2 = this.readUInt8().intValue();
return (MAX_SINGLE_BYTE_LENGTH + 1)
+ (firstByte - (MAX_SINGLE_BYTE_LENGTH + 1)) * MAX_BYTE_VALUE
+ b2;
}
It would also be useful to reject negative "sizeHint" values before calling "type.fromParser(...)". That would prevent the empty-read desynchronization case, although the formula fix is still required for the wrong-positive truncation range.
Suggested tests
Add round-trip and decode coverage for 2-byte variable-length boundaries:
193
448
12241
12242
12480
The tests should verify that the parser consumes exactly the advertised field length and that encoded 2-byte variable-length fields decode back to the original content.
References
XRPL binary format, length prefixing:
https://xrpl.org/docs/references/protocol/binary-format/
Affected parser:
https://github.com/XRPLF/xrpl4j/blob/main/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/serdes/BinaryParser.java
Related encoder:
https://github.com/XRPLF/xrpl4j/blob/main/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/serdes/BinarySerializer.java
"BinaryParser.readVariableLengthLength()" appears to decode XRPL variable-length fields incorrectly when the encoded length uses the 2-byte form.
According to the XRPL binary format, lengths from "193" to "12480" bytes should decode as:
In "xrpl4j", the 2-byte branch currently uses a formula shaped like the 3-byte branch:
This evaluates as:
That creates a constant "-12242" byte error for every 2-byte variable-length prefix.
Affected code
File:
Method:
The affected path is used here:
Impact
Valid XRPL binary containing a variable-length field between "193" and "12480" bytes can be misparsed by "xrpl4j".
For most of the range, "readVariableLengthLength()" returns a negative "sizeHint". Since "BinaryParser.read(int bytesToRead)" does not reject negative lengths, it returns an empty value and leaves the cursor at the start of the field content. The parser then interprets the remaining content bytes as later field headers, which can desynchronize the stream and cause decode failure.
For the upper part of the range, the returned value is positive but still too small, so the field is truncated and the parser desynchronizes afterward.
This affects ordinary XRPL fields that can exceed 192 bytes, for example:
A Java service that decodes ledger or transaction binary with "xrpl4j" can therefore fail on valid XRPL data containing such fields.
Exact range behavior
There are "12,288" possible 2-byte length prefixes for content lengths "193..12480".
Current behavior:
193..12241 -> negative sizeHint, empty read, parser desync
12242..12480 -> wrong positive sizeHint, truncated read, parser desync
Correct values -> 0
Example:
Correct length 193:
expected: 193
actual: -12049
Correct length 12480:
expected: 12480
actual: 238
Why this looks unintended
The "xrpl4j" encoder uses the correct base-193 formula for the same range:
length -= 193;
int byte1 = 193 + (length >>> 8);
int byte2 = length & 0xff;
So "xrpl4j" can encode a valid 2-byte variable-length field and then decode that same length prefix incorrectly.
The 1-byte and 3-byte branches appear to follow the XRPL length-prefix rules correctly. Only the 2-byte branch seems affected.
Suggested fix
Replace the 2-byte branch with the base-193 formula:
It would also be useful to reject negative "sizeHint" values before calling "type.fromParser(...)". That would prevent the empty-read desynchronization case, although the formula fix is still required for the wrong-positive truncation range.
Suggested tests
Add round-trip and decode coverage for 2-byte variable-length boundaries:
193
448
12241
12242
12480
The tests should verify that the parser consumes exactly the advertised field length and that encoded 2-byte variable-length fields decode back to the original content.
References
XRPL binary format, length prefixing:
https://xrpl.org/docs/references/protocol/binary-format/
Affected parser:
https://github.com/XRPLF/xrpl4j/blob/main/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/serdes/BinaryParser.java
Related encoder:
https://github.com/XRPLF/xrpl4j/blob/main/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/serdes/BinarySerializer.java