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();
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:
- Existing valid EA extension formats continue to be parsed normally.
- Vendor-specific or malformed EA payloads cannot move the ByteBuf reader index outside the EA field.
- The decoder safely skips an invalid EA extension instead of terminating the JT808 connection.
For example:
case 0xEA:
if (length > 2) {
buf.readUnsignedByte();
while (buf.readerIndex() < endIndex) {
if (endIndex - buf.readerIndex() < 2) {
break;
}
int extendedType = buf.readUnsignedByte();
int extendedLength = buf.readUnsignedByte();
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();
buf.readUnsignedInt();
buf.readUnsignedInt();
}
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.
Hello,
I found an issue in the JT808 decoder when receiving data from several terminals.
The issue occurs when parsing the
0xEAadditional information field. It affects both normal0x0200Location Reports and0x0704Batch Location Data Upload messages.The decoder eventually throws an exception such as:
or:
The exception originates from
Jt808ProtocolDecoder.Environment
Problem
The affected terminals send
0xEAadditional information fields such as:These can be interpreted as normal JT808 additional information items:
For example:
can simply mean:
However, the current
Jt808ProtocolDecoderimplementation treats the contents of0xEAas 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:
But the EA field only contains 4 bytes of payload.
Therefore, the calculated
extendedEndIndexis outside the actual EA field / ByteBuf.The same happens with:
where
0x93 = 147, and:where
0x8A = 138.Reproduction 1 — JT808 0x0704
The following
0x0704packet reproduces the problem:The relevant part is:
The
0x0704message contains one location record with a length of0x004C(76 bytes).Traccar eventually reports:
Reproduction 2 — JT808 0x0704
Another
0x0704packet:The relevant part is:
Here the parser interprets:
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
0x0200Location Report:The relevant part is:
The parser interprets:
although only one byte remains in the EA field after reading
0x8A.This results in:
The device does receive a normal
0x8001General Response before the decoder reports the exception.Suggested fix
I suggest adding boundary checks to the existing parser so that:
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:
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.