aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton.tananaev@gmail.com>2021-11-05 16:48:27 -0700
committerAnton Tananaev <anton.tananaev@gmail.com>2021-11-05 16:48:27 -0700
commit10a11e03fbb45506973c85ae49d0fd1443d0f9f1 (patch)
treecadcbe287fc14b0c9300b73163938539254fa89e
parentf8e6bc5ced2244f5f09664e7d778c0bf51cd4ab9 (diff)
downloadtraccar-server-10a11e03fbb45506973c85ae49d0fd1443d0f9f1.tar.gz
traccar-server-10a11e03fbb45506973c85ae49d0fd1443d0f9f1.tar.bz2
traccar-server-10a11e03fbb45506973c85ae49d0fd1443d0f9f1.zip
Support new Xexun protocol (fix #4727)
-rw-r--r--setup/default.xml1
-rw-r--r--src/main/java/org/traccar/helper/BufferUtil.java27
-rw-r--r--src/main/java/org/traccar/protocol/Xexun2FrameDecoder.java69
-rw-r--r--src/main/java/org/traccar/protocol/Xexun2Protocol.java34
-rw-r--r--src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java205
-rw-r--r--src/test/java/org/traccar/protocol/Xexun2FrameDecoderTest.java19
-rw-r--r--src/test/java/org/traccar/protocol/Xexun2ProtocolDecoderTest.java27
7 files changed, 373 insertions, 9 deletions
diff --git a/setup/default.xml b/setup/default.xml
index 4d4de39b0..4e2a5f746 100644
--- a/setup/default.xml
+++ b/setup/default.xml
@@ -298,5 +298,6 @@
<entry key='b2316.port'>5230</entry>
<entry key='hoopo.port'>5231</entry>
<entry key='dualcam.port'>5232</entry>
+ <entry key='xexun2.port'>5233</entry>
</properties>
diff --git a/src/main/java/org/traccar/helper/BufferUtil.java b/src/main/java/org/traccar/helper/BufferUtil.java
index b086f0f9e..bbf12d738 100644
--- a/src/main/java/org/traccar/helper/BufferUtil.java
+++ b/src/main/java/org/traccar/helper/BufferUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2021 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,19 +28,28 @@ public final class BufferUtil {
}
public static int indexOf(String needle, ByteBuf haystack) {
- ByteBuf needleBuffer = Unpooled.wrappedBuffer(needle.getBytes(StandardCharsets.US_ASCII));
+ return indexOf(needle, haystack, haystack.readerIndex(), haystack.writerIndex());
+ }
+
+ public static int indexOf(String needle, ByteBuf haystack, int startIndex, int endIndex) {
+ ByteBuf wrappedNeedle = Unpooled.wrappedBuffer(needle.getBytes(StandardCharsets.US_ASCII));
try {
- return ByteBufUtil.indexOf(needleBuffer, haystack);
+ return indexOf(wrappedNeedle, haystack, startIndex, endIndex);
} finally {
- needleBuffer.release();
+ wrappedNeedle.release();
}
}
- public static int indexOf(String needle, ByteBuf haystack, int startIndex, int endIndex) {
- ByteBuf wrappedHaystack = Unpooled.wrappedBuffer(haystack);
- wrappedHaystack.readerIndex(startIndex - haystack.readerIndex());
- wrappedHaystack.writerIndex(endIndex - haystack.readerIndex());
- int result = indexOf(needle, wrappedHaystack);
+ public static int indexOf(ByteBuf needle, ByteBuf haystack, int startIndex, int endIndex) {
+ ByteBuf wrappedHaystack;
+ if (startIndex == haystack.readerIndex() && endIndex == haystack.writerIndex()) {
+ wrappedHaystack = haystack;
+ } else {
+ wrappedHaystack = Unpooled.wrappedBuffer(haystack);
+ wrappedHaystack.readerIndex(startIndex - haystack.readerIndex());
+ wrappedHaystack.writerIndex(endIndex - haystack.readerIndex());
+ }
+ int result = ByteBufUtil.indexOf(needle, wrappedHaystack);
return result < 0 ? result : haystack.readerIndex() + startIndex + result;
}
diff --git a/src/main/java/org/traccar/protocol/Xexun2FrameDecoder.java b/src/main/java/org/traccar/protocol/Xexun2FrameDecoder.java
new file mode 100644
index 000000000..521d0209c
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/Xexun2FrameDecoder.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2021 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import org.traccar.BaseFrameDecoder;
+import org.traccar.helper.BufferUtil;
+
+public class Xexun2FrameDecoder extends BaseFrameDecoder {
+
+ @Override
+ protected Object decode(
+ ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception {
+
+ if (buf.readableBytes() < 5) {
+ return null;
+ }
+
+ ByteBuf flag = Unpooled.wrappedBuffer(new byte[] {(byte) 0xfa, (byte) 0xaf});
+ int index;
+ try {
+ index = BufferUtil.indexOf(flag, buf, buf.readerIndex() + 2, buf.writerIndex());
+ } finally {
+ flag.release();
+ }
+
+ if (index >= 0) {
+ ByteBuf result = Unpooled.buffer(index + 2 - buf.readerIndex());
+
+ while (buf.readerIndex() < index + 2) {
+ int b = buf.readUnsignedByte();
+ if (b == 0xfb && buf.isReadable() && buf.getUnsignedByte(buf.readerIndex()) == 0xbf) {
+ buf.readUnsignedByte(); // skip
+ int ext = buf.readUnsignedByte();
+ if (ext == 0x01) {
+ result.writeByte(0xfa);
+ result.writeByte(0xaf);
+ } else if (ext == 0x02) {
+ result.writeByte(0xfb);
+ result.writeByte(0xbf);
+ }
+ } else {
+ result.writeByte(b);
+ }
+ }
+
+ return result;
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/Xexun2Protocol.java b/src/main/java/org/traccar/protocol/Xexun2Protocol.java
new file mode 100644
index 000000000..265841c77
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/Xexun2Protocol.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2021 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+
+public class Xexun2Protocol extends BaseProtocol {
+
+ public Xexun2Protocol() {
+ addServer(new TrackerServer(false, getName()) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline) {
+ pipeline.addLast(new Xexun2FrameDecoder());
+ pipeline.addLast(new Xexun2ProtocolDecoder(Xexun2Protocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java
new file mode 100644
index 000000000..766a3f05b
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2021 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.DeviceSession;
+import org.traccar.NetworkMessage;
+import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.model.CellTower;
+import org.traccar.model.Network;
+import org.traccar.model.Position;
+import org.traccar.model.WifiAccessPoint;
+
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class Xexun2ProtocolDecoder extends BaseProtocolDecoder {
+
+ public Xexun2ProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ public static final int MSG_POSITION = 0x14;
+
+ private void sendResponse(Channel channel, int type, int index, ByteBuf imei) {
+ if (channel != null) {
+ ByteBuf response = Unpooled.buffer();
+ response.writeByte(0xfa);
+ response.writeByte(0xaf);
+
+ response.writeShort(type);
+ response.writeShort(index);
+ response.writeBytes(imei);
+ response.writeShort(1); // attributes / length
+ response.writeShort(0xfffe); // checksum
+ response.writeByte(1); // response
+
+ response.writeByte(0xfa);
+ response.writeByte(0xaf);
+
+ channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress()));
+ }
+ }
+
+ private String decodeAlarm(long value) {
+ if (BitUtil.check(value, 0)) {
+ return Position.ALARM_SOS;
+ }
+ if (BitUtil.check(value, 15)) {
+ return Position.ALARM_FALL_DOWN;
+ }
+ return null;
+ }
+
+ private double convertCoordinate(double value) {
+ double degrees = Math.floor(value / 100);
+ double minutes = value - degrees * 100;
+ return degrees + minutes / 60;
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ buf.skipBytes(2); // flag
+ int type = buf.readUnsignedShort();
+ int index = buf.readUnsignedShort();
+
+ ByteBuf imei = buf.readSlice(8);
+ DeviceSession deviceSession = getDeviceSession(
+ channel, remoteAddress, ByteBufUtil.hexDump(imei).substring(0, 15));
+ if (deviceSession == null) {
+ return null;
+ }
+
+ sendResponse(channel, type, index, imei);
+
+ buf.readUnsignedShort(); // attributes
+ buf.readUnsignedShort(); // checksum
+
+ if (type == MSG_POSITION) {
+ List<Integer> lengths = new ArrayList<>();
+ List<Position> positions = new ArrayList<>();
+
+ int count = buf.readUnsignedByte();
+ for (int i = 0; i < count; i++) {
+ lengths.add(buf.readUnsignedShort());
+ }
+
+ for (int i = 0; i < count; i++) {
+ int endIndex = buf.readerIndex() + lengths.get(i);
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.set(Position.KEY_INDEX, buf.readUnsignedByte());
+
+ position.setDeviceTime(new Date(buf.readUnsignedInt() * 1000));
+
+ position.set(Position.KEY_RSSI, buf.readUnsignedByte());
+
+ int battery = buf.readUnsignedShort();
+ position.set(Position.KEY_CHARGE, BitUtil.check(battery, 15));
+ position.set(Position.KEY_BATTERY_LEVEL, BitUtil.to(battery, 15));
+
+ int mask = buf.readUnsignedByte();
+
+ if (BitUtil.check(mask, 0)) {
+ position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedInt()));
+ }
+ if (BitUtil.check(mask, 1)) {
+ int positionMask = buf.readUnsignedByte();
+ if (BitUtil.check(positionMask, 0)) {
+ position.setValid(true);
+ position.setFixTime(position.getDeviceTime());
+ position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
+ position.setLongitude(convertCoordinate(buf.readFloat()));
+ position.setLatitude(convertCoordinate(buf.readFloat()));
+ }
+ Network network = new Network();
+ if (BitUtil.check(positionMask, 1)) {
+ int wifiCount = buf.readUnsignedByte();
+ for (int j = 0; j < wifiCount; j++) {
+ String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:");
+ network.addWifiAccessPoint(WifiAccessPoint.from(
+ mac.substring(0, mac.length() - 1), buf.readUnsignedByte()));
+ }
+ }
+ if (BitUtil.check(positionMask, 2)) {
+ int cellCount = buf.readUnsignedByte();
+ for (int j = 0; j < cellCount; j++) {
+ network.addCellTower(CellTower.from(
+ buf.readUnsignedShort(), buf.readUnsignedShort(),
+ buf.readInt(), buf.readUnsignedInt(), buf.readUnsignedByte()));
+ }
+ }
+ if (network.getWifiAccessPoints() != null || network.getCellTowers() != null) {
+ position.setNetwork(network);
+ }
+ if (BitUtil.check(positionMask, 3)) {
+ buf.skipBytes(12 * buf.readUnsignedByte()); // tof
+ }
+ if (BitUtil.check(positionMask, 5)) {
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1));
+ position.setCourse(buf.readUnsignedShort() * 0.1);
+ }
+ if (BitUtil.check(positionMask, 6)) {
+ position.setValid(true);
+ position.setFixTime(position.getDeviceTime());
+ position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
+ position.setLongitude(convertCoordinate(buf.readDouble()));
+ position.setLatitude(convertCoordinate(buf.readDouble()));
+
+ }
+ }
+ if (BitUtil.check(mask, 3)) {
+ buf.readUnsignedInt(); // fingerprint
+ }
+ if (BitUtil.check(mask, 4)) {
+ buf.skipBytes(20); // version
+ buf.skipBytes(8); // imsi
+ buf.skipBytes(10); // iccid
+ }
+ if (BitUtil.check(mask, 5)) {
+ buf.skipBytes(12); // device parameters
+ }
+
+ if (!position.getValid()) {
+ getLastLocation(position, position.getDeviceTime());
+ }
+ positions.add(position);
+
+ buf.readerIndex(endIndex);
+ }
+
+ return positions;
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/test/java/org/traccar/protocol/Xexun2FrameDecoderTest.java b/src/test/java/org/traccar/protocol/Xexun2FrameDecoderTest.java
new file mode 100644
index 000000000..aeca95376
--- /dev/null
+++ b/src/test/java/org/traccar/protocol/Xexun2FrameDecoderTest.java
@@ -0,0 +1,19 @@
+package org.traccar.protocol;
+
+import org.junit.Test;
+import org.traccar.ProtocolTest;
+
+public class Xexun2FrameDecoderTest extends ProtocolTest {
+
+ @Test
+ public void testDecode() throws Exception {
+
+ var decoder = new Xexun2FrameDecoder();
+
+ verifyFrame(
+ binary("faaf0014000286147503139003400032f2b001002f4260b0d6a0008019104a3378323130333135317c323130333132303100704020308715758089502023015648643670faaf"),
+ decoder.decode(null, null, binary("faaf0014000286147503139003400032f2b001002f4260b0d6a0008019104a3378323130333135317c323130333132303100704020308715758089502023015648643670faaf")));
+
+ }
+
+}
diff --git a/src/test/java/org/traccar/protocol/Xexun2ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Xexun2ProtocolDecoderTest.java
new file mode 100644
index 000000000..89c499016
--- /dev/null
+++ b/src/test/java/org/traccar/protocol/Xexun2ProtocolDecoderTest.java
@@ -0,0 +1,27 @@
+package org.traccar.protocol;
+
+import org.junit.Test;
+import org.traccar.ProtocolTest;
+
+public class Xexun2ProtocolDecoderTest extends ProtocolTest {
+
+ @Test
+ public void testDecode() throws Exception {
+
+ var decoder = new Xexun2ProtocolDecoder(null);
+
+ verifyPositions(decoder, false, binary(
+ "FAAF00140004863921033475388000AFB7D203003800380038F9608A7B801E0060820205788A205DF523D97844FDB90443D37844FDB90465CFB4FBF946B0E8CEF639095803F8CC00000002350000004000FA608A7BA81E0060820205788A205DF523D97844FDB90443D2F639095803F8CFB4FBF946B0E8CE7844FDB90465CD00000002350000004000FB608A7BD01E0060820205788A205DF523D97844FDB90443D2F639095803F8CFB4FBF946B0E8CE7844FDB90465CD00000002350000004000FAAF"));
+
+ verifyPositions(decoder, false, binary(
+ "faaf0014000286147503139003400032f2b001002f4260b0d6a0008019104a3378323130333135317c323130333132303100704020308715758089502023015648643670faaf"));
+
+ verifyPositions(decoder, false, binary(
+ "FAAF0014000486188105421927500035E6D2010032FC60EC264D00002003000000020205444E6DD72699D674427F7712CBC3BCF2AFD910BAC1C6FBE474CFC7A9B4FBE474CFC7A6FAAF"));
+
+ verifyPositions(decoder, binary(
+ "FAAF00140CF18626490454584530002BF2DD0200130013D360EFD7F514006402010D46322C4A450BA026D460EFD7FA14006402010D46322C4A450BA026FAAF"));
+
+ }
+
+}