JT808 0xEA Extension Parsing Causes IndexOutOfBoundsException

viking 10 hours ago

Hello,

I found an issue in the JT808 decoder when receiving data from several terminals.

The issue occurs when parsing the 0xEA additional information field. It affects both normal 0x0200 Location Reports and 0x0704 Batch Location Data Upload messages.

The decoder eventually throws an exception such as:

readerIndex: 200, writerIndex: 76
(expected: 0 <= readerIndex <= writerIndex <= capacity(76))

or:

readerIndex: 216, writerIndex: 87
(expected: 0 <= readerIndex <= writerIndex <= capacity(87))

The exception originates from Jt808ProtocolDecoder.

Environment

  • Traccar: 6.14.2
  • Protocol: JT808
  • Transport: TCP
  • The device traffic is forwarded through FRP, but the payload itself is unchanged JT808 data.

Problem

The affected terminals send 0xEA additional information fields such as:

EA04020C8300
EA04020C9300
EA0402008A00

These can be interpreted as normal JT808 additional information items:

EA
04
<4 bytes of vendor-specific data>

For example:

EA 04 02 0C 83 00

can simply mean:

ID     = EA
Length = 04
Data   = 02 0C 83 00

However, the current Jt808ProtocolDecoder implementation treats the contents of 0xEA as another nested TLV structure:

case 0xEA:
    if (length > 2) {
        buf.readUnsignedByte(); // extended info type
        while (buf.readerIndex() < endIndex) {
            int extendedType = buf.readUnsignedByte();
            int extendedLength = buf.readUnsignedByte();
            int extendedEndIndex = buf.readerIndex() + extendedLength;

            switch (extendedType) {
                ...
            }

            buf.readerIndex(extendedEndIndex);
        }
    }
    break;

This causes the last bytes of the vendor-specific payload to be interpreted as a nested length.

For example:

EA 04 02 0C 83 00
      │  │  │
      │  │  └── interpreted as extendedLength = 0x83 = 131
      │  └───── interpreted as extendedType = 0x0C
      └──────── interpreted as extended info type = 0x02

But the EA field only contains 4 bytes of payload.

Therefore, the calculated extendedEndIndex is outside the actual EA field / ByteBuf.

The same happens with:

EA 04 02 0C 93 00

where 0x93 = 147, and:

EA 04 02 00 8A 00

where 0x8A = 138.

Reproduction 1 — JT808 0x0704

The following 0x0704 packet reproduces the problem:

7e070400510144503762020002000101004c0000010000080003021953bc06c90bdb005800000038260816205419140400000000170200000104000026b5030200002504000000002a02000030011a31010fea04020c8300ef04000000008d7e

The relevant part is:

EA04020C8300
EF0400000000

The 0x0704 message contains one location record with a length of 0x004C (76 bytes).

Traccar eventually reports:

readerIndex: 200, writerIndex: 76

Reproduction 2 — JT808 0x0704

Another 0x0704 packet:

7e070400510422829016270162000101004c0000000000080003020ecdca07153fe8001c028300be26081318153014040000000017020000010400019c5f030202862504000000002a020000300115310110ea04020c9300ef0400008000cc7e

The relevant part is:

EA04020C9300
EF0400008000

Here the parser interprets:

0x93 = 147

as the nested extension length, although the actual EA payload is only 4 bytes.

Reproduction 3 — JT808 0x0200

The same problem also occurs with a normal 0x0200 Location Report:

7e020000480124900141560372000800000008000201e886c9073538450024000000002608180000001404000000001702000001040003e69a2504000000002a02000030011f31010bea0402008a00ef04000000008f7e

The relevant part is:

EA0402008A00
EF0400000000

The parser interprets:

extendedType   = 0x00
extendedLength = 0x8A = 138

although only one byte remains in the EA field after reading 0x8A.

This results in:

readerIndex: 216, writerIndex: 87
IndexOutOfBoundsException

The device does receive a normal 0x8001 General Response before the decoder reports the exception.

Suggested fix

I suggest adding boundary checks to the existing parser so that:

  1. Existing valid EA extension formats continue to be parsed normally.
  2. Vendor-specific or malformed EA payloads cannot move the ByteBuf reader index outside the EA field.
  3. The decoder safely skips an invalid EA extension instead of terminating the JT808 connection.

For example:

case 0xEA:
    if (length > 2) {
        buf.readUnsignedByte(); // extended info type

        while (buf.readerIndex() < endIndex) {

            // Need at least extendedType + extendedLength
            if (endIndex - buf.readerIndex() < 2) {
                break;
            }

            int extendedType = buf.readUnsignedByte();
            int extendedLength = buf.readUnsignedByte();

            // Prevent malformed/vendor-specific extensions
            // from exceeding the EA field boundary.
            if (extendedLength > endIndex - buf.readerIndex()) {
                break;
            }

            int extendedEndIndex = buf.readerIndex() + extendedLength;

            switch (extendedType) {
                case 0x11:
                    if (extendedLength >= 15) {
                        position.set("externalAlarms", buf.readUnsignedShort());
                        position.set("alarmThresholdType", buf.readUnsignedByte());
                        buf.readUnsignedInt(); // upper threshold
                        buf.readUnsignedInt(); // current value
                        buf.readUnsignedInt(); // lower threshold
                    }
                    break;

                case 0x13:
                    if (extendedLength >= 2) {
                        position.set("externalIlluminance", buf.readUnsignedShort());
                    }
                    break;

                case 0x14:
                    if (extendedLength >= 2) {
                        position.set("externalAirPressure", buf.readUnsignedShort());
                    }
                    break;

                case 0x15:
                    if (extendedLength >= 2) {
                        position.set("externalHumidity",
                                buf.readUnsignedShort() / 10.0);
                    }
                    break;

                case 0x16:
                    if (extendedLength >= 2) {
                        position.set("externalTemp",
                                buf.readUnsignedShort() / 10.0 - 50);
                    }
                    break;

                default:
                    break;
            }

            buf.readerIndex(extendedEndIndex);
        }
    }
    break;

The most important check is:

if (extendedLength > endIndex - buf.readerIndex()) {
    break;
}

This should preserve the current behavior for valid nested EA extensions while preventing vendor-specific EA data from causing an IndexOutOfBoundsException.

It may also be worth adding similar length/boundary validation to other variable-length JT808 additional-information parsers for robustness.

Expected behavior

With the proposed validation, packets containing:

EA04020C8300
EA04020C9300
EA0402008A00

should no longer cause the JT808 decoder to fail.

The EA data can simply be ignored when it does not conform to the nested extension format expected by the decoder, while the rest of the location report should continue to be processed normally.

I believe this would improve compatibility with terminals using vendor-specific JT808 extensions without changing the existing handling of valid EA extension types.

Thank you.

Anton Tananaev 9 hours ago

There's no need to dump AI output here. We would need the source data:

  1. Raw unedited logs
  2. Protocol documentation
viking 8 hours ago

I apologize, I thought the AI ​​would be more professional than I was. Below is the original log text, with two examples: 0x0200 and 0x0704. Also included is the latest official documentation (JT/T 808-2019, page 23; the original text is in Chinese, but I can translate it into English if needed).

viking 8 hours ago

7e0200:

2026-08-18 00:05:01  INFO: [Tc085932f: jt808 < 127.0.0.1] 7e020000480124900141560374000800000008000201e886c907353845001f000000002608180005001404000000001702000001040003e69a2504000000002a02000030011f31010bea0402008a00ef0400000000b77e
2026-08-18 00:05:01  INFO: [Tc085932f: jt808 > 127.0.0.1] 7e8001000501249001415600000374020000527e
2026-08-18 00:05:01  WARN: [Tc085932f] error - readerIndex: 216, writerIndex: 87 (expected: 0 <= readerIndex <= writerIndex <= capacity(87)) - IndexOutOfBoundsException (... < Jt808ProtocolDecoder:1009 < *:410 < ExtendedObjectDecoder:72 < ... < WrapperContext:102 < ... < WrapperInboundHandler:56 < ...)
2026-08-18 00:05:01  INFO: [Tc085932f] disconnected

7e0704:

2026-08-18 05:54:20  INFO: [T8fc34d35: jt808 < 127.0.0.1] 7e070400510144503762020002000101004c00040100000800030202301006f893e8001d00000095260817221812140400000000170200000104000036c9030200002504000000002a02000030011f31010eea04020c8300ef0400000000037e
2026-08-18 05:54:20  INFO: [T8fc34d35: jt808 > 127.0.0.1] 7e8001000501445037620200000002070400c77e
2026-08-18 05:54:20  WARN: [T8fc34d35] error - readerIndex: 200, writerIndex: 76 (expected: 0 <= readerIndex <= writerIndex <= capacity(76)) - IndexOutOfBoundsException (... < Jt808ProtocolDecoder:1009 < *:1387 < *:424 < ExtendedObjectDecoder:72 < ... < WrapperContext:102 < ... < WrapperInboundHandler:56 < ...)
2026-08-18 05:54:20  INFO: [T8fc34d35] disconnected

Protocol documentation:
JT808-2019 Protocol Documentation (JT/T 808-2019, page 23(DPF, page 27); the original text is in Chinese, but I can translate it into English if needed).

Anton Tananaev 7 hours ago
viking 7 hours ago

Thank you!