|
|
@@ -0,0 +1,258 @@
|
|
|
+package com.jttserver.codec;
|
|
|
+
|
|
|
+import io.netty.buffer.ByteBuf;
|
|
|
+import io.netty.buffer.Unpooled;
|
|
|
+import io.netty.channel.embedded.EmbeddedChannel;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
+
|
|
|
+import com.jttserver.protocol.JttConstants;
|
|
|
+
|
|
|
+public class Jtt1078MessageDecoder2019Test {
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void testSingleCompletePacket2019() {
|
|
|
+ // 测试2019版本单个完整的数据包
|
|
|
+ // 2019版本在第8字节开始的SIM卡号比2013版本多了4个字节(00 00 00 00)
|
|
|
+
|
|
|
+ // 创建嵌入式通道用于测试,使用2019版本
|
|
|
+ EmbeddedChannel channel = new EmbeddedChannel(new Jtt1078MessageDecoder(JttConstants.TYPE_JTT808_2019));
|
|
|
+
|
|
|
+ // 构造一个完整的JTT1078 2019版本数据包
|
|
|
+ // 头部: 0x30316364 (4字节)
|
|
|
+ // 包头(总共22字节 = 18字节 + 4字节SIM卡号扩展)
|
|
|
+ // 数据长度字段为2字节,位于包头的最后2个字节,设为5
|
|
|
+ // 数据部分: 5字节
|
|
|
+ byte[] packet = new byte[] {
|
|
|
+ 0x30, 0x31, 0x63, 0x64, // 头部标识 0x30316364 (字节0-3)
|
|
|
+ 0x01, 0x02, 0x03, 0x04, // 字节4-7
|
|
|
+ 0x00, 0x00, 0x00, 0x00, // 2019版本新增的4字节SIM卡号扩展 (字节8-11)
|
|
|
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, // 7字节填充 (字节12-18)
|
|
|
+ (byte) 0x40, // 字节19,前4位为0100,表示包头长度为22字节 (18+4)
|
|
|
+ 0x00, 0x05, // 数据长度字段,表示5字节数据 (位于包头最后2个字节,大端序)
|
|
|
+ 0x11, 0x22, 0x33, 0x44, 0x55 // 5字节实际数据
|
|
|
+ };
|
|
|
+
|
|
|
+ // 写入完整数据包
|
|
|
+ channel.writeInbound(Unpooled.copiedBuffer(packet));
|
|
|
+
|
|
|
+ // 读取解码后的数据
|
|
|
+ Object result = channel.readInbound();
|
|
|
+
|
|
|
+ // 验证解码结果
|
|
|
+ assertNotNull(result, "Result should not be null");
|
|
|
+ assertTrue(result instanceof byte[], "Result should be byte[]");
|
|
|
+ byte[] decodedPacket = (byte[]) result;
|
|
|
+ assertEquals(packet.length, decodedPacket.length);
|
|
|
+ assertArrayEquals(packet, decodedPacket);
|
|
|
+
|
|
|
+ // 验证没有更多数据
|
|
|
+ assertNull(channel.readInbound());
|
|
|
+ assertFalse(channel.finish()); // 通道应该没有更多数据,所以finish应该返回false
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void testStickyPackets2019() {
|
|
|
+ // 测试2019版本粘包情况:两个完整数据包连接在一起
|
|
|
+ EmbeddedChannel channel = new EmbeddedChannel(new Jtt1078MessageDecoder(JttConstants.TYPE_JTT808_2019));
|
|
|
+
|
|
|
+ // 构造两个2019版本数据包 (包头长度22字节 = 18字节 + 4字节SIM卡号扩展)
|
|
|
+ byte[] packet1 = new byte[] {
|
|
|
+ 0x30, 0x31, 0x63, 0x64, // 头部标识 (字节0-3)
|
|
|
+ 0x01, 0x02, 0x03, 0x04, // 字节4-7
|
|
|
+ 0x00, 0x00, 0x00, 0x00, // 2019版本新增的4字节SIM卡号扩展 (字节8-11)
|
|
|
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, // 7字节填充 (字节12-18)
|
|
|
+ (byte) 0x40, // 字节19,前4位为0100,表示包头长度为22字节
|
|
|
+ 0x00, 0x03, // 数据长度: 3字节 (位于包头最后2个字节,大端序)
|
|
|
+ 0x11, 0x22, 0x33 // 数据 (3字节)
|
|
|
+ };
|
|
|
+
|
|
|
+ byte[] packet2 = new byte[] {
|
|
|
+ 0x30, 0x31, 0x63, 0x64, // 头部标识 (字节0-3)
|
|
|
+ 0x01, 0x02, 0x03, 0x04, // 字节4-7
|
|
|
+ 0x00, 0x00, 0x00, 0x00, // 2019版本新增的4字节SIM卡号扩展 (字节8-11)
|
|
|
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, // 7字节填充 (字节12-18)
|
|
|
+ (byte) 0x40, // 字节19,前4位为0100,表示包头长度为22字节
|
|
|
+ 0x00, 0x02, // 数据长度: 2字节 (位于包头最后2个字节,大端序)
|
|
|
+ 0x44, 0x55 // 数据 (2字节)
|
|
|
+ };
|
|
|
+
|
|
|
+ // 将两个包粘在一起
|
|
|
+ byte[] stickyPacket = new byte[packet1.length + packet2.length];
|
|
|
+ System.arraycopy(packet1, 0, stickyPacket, 0, packet1.length);
|
|
|
+ System.arraycopy(packet2, 0, stickyPacket, packet1.length, packet2.length);
|
|
|
+
|
|
|
+ // 写入粘包数据
|
|
|
+ channel.writeInbound(Unpooled.copiedBuffer(stickyPacket));
|
|
|
+
|
|
|
+ // 应该能正确解析出两个数据包
|
|
|
+ Object result1 = channel.readInbound();
|
|
|
+ Object result2 = channel.readInbound();
|
|
|
+
|
|
|
+ // 验证第一个包
|
|
|
+ assertNotNull(result1, "First packet should not be null");
|
|
|
+ assertTrue(result1 instanceof byte[], "First packet should be byte[]");
|
|
|
+ byte[] decodedPacket1 = (byte[]) result1;
|
|
|
+ assertEquals(packet1.length, decodedPacket1.length);
|
|
|
+ assertArrayEquals(packet1, decodedPacket1);
|
|
|
+
|
|
|
+ // 验证第二个包
|
|
|
+ assertNotNull(result2, "Second packet should not be null");
|
|
|
+ assertTrue(result2 instanceof byte[], "Second packet should be byte[]");
|
|
|
+ byte[] decodedPacket2 = (byte[]) result2;
|
|
|
+ assertEquals(packet2.length, decodedPacket2.length);
|
|
|
+ assertArrayEquals(packet2, decodedPacket2);
|
|
|
+
|
|
|
+ // 验证没有更多数据
|
|
|
+ assertNull(channel.readInbound());
|
|
|
+ assertFalse(channel.finish()); // 通道应该没有更多数据,所以finish应该返回false
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void testFragmentedPacket2019() {
|
|
|
+ // 测试2019版本拆包情况:一个数据包分两次到达
|
|
|
+ EmbeddedChannel channel = new EmbeddedChannel(new Jtt1078MessageDecoder(JttConstants.TYPE_JTT808_2019));
|
|
|
+
|
|
|
+ // 构造一个完整2019版本数据包
|
|
|
+ byte[] fullPacket = new byte[] {
|
|
|
+ 0x30, 0x31, 0x63, 0x64, // 头部标识 (字节0-3)
|
|
|
+ 0x01, 0x02, 0x03, 0x04, // 字节4-7
|
|
|
+ 0x00, 0x00, 0x00, 0x00, // 2019版本新增的4字节SIM卡号扩展 (字节8-11)
|
|
|
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, // 7字节填充 (字节12-18)
|
|
|
+ (byte) 0x40, // 字节19,前4位为0100,表示包头长度为22字节
|
|
|
+ 0x00, 0x04, // 数据长度: 4字节 (位于包头最后2个字节,大端序)
|
|
|
+ 0x11, 0x22, 0x33, 0x44 // 数据 (4字节)
|
|
|
+ };
|
|
|
+
|
|
|
+ // 第一次只发送前12字节
|
|
|
+ ByteBuf firstPart = Unpooled.copiedBuffer(fullPacket, 0, 12);
|
|
|
+ channel.writeInbound(firstPart); // 不应该产生输出
|
|
|
+
|
|
|
+ // 验证还没有解码出数据
|
|
|
+ assertNull(channel.readInbound(), "Should not have decoded data after first part");
|
|
|
+
|
|
|
+ // 第二次发送剩余部分
|
|
|
+ ByteBuf secondPart = Unpooled.copiedBuffer(fullPacket, 12, fullPacket.length - 12);
|
|
|
+ channel.writeInbound(secondPart);
|
|
|
+
|
|
|
+ // 现在应该能读取到解码后的数据
|
|
|
+ Object result = channel.readInbound();
|
|
|
+ assertNotNull(result, "Result should not be null after second write");
|
|
|
+ assertTrue(result instanceof byte[], "Result should be byte[]");
|
|
|
+ byte[] decodedPacket = (byte[]) result;
|
|
|
+ assertEquals(fullPacket.length, decodedPacket.length);
|
|
|
+ assertArrayEquals(fullPacket, decodedPacket);
|
|
|
+
|
|
|
+ // 验证没有更多数据
|
|
|
+ assertNull(channel.readInbound());
|
|
|
+ assertFalse(channel.finish()); // 通道应该没有更多数据,所以finish应该返回false
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void testMultipleFragmentedPackets2019() {
|
|
|
+ // 测试2019版本多个拆包情况:第一个包分两段,第二个包完整
|
|
|
+ EmbeddedChannel channel = new EmbeddedChannel(new Jtt1078MessageDecoder(JttConstants.TYPE_JTT808_2019));
|
|
|
+
|
|
|
+ // 构造两个2019版本数据包
|
|
|
+ byte[] packet1 = new byte[] {
|
|
|
+ 0x30, 0x31, 0x63, 0x64, // 头部标识 (字节0-3)
|
|
|
+ 0x01, 0x02, 0x03, 0x04, // 字节4-7
|
|
|
+ 0x00, 0x00, 0x00, 0x00, // 2019版本新增的4字节SIM卡号扩展 (字节8-11)
|
|
|
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, // 7字节填充 (字节12-18)
|
|
|
+ (byte) 0x40, // 字节19,前4位为0100,表示包头长度为22字节
|
|
|
+ 0x00, 0x02, // 数据长度: 2字节 (位于包头最后2个字节,大端序)
|
|
|
+ 0x11, 0x22 // 数据 (2字节)
|
|
|
+ };
|
|
|
+
|
|
|
+ byte[] packet2 = new byte[] {
|
|
|
+ 0x30, 0x31, 0x63, 0x64, // 头部标识 (字节0-3)
|
|
|
+ 0x01, 0x02, 0x03, 0x04, // 字节4-7
|
|
|
+ 0x00, 0x00, 0x00, 0x00, // 2019版本新增的4字节SIM卡号扩展 (字节8-11)
|
|
|
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, // 7字节填充 (字节12-18)
|
|
|
+ (byte) 0x40, // 字节19,前4位为0100,表示包头长度为22字节
|
|
|
+ 0x00, 0x03, // 数据长度: 3字节 (位于包头最后2个字节,大端序)
|
|
|
+ 0x33, 0x44, 0x55 // 数据 (3字节)
|
|
|
+ };
|
|
|
+
|
|
|
+ // 第一次发送第一个包的前16字节
|
|
|
+ byte[] firstChunk = new byte[16];
|
|
|
+ System.arraycopy(packet1, 0, firstChunk, 0, 16); // packet1的前16字节
|
|
|
+
|
|
|
+ channel.writeInbound(Unpooled.copiedBuffer(firstChunk));
|
|
|
+
|
|
|
+ // 第二次发送第一个包的剩余部分 + 第二个包的完整数据
|
|
|
+ byte[] secondChunk = new byte[packet1.length - 16 + packet2.length];
|
|
|
+ System.arraycopy(packet1, 16, secondChunk, 0, packet1.length - 16); // packet1的剩余部分
|
|
|
+ System.arraycopy(packet2, 0, secondChunk, packet1.length - 16, packet2.length); // 完整的packet2
|
|
|
+
|
|
|
+ channel.writeInbound(Unpooled.copiedBuffer(secondChunk));
|
|
|
+
|
|
|
+ // 应该能正确解析出两个数据包
|
|
|
+ Object result1 = channel.readInbound();
|
|
|
+ Object result2 = channel.readInbound();
|
|
|
+
|
|
|
+ // 验证第一个包
|
|
|
+ assertNotNull(result1, "First packet should not be null");
|
|
|
+ assertTrue(result1 instanceof byte[], "First packet should be byte[]");
|
|
|
+ byte[] decodedPacket1 = (byte[]) result1;
|
|
|
+ assertEquals(packet1.length, decodedPacket1.length);
|
|
|
+ assertArrayEquals(packet1, decodedPacket1);
|
|
|
+
|
|
|
+ // 验证第二个包
|
|
|
+ assertNotNull(result2, "Second packet should not be null");
|
|
|
+ assertTrue(result2 instanceof byte[], "Second packet should be byte[]");
|
|
|
+ byte[] decodedPacket2 = (byte[]) result2;
|
|
|
+ assertEquals(packet2.length, decodedPacket2.length);
|
|
|
+ assertArrayEquals(packet2, decodedPacket2);
|
|
|
+
|
|
|
+ // 验证没有更多数据
|
|
|
+ assertNull(channel.readInbound());
|
|
|
+ assertFalse(channel.finish()); // 通道应该没有更多数据,所以finish应该返回false
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void testRealPacket2019() {
|
|
|
+ // 测试真实的2019版本数据包
|
|
|
+ // 数据包: "30316364816200000000000001501122334401010000019ac52ea5c003e801e0001c0000000167640029ac154a0b01269a8101012000007d00000ea60080"
|
|
|
+ EmbeddedChannel channel = new EmbeddedChannel(new Jtt1078MessageDecoder(JttConstants.TYPE_JTT808_2019));
|
|
|
+
|
|
|
+ // 将十六进制字符串转换为字节数组
|
|
|
+ String hexString = "30316364816200000000000001501122334401010000019ac52ea5c003e801e0001c0000000167640029ac154a0b01269a8101012000007d00000ea60080";
|
|
|
+ byte[] realPacket = hexStringToBytes(hexString);
|
|
|
+
|
|
|
+ // 写入真实数据包
|
|
|
+ channel.writeInbound(Unpooled.copiedBuffer(realPacket));
|
|
|
+
|
|
|
+ // 读取解码后的数据
|
|
|
+ Object result = channel.readInbound();
|
|
|
+
|
|
|
+ // 验证解码结果
|
|
|
+ assertNotNull(result, "Real packet should not be null");
|
|
|
+ assertTrue(result instanceof byte[], "Real packet should be byte[]");
|
|
|
+ byte[] decodedPacket = (byte[]) result;
|
|
|
+ assertEquals(realPacket.length, decodedPacket.length);
|
|
|
+ assertArrayEquals(realPacket, decodedPacket);
|
|
|
+
|
|
|
+ // 验证没有更多数据
|
|
|
+ assertNull(channel.readInbound());
|
|
|
+ assertFalse(channel.finish());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将十六进制字符串转换为字节数组
|
|
|
+ *
|
|
|
+ * @param hexString 十六进制字符串
|
|
|
+ * @return 字节数组
|
|
|
+ */
|
|
|
+ private byte[] hexStringToBytes(String hexString) {
|
|
|
+ int len = hexString.length();
|
|
|
+ byte[] data = new byte[len / 2];
|
|
|
+ for (int i = 0; i < len; i += 2) {
|
|
|
+ data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
|
|
|
+ + Character.digit(hexString.charAt(i + 1), 16));
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|