Skip to content

BinaryParser decodes 2-byte variable-length fields with the wrong length formula #805

Description

@RaymondSeven

"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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions