aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar/helper
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/traccar/helper')
-rw-r--r--src/main/java/org/traccar/helper/BcdUtil.java63
-rw-r--r--src/main/java/org/traccar/helper/BitBuffer.java101
-rw-r--r--src/main/java/org/traccar/helper/BitUtil.java51
-rw-r--r--src/main/java/org/traccar/helper/BufferUtil.java47
-rw-r--r--src/main/java/org/traccar/helper/Checksum.java200
-rw-r--r--src/main/java/org/traccar/helper/DataConverter.java47
-rw-r--r--src/main/java/org/traccar/helper/DateBuilder.java126
-rw-r--r--src/main/java/org/traccar/helper/DateUtil.java78
-rw-r--r--src/main/java/org/traccar/helper/DistanceCalculator.java53
-rw-r--r--src/main/java/org/traccar/helper/Hashing.java100
-rw-r--r--src/main/java/org/traccar/helper/LocationTree.java125
-rw-r--r--src/main/java/org/traccar/helper/Log.java265
-rw-r--r--src/main/java/org/traccar/helper/LogAction.java99
-rw-r--r--src/main/java/org/traccar/helper/ObdDecoder.java105
-rw-r--r--src/main/java/org/traccar/helper/Parser.java348
-rw-r--r--src/main/java/org/traccar/helper/PatternBuilder.java98
-rw-r--r--src/main/java/org/traccar/helper/PatternUtil.java81
-rw-r--r--src/main/java/org/traccar/helper/SanitizerModule.java45
-rw-r--r--src/main/java/org/traccar/helper/UnitsConverter.java88
19 files changed, 2120 insertions, 0 deletions
diff --git a/src/main/java/org/traccar/helper/BcdUtil.java b/src/main/java/org/traccar/helper/BcdUtil.java
new file mode 100644
index 000000000..c87529e32
--- /dev/null
+++ b/src/main/java/org/traccar/helper/BcdUtil.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012 - 2018 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.helper;
+
+import io.netty.buffer.ByteBuf;
+
+public final class BcdUtil {
+
+ private BcdUtil() {
+ }
+
+ public static int readInteger(ByteBuf buf, int digits) {
+ int result = 0;
+
+ for (int i = 0; i < digits / 2; i++) {
+ int b = buf.readUnsignedByte();
+ result *= 10;
+ result += b >>> 4;
+ result *= 10;
+ result += b & 0x0f;
+ }
+
+ if (digits % 2 != 0) {
+ int b = buf.getUnsignedByte(buf.readerIndex());
+ result *= 10;
+ result += b >>> 4;
+ }
+
+ return result;
+ }
+
+ public static double readCoordinate(ByteBuf buf) {
+ int b1 = buf.readUnsignedByte();
+ int b2 = buf.readUnsignedByte();
+ int b3 = buf.readUnsignedByte();
+ int b4 = buf.readUnsignedByte();
+
+ double value = (b2 & 0xf) * 10 + (b3 >> 4);
+ value += (((b3 & 0xf) * 10 + (b4 >> 4)) * 10 + (b4 & 0xf)) / 1000.0;
+ value /= 60;
+ value += ((b1 >> 4 & 0x7) * 10 + (b1 & 0xf)) * 10 + (b2 >> 4);
+
+ if ((b1 & 0x80) != 0) {
+ value = -value;
+ }
+
+ return value;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/BitBuffer.java b/src/main/java/org/traccar/helper/BitBuffer.java
new file mode 100644
index 000000000..f30a4557b
--- /dev/null
+++ b/src/main/java/org/traccar/helper/BitBuffer.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2016 - 2018 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.helper;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+
+public class BitBuffer {
+
+ private final ByteBuf buffer;
+
+ private int writeByte;
+ private int writeCount;
+
+ private int readByte;
+ private int readCount;
+
+ public BitBuffer() {
+ buffer = Unpooled.buffer();
+ }
+
+ public BitBuffer(ByteBuf buffer) {
+ this.buffer = buffer;
+ }
+
+ public void writeEncoded(byte[] bytes) {
+ for (byte b : bytes) {
+ b -= 48;
+ if (b > 40) {
+ b -= 8;
+ }
+ write(b);
+ }
+ }
+
+ public void write(int b) {
+ if (writeCount == 0) {
+ writeByte |= b;
+ writeCount = 6;
+ } else {
+ int remaining = 8 - writeCount;
+ writeByte <<= remaining;
+ writeByte |= b >> (6 - remaining);
+ buffer.writeByte(writeByte);
+ writeByte = b & ((1 << (6 - remaining)) - 1);
+ writeCount = 6 - remaining;
+ }
+ }
+
+ public int readUnsigned(int length) {
+ int result = 0;
+
+ while (length > 0) {
+ if (readCount == 0) {
+ readByte = buffer.readUnsignedByte();
+ readCount = 8;
+ }
+ if (readCount >= length) {
+ result <<= length;
+ result |= readByte >> (readCount - length);
+ readByte &= (1 << (readCount - length)) - 1;
+ readCount -= length;
+ length = 0;
+ } else {
+ result <<= readCount;
+ result |= readByte;
+ length -= readCount;
+ readByte = 0;
+ readCount = 0;
+ }
+ }
+
+ return result;
+ }
+
+ public int readSigned(int length) {
+ int result = readUnsigned(length);
+ int signBit = 1 << (length - 1);
+ if ((result & signBit) == 0) {
+ return result;
+ } else {
+ result &= signBit - 1;
+ result += ~(signBit - 1);
+ return result;
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/BitUtil.java b/src/main/java/org/traccar/helper/BitUtil.java
new file mode 100644
index 000000000..b6108edff
--- /dev/null
+++ b/src/main/java/org/traccar/helper/BitUtil.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 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.helper;
+
+public final class BitUtil {
+
+ private BitUtil() {
+ }
+
+ public static boolean check(long number, int index) {
+ return (number & (1 << index)) != 0;
+ }
+
+ public static int between(int number, int from, int to) {
+ return (number >> from) & ((1 << to - from) - 1);
+ }
+
+ public static int from(int number, int from) {
+ return number >> from;
+ }
+
+ public static int to(int number, int to) {
+ return between(number, 0, to);
+ }
+
+ public static long between(long number, int from, int to) {
+ return (number >> from) & ((1L << to - from) - 1L);
+ }
+
+ public static long from(long number, int from) {
+ return number >> from;
+ }
+
+ public static long to(long number, int to) {
+ return between(number, 0, to);
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/BufferUtil.java b/src/main/java/org/traccar/helper/BufferUtil.java
new file mode 100644
index 000000000..15c619ec5
--- /dev/null
+++ b/src/main/java/org/traccar/helper/BufferUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 Andrey Kunitsyn (andrey@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.helper;
+
+import java.nio.charset.StandardCharsets;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+
+public final class BufferUtil {
+
+ private BufferUtil() {
+ }
+
+ public static int indexOf(String needle, ByteBuf haystack) {
+ ByteBuf needleBuffer = Unpooled.wrappedBuffer(needle.getBytes(StandardCharsets.US_ASCII));
+ try {
+ return ByteBufUtil.indexOf(needleBuffer, haystack);
+ } finally {
+ needleBuffer.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);
+ return result < 0 ? result : haystack.readerIndex() + result;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/Checksum.java b/src/main/java/org/traccar/helper/Checksum.java
new file mode 100644
index 000000000..adfa697c5
--- /dev/null
+++ b/src/main/java/org/traccar/helper/Checksum.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2012 - 2018 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.helper;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.zip.CRC32;
+
+public final class Checksum {
+
+ private Checksum() {
+ }
+
+ public static class Algorithm {
+
+ private int poly;
+ private int init;
+ private boolean refIn;
+ private boolean refOut;
+ private int xorOut;
+ private int[] table;
+
+ public Algorithm(int bits, int poly, int init, boolean refIn, boolean refOut, int xorOut) {
+ this.poly = poly;
+ this.init = init;
+ this.refIn = refIn;
+ this.refOut = refOut;
+ this.xorOut = xorOut;
+ this.table = bits == 8 ? initTable8() : initTable16();
+ }
+
+ private int[] initTable8() {
+ int[] table = new int[256];
+ int crc;
+ for (int i = 0; i < 256; i++) {
+ crc = i;
+ for (int j = 0; j < 8; j++) {
+ boolean bit = (crc & 0x80) != 0;
+ crc <<= 1;
+ if (bit) {
+ crc ^= poly;
+ }
+ }
+ table[i] = crc & 0xFF;
+ }
+ return table;
+ }
+
+ private int[] initTable16() {
+ int[] table = new int[256];
+ int crc;
+ for (int i = 0; i < 256; i++) {
+ crc = i << 8;
+ for (int j = 0; j < 8; j++) {
+ boolean bit = (crc & 0x8000) != 0;
+ crc <<= 1;
+ if (bit) {
+ crc ^= poly;
+ }
+ }
+ table[i] = crc & 0xFFFF;
+ }
+ return table;
+ }
+
+ }
+
+ private static int reverse(int value, int bits) {
+ int result = 0;
+ for (int i = 0; i < bits; i++) {
+ result = (result << 1) | (value & 1);
+ value >>= 1;
+ }
+ return result;
+ }
+
+ public static int crc8(Algorithm algorithm, ByteBuffer buf) {
+ int crc = algorithm.init;
+ while (buf.hasRemaining()) {
+ int b = buf.get() & 0xFF;
+ if (algorithm.refIn) {
+ b = reverse(b, 8);
+ }
+ crc = algorithm.table[(crc & 0xFF) ^ b];
+ }
+ if (algorithm.refOut) {
+ crc = reverse(crc, 8);
+ }
+ return (crc ^ algorithm.xorOut) & 0xFF;
+ }
+
+ public static int crc16(Algorithm algorithm, ByteBuffer buf) {
+ int crc = algorithm.init;
+ while (buf.hasRemaining()) {
+ int b = buf.get() & 0xFF;
+ if (algorithm.refIn) {
+ b = reverse(b, 8);
+ }
+ crc = (crc << 8) ^ algorithm.table[((crc >> 8) & 0xFF) ^ b];
+ }
+ if (algorithm.refOut) {
+ crc = reverse(crc, 16);
+ }
+ return (crc ^ algorithm.xorOut) & 0xFFFF;
+ }
+
+ public static final Algorithm CRC8_EGTS = new Algorithm(8, 0x31, 0xFF, false, false, 0x00);
+ public static final Algorithm CRC8_ROHC = new Algorithm(8, 0x07, 0xFF, true, true, 0x00);
+
+ public static final Algorithm CRC16_IBM = new Algorithm(16, 0x8005, 0x0000, true, true, 0x0000);
+ public static final Algorithm CRC16_X25 = new Algorithm(16, 0x1021, 0xFFFF, true, true, 0xFFFF);
+ public static final Algorithm CRC16_MODBUS = new Algorithm(16, 0x8005, 0xFFFF, true, true, 0x0000);
+ public static final Algorithm CRC16_CCITT_FALSE = new Algorithm(16, 0x1021, 0xFFFF, false, false, 0x0000);
+ public static final Algorithm CRC16_KERMIT = new Algorithm(16, 0x1021, 0x0000, true, true, 0x0000);
+ public static final Algorithm CRC16_XMODEM = new Algorithm(16, 0x1021, 0x0000, false, false, 0x0000);
+
+ public static int crc32(ByteBuffer buf) {
+ CRC32 checksum = new CRC32();
+ while (buf.hasRemaining()) {
+ checksum.update(buf.get());
+ }
+ return (int) checksum.getValue();
+ }
+
+ public static int xor(ByteBuffer buf) {
+ int checksum = 0;
+ while (buf.hasRemaining()) {
+ checksum ^= buf.get();
+ }
+ return checksum;
+ }
+
+ public static int xor(String string) {
+ byte checksum = 0;
+ for (byte b : string.getBytes(StandardCharsets.US_ASCII)) {
+ checksum ^= b;
+ }
+ return checksum;
+ }
+
+ public static String nmea(String msg) {
+ int checksum = 0;
+ byte[] bytes = msg.getBytes(StandardCharsets.US_ASCII);
+ for (int i = 1; i < bytes.length; i++) {
+ checksum ^= bytes[i];
+ }
+ return String.format("*%02x", checksum).toUpperCase();
+ }
+
+ public static int sum(ByteBuffer buf) {
+ byte checksum = 0;
+ while (buf.hasRemaining()) {
+ checksum += buf.get();
+ }
+ return checksum;
+ }
+
+ public static String sum(String msg) {
+ byte checksum = 0;
+ for (byte b : msg.getBytes(StandardCharsets.US_ASCII)) {
+ checksum += b;
+ }
+ return String.format("%02X", checksum).toUpperCase();
+ }
+
+ public static long luhn(long imei) {
+ long checksum = 0;
+ long remain = imei;
+
+ for (int i = 0; remain != 0; i++) {
+ long digit = remain % 10;
+
+ if (i % 2 == 0) {
+ digit *= 2;
+ if (digit >= 10) {
+ digit = 1 + (digit % 10);
+ }
+ }
+
+ checksum += digit;
+ remain /= 10;
+ }
+
+ return (10 - (checksum % 10)) % 10;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/DataConverter.java b/src/main/java/org/traccar/helper/DataConverter.java
new file mode 100644
index 000000000..7abd4ae93
--- /dev/null
+++ b/src/main/java/org/traccar/helper/DataConverter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 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.helper;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+
+public final class DataConverter {
+
+ private DataConverter() {
+ }
+
+ public static byte[] parseHex(String string) {
+ try {
+ return Hex.decodeHex(string);
+ } catch (DecoderException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String printHex(byte[] data) {
+ return Hex.encodeHexString(data);
+ }
+
+ public static byte[] parseBase64(String string) {
+ return Base64.decodeBase64(string);
+ }
+
+ public static String printBase64(byte[] data) {
+ return Base64.encodeBase64String(data);
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/DateBuilder.java b/src/main/java/org/traccar/helper/DateBuilder.java
new file mode 100644
index 000000000..6e1b779f0
--- /dev/null
+++ b/src/main/java/org/traccar/helper/DateBuilder.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2015 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.helper;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class DateBuilder {
+
+ private Calendar calendar;
+
+ public DateBuilder() {
+ this(TimeZone.getTimeZone("UTC"));
+ }
+
+ public DateBuilder(Date time) {
+ this(time, TimeZone.getTimeZone("UTC"));
+ }
+
+ public DateBuilder(TimeZone timeZone) {
+ this(new Date(0), timeZone);
+ }
+
+ public DateBuilder(Date time, TimeZone timeZone) {
+ calendar = Calendar.getInstance(timeZone);
+ calendar.clear();
+ calendar.setTimeInMillis(time.getTime());
+ }
+
+ public DateBuilder setYear(int year) {
+ if (year < 100) {
+ year += 2000;
+ }
+ calendar.set(Calendar.YEAR, year);
+ return this;
+ }
+
+ public DateBuilder setMonth(int month) {
+ calendar.set(Calendar.MONTH, month - 1);
+ return this;
+ }
+
+ public DateBuilder setDay(int day) {
+ calendar.set(Calendar.DAY_OF_MONTH, day);
+ return this;
+ }
+
+ public DateBuilder setDate(int year, int month, int day) {
+ return setYear(year).setMonth(month).setDay(day);
+ }
+
+ public DateBuilder setDateReverse(int day, int month, int year) {
+ return setDate(year, month, day);
+ }
+
+ public DateBuilder setCurrentDate() {
+ Calendar now = Calendar.getInstance(calendar.getTimeZone());
+ return setYear(now.get(Calendar.YEAR)).setMonth(now.get(Calendar.MONTH)).setDay(now.get(Calendar.DAY_OF_MONTH));
+ }
+
+ public DateBuilder setHour(int hour) {
+ calendar.set(Calendar.HOUR_OF_DAY, hour);
+ return this;
+ }
+
+ public DateBuilder setMinute(int minute) {
+ calendar.set(Calendar.MINUTE, minute);
+ return this;
+ }
+
+ public DateBuilder addMinute(int minute) {
+ calendar.add(Calendar.MINUTE, minute);
+ return this;
+ }
+
+ public DateBuilder setSecond(int second) {
+ calendar.set(Calendar.SECOND, second);
+ return this;
+ }
+
+ public DateBuilder addSeconds(long seconds) {
+ calendar.setTimeInMillis(calendar.getTimeInMillis() + seconds * 1000);
+ return this;
+ }
+
+ public DateBuilder setMillis(int millis) {
+ calendar.set(Calendar.MILLISECOND, millis);
+ return this;
+ }
+
+ public DateBuilder addMillis(long millis) {
+ calendar.setTimeInMillis(calendar.getTimeInMillis() + millis);
+ return this;
+ }
+
+ public DateBuilder setTime(int hour, int minute, int second) {
+ return setHour(hour).setMinute(minute).setSecond(second);
+ }
+
+ public DateBuilder setTimeReverse(int second, int minute, int hour) {
+ return setHour(hour).setMinute(minute).setSecond(second);
+ }
+
+ public DateBuilder setTime(int hour, int minute, int second, int millis) {
+ return setHour(hour).setMinute(minute).setSecond(second).setMillis(millis);
+ }
+
+ public Date getDate() {
+ return calendar.getTime();
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/DateUtil.java b/src/main/java/org/traccar/helper/DateUtil.java
new file mode 100644
index 000000000..20a483e3c
--- /dev/null
+++ b/src/main/java/org/traccar/helper/DateUtil.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 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.helper;
+
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Calendar;
+import java.util.Date;
+
+public final class DateUtil {
+
+ private DateUtil() {
+ }
+
+ public static Date correctDay(Date guess) {
+ return correctDate(new Date(), guess, Calendar.DAY_OF_MONTH);
+ }
+
+ public static Date correctYear(Date guess) {
+ return correctDate(new Date(), guess, Calendar.YEAR);
+ }
+
+ public static Date correctDate(Date now, Date guess, int field) {
+
+ if (guess.getTime() > now.getTime()) {
+ Date previous = dateAdd(guess, field, -1);
+ if (now.getTime() - previous.getTime() < guess.getTime() - now.getTime()) {
+ return previous;
+ }
+ } else if (guess.getTime() < now.getTime()) {
+ Date next = dateAdd(guess, field, 1);
+ if (next.getTime() - now.getTime() < now.getTime() - guess.getTime()) {
+ return next;
+ }
+ }
+
+ return guess;
+ }
+
+ private static Date dateAdd(Date guess, int field, int amount) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(guess);
+ calendar.add(field, amount);
+ return calendar.getTime();
+ }
+
+ public static Date parseDate(String value) {
+ return Date.from(Instant.from(DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(value)));
+ }
+
+ public static String formatDate(Date date) {
+ return formatDate(date, true);
+ }
+
+ public static String formatDate(Date date, boolean zoned) {
+ if (zoned) {
+ return DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.systemDefault()).format(date.toInstant());
+ } else {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/DistanceCalculator.java b/src/main/java/org/traccar/helper/DistanceCalculator.java
new file mode 100644
index 000000000..88d4ef8a4
--- /dev/null
+++ b/src/main/java/org/traccar/helper/DistanceCalculator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014 - 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@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.helper;
+
+public final class DistanceCalculator {
+
+ private DistanceCalculator() {
+ }
+
+ private static final double EQUATORIAL_EARTH_RADIUS = 6378.1370;
+ private static final double DEG_TO_RAD = Math.PI / 180;
+
+ public static double distance(double lat1, double lon1, double lat2, double lon2) {
+ double dlong = (lon2 - lon1) * DEG_TO_RAD;
+ double dlat = (lat2 - lat1) * DEG_TO_RAD;
+ double a = Math.pow(Math.sin(dlat / 2), 2)
+ + Math.cos(lat1 * DEG_TO_RAD) * Math.cos(lat2 * DEG_TO_RAD) * Math.pow(Math.sin(dlong / 2), 2);
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ double d = EQUATORIAL_EARTH_RADIUS * c;
+ return d * 1000;
+ }
+
+ public static double distanceToLine(
+ double pointLat, double pointLon, double lat1, double lon1, double lat2, double lon2) {
+ double d0 = distance(pointLat, pointLon, lat1, lon1);
+ double d1 = distance(lat1, lon1, lat2, lon2);
+ double d2 = distance(lat2, lon2, pointLat, pointLon);
+ if (Math.pow(d0, 2) > Math.pow(d1, 2) + Math.pow(d2, 2)) {
+ return d2;
+ }
+ if (Math.pow(d2, 2) > Math.pow(d1, 2) + Math.pow(d0, 2)) {
+ return d0;
+ }
+ double halfP = (d0 + d1 + d2) * 0.5;
+ double area = Math.sqrt(halfP * (halfP - d0) * (halfP - d1) * (halfP - d2));
+ return 2 * area / d1;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/Hashing.java b/src/main/java/org/traccar/helper/Hashing.java
new file mode 100644
index 000000000..e91310eda
--- /dev/null
+++ b/src/main/java/org/traccar/helper/Hashing.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2015 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.helper;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+
+public final class Hashing {
+
+ public static final int ITERATIONS = 1000;
+ public static final int SALT_SIZE = 24;
+ public static final int HASH_SIZE = 24;
+
+ private static SecretKeyFactory factory;
+ static {
+ try {
+ factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static class HashingResult {
+
+ private final String hash;
+ private final String salt;
+
+ public HashingResult(String hash, String salt) {
+ this.hash = hash;
+ this.salt = salt;
+ }
+
+ public String getHash() {
+ return hash;
+ }
+
+ public String getSalt() {
+ return salt;
+ }
+ }
+
+ private Hashing() {
+ }
+
+ private static byte[] function(char[] password, byte[] salt) {
+ try {
+ PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, HASH_SIZE * Byte.SIZE);
+ return factory.generateSecret(spec).getEncoded();
+ } catch (InvalidKeySpecException e) {
+ throw new SecurityException(e);
+ }
+ }
+
+ private static final SecureRandom RANDOM = new SecureRandom();
+
+ public static HashingResult createHash(String password) {
+ byte[] salt = new byte[SALT_SIZE];
+ RANDOM.nextBytes(salt);
+ byte[] hash = function(password.toCharArray(), salt);
+ return new HashingResult(
+ DataConverter.printHex(hash),
+ DataConverter.printHex(salt));
+ }
+
+ public static boolean validatePassword(String password, String hashHex, String saltHex) {
+ byte[] hash = DataConverter.parseHex(hashHex);
+ byte[] salt = DataConverter.parseHex(saltHex);
+ return slowEquals(hash, function(password.toCharArray(), salt));
+ }
+
+ /**
+ * Compares two byte arrays in length-constant time. This comparison method
+ * is used so that password hashes cannot be extracted from an on-line
+ * system using a timing attack and then attacked off-line.
+ */
+ private static boolean slowEquals(byte[] a, byte[] b) {
+ int diff = a.length ^ b.length;
+ for (int i = 0; i < a.length && i < b.length; i++) {
+ diff |= a[i] ^ b[i];
+ }
+ return diff == 0;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/LocationTree.java b/src/main/java/org/traccar/helper/LocationTree.java
new file mode 100644
index 000000000..3aff3ce33
--- /dev/null
+++ b/src/main/java/org/traccar/helper/LocationTree.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016 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.helper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class LocationTree {
+
+ public static class Item {
+
+ private Item left, right;
+ private float x, y;
+ private String data;
+
+ public Item(float x, float y) {
+ this(x, y, null);
+ }
+
+ public Item(float x, float y, String data) {
+ this.x = x;
+ this.y = y;
+ this.data = data;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ private float squaredDistance(Item item) {
+ return (x - item.x) * (x - item.x) + (y - item.y) * (y - item.y);
+ }
+
+ private float axisSquaredDistance(Item item, int axis) {
+ if (axis == 0) {
+ return (x - item.x) * (x - item.x);
+ } else {
+ return (y - item.y) * (y - item.y);
+ }
+ }
+
+ }
+
+ private Item root;
+
+ private ArrayList<Comparator<Item>> comparators = new ArrayList<>();
+
+ public LocationTree(List<Item> items) {
+ comparators.add(new Comparator<Item>() {
+ @Override
+ public int compare(Item o1, Item o2) {
+ return Float.compare(o1.x, o2.x);
+ }
+ });
+ comparators.add(new Comparator<Item>() {
+ @Override
+ public int compare(Item o1, Item o2) {
+ return Float.compare(o1.y, o2.y);
+ }
+ });
+ root = createTree(items, 0);
+ }
+
+ private Item createTree(List<Item> items, int depth) {
+ if (items.isEmpty()) {
+ return null;
+ }
+ Collections.sort(items, comparators.get(depth % 2));
+ int currentIndex = items.size() / 2;
+ Item median = items.get(currentIndex);
+ median.left = createTree(new ArrayList<>(items.subList(0, currentIndex)), depth + 1);
+ median.right = createTree(new ArrayList<>(items.subList(currentIndex + 1, items.size())), depth + 1);
+ return median;
+ }
+
+ public Item findNearest(Item search) {
+ return findNearest(root, search, 0);
+ }
+
+ private Item findNearest(Item current, Item search, int depth) {
+ int direction = comparators.get(depth % 2).compare(search, current);
+
+ Item next, other;
+ if (direction < 0) {
+ next = current.left;
+ other = current.right;
+ } else {
+ next = current.right;
+ other = current.left;
+ }
+
+ Item best = current;
+ if (next != null) {
+ best = findNearest(next, search, depth + 1);
+ }
+
+ if (current.squaredDistance(search) < best.squaredDistance(search)) {
+ best = current;
+ }
+ if (other != null && current.axisSquaredDistance(search, depth % 2) < best.squaredDistance(search)) {
+ Item possibleBest = findNearest(other, search, depth + 1);
+ if (possibleBest.squaredDistance(search) < best.squaredDistance(search)) {
+ best = possibleBest;
+ }
+ }
+
+ return best;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/Log.java b/src/main/java/org/traccar/helper/Log.java
new file mode 100644
index 000000000..f328e8ce9
--- /dev/null
+++ b/src/main/java/org/traccar/helper/Log.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2012 - 2019 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.helper;
+
+import org.traccar.config.Config;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+public final class Log {
+
+ private Log() {
+ }
+
+ private static final String STACK_PACKAGE = "org.traccar";
+ private static final int STACK_LIMIT = 3;
+
+ private static class RollingFileHandler extends Handler {
+
+ private String name;
+ private String suffix;
+ private Writer writer;
+ private boolean rotate;
+
+ RollingFileHandler(String name, boolean rotate) {
+ this.name = name;
+ this.rotate = rotate;
+ }
+
+ @Override
+ public synchronized void publish(LogRecord record) {
+ if (isLoggable(record)) {
+ try {
+ String suffix = "";
+ if (rotate) {
+ suffix = new SimpleDateFormat("yyyyMMdd").format(new Date(record.getMillis()));
+ if (writer != null && !suffix.equals(this.suffix)) {
+ writer.close();
+ writer = null;
+ if (!new File(name).renameTo(new File(name + "." + this.suffix))) {
+ throw new RuntimeException("Log file renaming failed");
+ }
+ }
+ }
+ if (writer == null) {
+ this.suffix = suffix;
+ writer = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(name, true), StandardCharsets.UTF_8));
+ }
+ writer.write(getFormatter().format(record));
+ writer.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public synchronized void flush() {
+ if (writer != null) {
+ try {
+ writer.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public synchronized void close() throws SecurityException {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ }
+
+ public static class LogFormatter extends Formatter {
+
+ private boolean fullStackTraces;
+
+ LogFormatter(boolean fullStackTraces) {
+ this.fullStackTraces = fullStackTraces;
+ }
+
+ private static String formatLevel(Level level) {
+ switch (level.getName()) {
+ case "FINEST":
+ return "TRACE";
+ case "FINER":
+ case "FINE":
+ case "CONFIG":
+ return "DEBUG";
+ case "INFO":
+ return "INFO";
+ case "WARNING":
+ return "WARN";
+ case "SEVERE":
+ default:
+ return "ERROR";
+ }
+ }
+
+ @Override
+ public String format(LogRecord record) {
+ StringBuilder message = new StringBuilder();
+
+ if (record.getMessage() != null) {
+ message.append(record.getMessage());
+ }
+
+ if (record.getThrown() != null) {
+ if (message.length() > 0) {
+ message.append(" - ");
+ }
+ if (fullStackTraces) {
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(stringWriter);
+ record.getThrown().printStackTrace(printWriter);
+ message.append(System.lineSeparator()).append(stringWriter.toString());
+ } else {
+ message.append(exceptionStack(record.getThrown()));
+ }
+ }
+
+ return String.format("%1$tF %1$tT %2$5s: %3$s%n",
+ new Date(record.getMillis()), formatLevel(record.getLevel()), message.toString());
+ }
+
+ }
+
+ public static void setupDefaultLogger() {
+ String path = null;
+ URL url = ClassLoader.getSystemClassLoader().getResource(".");
+ if (url != null) {
+ File jarPath = new File(url.getPath());
+ File logsPath = new File(jarPath, "logs");
+ if (!logsPath.exists() || !logsPath.isDirectory()) {
+ logsPath = jarPath;
+ }
+ path = new File(logsPath, "tracker-server.log").getPath();
+ }
+ setupLogger(path == null, path, Level.WARNING.getName(), false, true);
+ }
+
+ public static void setupLogger(Config config) {
+ setupLogger(
+ config.getBoolean("logger.console"),
+ config.getString("logger.file"),
+ config.getString("logger.level"),
+ config.getBoolean("logger.fullStackTraces"),
+ config.getBoolean("logger.rotate"));
+ }
+
+ private static void setupLogger(
+ boolean console, String file, String levelString, boolean fullStackTraces, boolean rotate) {
+
+ Logger rootLogger = Logger.getLogger("");
+ for (Handler handler : rootLogger.getHandlers()) {
+ rootLogger.removeHandler(handler);
+ }
+
+ Handler handler;
+ if (console) {
+ handler = new ConsoleHandler();
+ } else {
+ handler = new RollingFileHandler(file, rotate);
+ }
+
+ handler.setFormatter(new LogFormatter(fullStackTraces));
+
+ Level level = Level.parse(levelString.toUpperCase());
+ rootLogger.setLevel(level);
+ handler.setLevel(level);
+ handler.setFilter(record -> record != null && !record.getLoggerName().startsWith("sun"));
+
+ rootLogger.addHandler(handler);
+ }
+
+ public static String exceptionStack(Throwable exception) {
+ StringBuilder s = new StringBuilder();
+ String exceptionMsg = exception.getMessage();
+ if (exceptionMsg != null) {
+ s.append(exceptionMsg);
+ s.append(" - ");
+ }
+ s.append(exception.getClass().getSimpleName());
+ StackTraceElement[] stack = exception.getStackTrace();
+
+ if (stack.length > 0) {
+ int count = STACK_LIMIT;
+ boolean first = true;
+ boolean skip = false;
+ String file = "";
+ s.append(" (");
+ for (StackTraceElement element : stack) {
+ if (count > 0 && element.getClassName().startsWith(STACK_PACKAGE)) {
+ if (!first) {
+ s.append(" < ");
+ } else {
+ first = false;
+ }
+
+ if (skip) {
+ s.append("... < ");
+ skip = false;
+ }
+
+ if (file.equals(element.getFileName())) {
+ s.append("*");
+ } else {
+ file = element.getFileName();
+ s.append(file, 0, file.length() - 5); // remove ".java"
+ count -= 1;
+ }
+ s.append(":").append(element.getLineNumber());
+ } else {
+ skip = true;
+ }
+ }
+ if (skip) {
+ if (!first) {
+ s.append(" < ");
+ }
+ s.append("...");
+ }
+ s.append(")");
+ }
+ return s.toString();
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/LogAction.java b/src/main/java/org/traccar/helper/LogAction.java
new file mode 100644
index 000000000..db13337b8
--- /dev/null
+++ b/src/main/java/org/traccar/helper/LogAction.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@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.helper;
+
+import java.beans.Introspector;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.model.BaseModel;
+
+public final class LogAction {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(LogAction.class);
+
+ private LogAction() {
+ }
+
+ private static final String ACTION_CREATE = "create";
+ private static final String ACTION_EDIT = "edit";
+ private static final String ACTION_REMOVE = "remove";
+
+ private static final String ACTION_LINK = "link";
+ private static final String ACTION_UNLINK = "unlink";
+
+ private static final String ACTION_LOGIN = "login";
+ private static final String ACTION_LOGOUT = "logout";
+
+ private static final String ACTION_DEVICE_ACCUMULATORS = "resetDeviceAccumulators";
+
+ private static final String PATTERN_OBJECT = "user: %d, action: %s, object: %s, id: %d";
+ private static final String PATTERN_LINK = "user: %d, action: %s, owner: %s, id: %d, property: %s, id: %d";
+ private static final String PATTERN_LOGIN = "user: %d, action: %s";
+ private static final String PATTERN_DEVICE_ACCUMULATORS = "user: %d, action: %s, deviceId: %d";
+
+ public static void create(long userId, BaseModel object) {
+ logObjectAction(ACTION_CREATE, userId, object.getClass(), object.getId());
+ }
+
+ public static void edit(long userId, BaseModel object) {
+ logObjectAction(ACTION_EDIT, userId, object.getClass(), object.getId());
+ }
+
+ public static void remove(long userId, Class<?> clazz, long objectId) {
+ logObjectAction(ACTION_REMOVE, userId, clazz, objectId);
+ }
+
+ public static void link(long userId, Class<?> owner, long ownerId, Class<?> property, long propertyId) {
+ logLinkAction(ACTION_LINK, userId, owner, ownerId, property, propertyId);
+ }
+
+ public static void unlink(long userId, Class<?> owner, long ownerId, Class<?> property, long propertyId) {
+ logLinkAction(ACTION_UNLINK, userId, owner, ownerId, property, propertyId);
+ }
+
+ public static void login(long userId) {
+ logLoginAction(ACTION_LOGIN, userId);
+ }
+
+ public static void logout(long userId) {
+ logLoginAction(ACTION_LOGOUT, userId);
+ }
+
+ public static void resetDeviceAccumulators(long userId, long deviceId) {
+ LOGGER.info(String.format(
+ PATTERN_DEVICE_ACCUMULATORS, userId, ACTION_DEVICE_ACCUMULATORS, deviceId));
+ }
+
+ private static void logObjectAction(String action, long userId, Class<?> clazz, long objectId) {
+ LOGGER.info(String.format(
+ PATTERN_OBJECT, userId, action, Introspector.decapitalize(clazz.getSimpleName()), objectId));
+ }
+
+ private static void logLinkAction(String action, long userId,
+ Class<?> owner, long ownerId, Class<?> property, long propertyId) {
+ LOGGER.info(String.format(
+ PATTERN_LINK, userId, action,
+ Introspector.decapitalize(owner.getSimpleName()), ownerId,
+ Introspector.decapitalize(property.getSimpleName()), propertyId));
+ }
+
+ private static void logLoginAction(String action, long userId) {
+ LOGGER.info(String.format(PATTERN_LOGIN, userId, action));
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/ObdDecoder.java b/src/main/java/org/traccar/helper/ObdDecoder.java
new file mode 100644
index 000000000..1bdcce352
--- /dev/null
+++ b/src/main/java/org/traccar/helper/ObdDecoder.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2015 - 2018 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.helper;
+
+import org.traccar.model.Position;
+
+import java.util.AbstractMap;
+import java.util.Map;
+
+public final class ObdDecoder {
+
+ private ObdDecoder() {
+ }
+
+ private static final int MODE_CURRENT = 0x01;
+ private static final int MODE_FREEZE_FRAME = 0x02;
+ private static final int MODE_CODES = 0x03;
+
+ public static Map.Entry<String, Object> decode(int mode, String value) {
+ switch (mode) {
+ case MODE_CURRENT:
+ case MODE_FREEZE_FRAME:
+ return decodeData(
+ Integer.parseInt(value.substring(0, 2), 16),
+ Integer.parseInt(value.substring(2), 16), true);
+ case MODE_CODES:
+ return decodeCodes(value);
+ default:
+ return null;
+ }
+ }
+
+ private static Map.Entry<String, Object> createEntry(String key, Object value) {
+ return new AbstractMap.SimpleEntry<>(key, value);
+ }
+
+ public static Map.Entry<String, Object> decodeCodes(String value) {
+ StringBuilder codes = new StringBuilder();
+ for (int i = 0; i < value.length() / 4; i++) {
+ int numValue = Integer.parseInt(value.substring(i * 4, (i + 1) * 4), 16);
+ codes.append(' ');
+ switch (numValue >> 14) {
+ case 1:
+ codes.append('C');
+ break;
+ case 2:
+ codes.append('B');
+ break;
+ case 3:
+ codes.append('U');
+ break;
+ default:
+ codes.append('P');
+ break;
+ }
+ codes.append(String.format("%04X", numValue & 0x3FFF));
+ }
+ if (codes.length() > 0) {
+ return createEntry(Position.KEY_DTCS, codes.toString().trim());
+ } else {
+ return null;
+ }
+ }
+
+ public static Map.Entry<String, Object> decodeData(int pid, int value, boolean convert) {
+ switch (pid) {
+ case 0x04:
+ return createEntry(Position.KEY_ENGINE_LOAD, convert ? value * 100 / 255 : value);
+ case 0x05:
+ return createEntry(Position.KEY_COOLANT_TEMP, convert ? value - 40 : value);
+ case 0x0B:
+ return createEntry("mapIntake", value);
+ case 0x0C:
+ return createEntry(Position.KEY_RPM, convert ? value / 4 : value);
+ case 0x0D:
+ return createEntry(Position.KEY_OBD_SPEED, value);
+ case 0x0F:
+ return createEntry("intakeTemp", convert ? value - 40 : value);
+ case 0x11:
+ return createEntry(Position.KEY_THROTTLE, convert ? value * 100 / 255 : value);
+ case 0x21:
+ return createEntry("milDistance", value);
+ case 0x2F:
+ return createEntry(Position.KEY_FUEL_LEVEL, convert ? value * 100 / 255 : value);
+ case 0x31:
+ return createEntry("clearedDistance", value);
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/Parser.java b/src/main/java/org/traccar/helper/Parser.java
new file mode 100644
index 000000000..1471ec237
--- /dev/null
+++ b/src/main/java/org/traccar/helper/Parser.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2015 - 2017 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.helper;
+
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Parser {
+
+ private int position;
+ private final Matcher matcher;
+
+ public Parser(Pattern pattern, String input) {
+ matcher = pattern.matcher(input);
+ }
+
+ public boolean matches() {
+ position = 1;
+ return matcher.matches();
+ }
+
+ public boolean find() {
+ position = 1;
+ return matcher.find();
+ }
+
+ public void skip(int number) {
+ position += number;
+ }
+
+ public boolean hasNext() {
+ return hasNext(1);
+ }
+
+ public boolean hasNext(int number) {
+ String value = matcher.group(position);
+ if (value != null && !value.isEmpty()) {
+ return true;
+ } else {
+ position += number;
+ return false;
+ }
+ }
+
+ public String next() {
+ return matcher.group(position++);
+ }
+
+ public Integer nextInt() {
+ if (hasNext()) {
+ return Integer.parseInt(next());
+ } else {
+ return null;
+ }
+ }
+
+ public int nextInt(int defaultValue) {
+ if (hasNext()) {
+ return Integer.parseInt(next());
+ } else {
+ return defaultValue;
+ }
+ }
+
+ public Integer nextHexInt() {
+ if (hasNext()) {
+ return Integer.parseInt(next(), 16);
+ } else {
+ return null;
+ }
+ }
+
+ public int nextHexInt(int defaultValue) {
+ if (hasNext()) {
+ return Integer.parseInt(next(), 16);
+ } else {
+ return defaultValue;
+ }
+ }
+
+ public Integer nextBinInt() {
+ if (hasNext()) {
+ return Integer.parseInt(next(), 2);
+ } else {
+ return null;
+ }
+ }
+
+ public int nextBinInt(int defaultValue) {
+ if (hasNext()) {
+ return Integer.parseInt(next(), 2);
+ } else {
+ return defaultValue;
+ }
+ }
+
+ public Long nextLong() {
+ if (hasNext()) {
+ return Long.parseLong(next());
+ } else {
+ return null;
+ }
+ }
+
+ public Long nextHexLong() {
+ if (hasNext()) {
+ return Long.parseLong(next(), 16);
+ } else {
+ return null;
+ }
+ }
+
+ public long nextLong(long defaultValue) {
+ return nextLong(10, defaultValue);
+ }
+
+ public long nextLong(int radix, long defaultValue) {
+ if (hasNext()) {
+ return Long.parseLong(next(), radix);
+ } else {
+ return defaultValue;
+ }
+ }
+
+ public Double nextDouble() {
+ if (hasNext()) {
+ return Double.parseDouble(next());
+ } else {
+ return null;
+ }
+ }
+
+ public double nextDouble(double defaultValue) {
+ if (hasNext()) {
+ return Double.parseDouble(next());
+ } else {
+ return defaultValue;
+ }
+ }
+
+ public enum CoordinateFormat {
+ DEG_DEG,
+ DEG_HEM,
+ DEG_MIN_MIN,
+ DEG_MIN_HEM,
+ DEG_MIN_MIN_HEM,
+ HEM_DEG_MIN_MIN,
+ HEM_DEG,
+ HEM_DEG_MIN,
+ HEM_DEG_MIN_HEM
+ }
+
+ public double nextCoordinate(CoordinateFormat format) {
+ double coordinate;
+ String hemisphere = null;
+
+ switch (format) {
+ case DEG_DEG:
+ coordinate = Double.parseDouble(next() + '.' + next());
+ break;
+ case DEG_HEM:
+ coordinate = nextDouble(0);
+ hemisphere = next();
+ break;
+ case DEG_MIN_MIN:
+ coordinate = nextInt(0);
+ coordinate += Double.parseDouble(next() + '.' + next()) / 60;
+ break;
+ case DEG_MIN_MIN_HEM:
+ coordinate = nextInt(0);
+ coordinate += Double.parseDouble(next() + '.' + next()) / 60;
+ hemisphere = next();
+ break;
+ case HEM_DEG:
+ hemisphere = next();
+ coordinate = nextDouble(0);
+ break;
+ case HEM_DEG_MIN:
+ hemisphere = next();
+ coordinate = nextInt(0);
+ coordinate += nextDouble(0) / 60;
+ break;
+ case HEM_DEG_MIN_HEM:
+ hemisphere = next();
+ coordinate = nextInt(0);
+ coordinate += nextDouble(0) / 60;
+ if (hasNext()) {
+ hemisphere = next();
+ }
+ break;
+ case HEM_DEG_MIN_MIN:
+ hemisphere = next();
+ coordinate = nextInt(0);
+ coordinate += Double.parseDouble(next() + '.' + next()) / 60;
+ break;
+ case DEG_MIN_HEM:
+ default:
+ coordinate = nextInt(0);
+ coordinate += nextDouble(0) / 60;
+ hemisphere = next();
+ break;
+ }
+
+ if (hemisphere != null && (hemisphere.equals("S") || hemisphere.equals("W") || hemisphere.equals("-"))) {
+ coordinate = -Math.abs(coordinate);
+ }
+
+ return coordinate;
+ }
+
+ public double nextCoordinate() {
+ return nextCoordinate(CoordinateFormat.DEG_MIN_HEM);
+ }
+
+ public enum DateTimeFormat {
+ HMS,
+ SMH,
+ HMS_YMD,
+ HMS_DMY,
+ SMH_YMD,
+ SMH_DMY,
+ DMY_HMS,
+ DMY_HMSS,
+ YMD_HMS,
+ YMD_HMSS,
+ }
+
+ public Date nextDateTime(DateTimeFormat format, String timeZone) {
+ int year = 0, month = 0, day = 0;
+ int hour = 0, minute = 0, second = 0, millisecond = 0;
+
+ switch (format) {
+ case HMS:
+ hour = nextInt(0);
+ minute = nextInt(0);
+ second = nextInt(0);
+ break;
+ case SMH:
+ second = nextInt(0);
+ minute = nextInt(0);
+ hour = nextInt(0);
+ break;
+ case HMS_YMD:
+ hour = nextInt(0);
+ minute = nextInt(0);
+ second = nextInt(0);
+ year = nextInt(0);
+ month = nextInt(0);
+ day = nextInt(0);
+ break;
+ case HMS_DMY:
+ hour = nextInt(0);
+ minute = nextInt(0);
+ second = nextInt(0);
+ day = nextInt(0);
+ month = nextInt(0);
+ year = nextInt(0);
+ break;
+ case SMH_YMD:
+ second = nextInt(0);
+ minute = nextInt(0);
+ hour = nextInt(0);
+ year = nextInt(0);
+ month = nextInt(0);
+ day = nextInt(0);
+ break;
+ case SMH_DMY:
+ second = nextInt(0);
+ minute = nextInt(0);
+ hour = nextInt(0);
+ day = nextInt(0);
+ month = nextInt(0);
+ year = nextInt(0);
+ break;
+ case DMY_HMS:
+ case DMY_HMSS:
+ day = nextInt(0);
+ month = nextInt(0);
+ year = nextInt(0);
+ hour = nextInt(0);
+ minute = nextInt(0);
+ second = nextInt(0);
+ break;
+ case YMD_HMS:
+ case YMD_HMSS:
+ default:
+ year = nextInt(0);
+ month = nextInt(0);
+ day = nextInt(0);
+ hour = nextInt(0);
+ minute = nextInt(0);
+ second = nextInt(0);
+ break;
+ }
+
+ if (format == DateTimeFormat.YMD_HMSS || format == DateTimeFormat.DMY_HMSS) {
+ millisecond = nextInt(0); // (ddd)
+ }
+
+ if (year >= 0 && year < 100) {
+ year += 2000;
+ }
+
+ DateBuilder dateBuilder;
+ if (format != DateTimeFormat.HMS && format != DateTimeFormat.SMH) {
+ if (timeZone != null) {
+ dateBuilder = new DateBuilder(TimeZone.getTimeZone(timeZone));
+ } else {
+ dateBuilder = new DateBuilder();
+ }
+ dateBuilder.setDate(year, month, day);
+ } else {
+ if (timeZone != null) {
+ dateBuilder = new DateBuilder(new Date(), TimeZone.getTimeZone(timeZone));
+ } else {
+ dateBuilder = new DateBuilder(new Date());
+ }
+ }
+
+ dateBuilder.setTime(hour, minute, second, millisecond);
+
+ return dateBuilder.getDate();
+ }
+
+ public Date nextDateTime(DateTimeFormat format) {
+ return nextDateTime(format, null);
+ }
+
+ public Date nextDateTime() {
+ return nextDateTime(DateTimeFormat.YMD_HMS, null);
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/PatternBuilder.java b/src/main/java/org/traccar/helper/PatternBuilder.java
new file mode 100644
index 000000000..5c4638189
--- /dev/null
+++ b/src/main/java/org/traccar/helper/PatternBuilder.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2015 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.helper;
+
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+public class PatternBuilder {
+
+ private final ArrayList<String> fragments = new ArrayList<>();
+
+ public PatternBuilder optional() {
+ return optional(1);
+ }
+
+ public PatternBuilder optional(int count) {
+ fragments.add(fragments.size() - count, "(?:");
+ fragments.add(")?");
+ return this;
+ }
+
+ public PatternBuilder expression(String s) {
+ s = s.replaceAll("\\|$", "\\\\|"); // special case for delimiter
+
+ fragments.add(s);
+ return this;
+ }
+
+ public PatternBuilder text(String s) {
+ fragments.add(s.replaceAll("([\\\\\\.\\[\\{\\(\\)\\*\\+\\?\\^\\$\\|])", "\\\\$1"));
+ return this;
+ }
+
+ public PatternBuilder number(String s) {
+ s = s.replace("dddd", "d{4}").replace("ddd", "d{3}").replace("dd", "d{2}");
+ s = s.replace("xxxx", "x{4}").replace("xxx", "x{3}").replace("xx", "x{2}");
+
+ s = s.replace("d", "\\d").replace("x", "[0-9a-fA-F]").replaceAll("([\\.])", "\\\\$1");
+ s = s.replaceAll("\\|$", "\\\\|").replaceAll("^\\|", "\\\\|"); // special case for delimiter
+
+ fragments.add(s);
+ return this;
+ }
+
+ public PatternBuilder any() {
+ fragments.add(".*");
+ return this;
+ }
+
+ public PatternBuilder binary(String s) {
+ fragments.add(s.replaceAll("(\\p{XDigit}{2})", "\\\\$1"));
+ return this;
+ }
+
+ public PatternBuilder or() {
+ fragments.add("|");
+ return this;
+ }
+
+ public PatternBuilder groupBegin() {
+ return expression("(?:");
+ }
+
+ public PatternBuilder groupEnd() {
+ return expression(")");
+ }
+
+ public PatternBuilder groupEnd(String s) {
+ return expression(")" + s);
+ }
+
+ public Pattern compile() {
+ return Pattern.compile(toString(), Pattern.DOTALL);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ for (String fragment : fragments) {
+ builder.append(fragment);
+ }
+ return builder.toString();
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/PatternUtil.java b/src/main/java/org/traccar/helper/PatternUtil.java
new file mode 100644
index 000000000..74813e1d9
--- /dev/null
+++ b/src/main/java/org/traccar/helper/PatternUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2015 - 2017 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.helper;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.management.ManagementFactory;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+public final class PatternUtil {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PatternUtil.class);
+
+ private PatternUtil() {
+ }
+
+ public static class MatchResult {
+ private String patternMatch;
+ private String patternTail;
+ private String stringMatch;
+ private String stringTail;
+
+ public String getPatternMatch() {
+ return patternMatch;
+ }
+
+ public String getPatternTail() {
+ return patternTail;
+ }
+
+ public String getStringMatch() {
+ return stringMatch;
+ }
+
+ public String getStringTail() {
+ return stringTail;
+ }
+ }
+
+ public static MatchResult checkPattern(String pattern, String input) {
+
+ if (!ManagementFactory.getRuntimeMXBean().getInputArguments().toString().contains("-agentlib:jdwp")) {
+ throw new RuntimeException("PatternUtil usage detected");
+ }
+
+ MatchResult result = new MatchResult();
+
+ for (int i = 0; i < pattern.length(); i++) {
+ try {
+ Matcher matcher = Pattern.compile("(" + pattern.substring(0, i) + ").*").matcher(input);
+ if (matcher.matches()) {
+ result.patternMatch = pattern.substring(0, i);
+ result.patternTail = pattern.substring(i);
+ result.stringMatch = matcher.group(1);
+ result.stringTail = input.substring(matcher.group(1).length());
+ }
+ } catch (PatternSyntaxException error) {
+ LOGGER.warn("Pattern matching error", error);
+ }
+ }
+
+ return result;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/SanitizerModule.java b/src/main/java/org/traccar/helper/SanitizerModule.java
new file mode 100644
index 000000000..af9ac5c2b
--- /dev/null
+++ b/src/main/java/org/traccar/helper/SanitizerModule.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 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.helper;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import org.owasp.encoder.Encode;
+
+import java.io.IOException;
+
+public class SanitizerModule extends SimpleModule {
+
+ public static class SanitizerSerializer extends StdSerializer<String> {
+
+ protected SanitizerSerializer() {
+ super(String.class);
+ }
+
+ @Override
+ public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ gen.writeString(Encode.forHtml(value));
+ }
+
+ }
+
+ public SanitizerModule() {
+ addSerializer(new SanitizerSerializer());
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/UnitsConverter.java b/src/main/java/org/traccar/helper/UnitsConverter.java
new file mode 100644
index 000000000..3dd435df4
--- /dev/null
+++ b/src/main/java/org/traccar/helper/UnitsConverter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015 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.helper;
+
+public final class UnitsConverter {
+
+ private static final double KNOTS_TO_KPH_RATIO = 0.539957;
+ private static final double KNOTS_TO_MPH_RATIO = 0.868976;
+ private static final double KNOTS_TO_MPS_RATIO = 1.94384;
+ private static final double KNOTS_TO_CPS_RATIO = 0.0194384449;
+ private static final double METERS_TO_FEET_RATIO = 0.3048;
+ private static final double METERS_TO_MILE_RATIO = 1609.34;
+ private static final long MILLISECONDS_TO_HOURS_RATIO = 3600000;
+ private static final long MILLISECONDS_TO_MINUTES_RATIO = 60000;
+
+ private UnitsConverter() {
+ }
+
+ public static double knotsFromKph(double value) { // km/h
+ return value * KNOTS_TO_KPH_RATIO;
+ }
+
+ public static double kphFromKnots(double value) {
+ return value / KNOTS_TO_KPH_RATIO;
+ }
+
+ public static double knotsFromMph(double value) {
+ return value * KNOTS_TO_MPH_RATIO;
+ }
+
+ public static double mphFromKnots(double value) {
+ return value / KNOTS_TO_MPH_RATIO;
+ }
+
+ public static double knotsFromMps(double value) { // m/s
+ return value * KNOTS_TO_MPS_RATIO;
+ }
+
+ public static double mpsFromKnots(double value) {
+ return value / KNOTS_TO_MPS_RATIO;
+ }
+
+ public static double knotsFromCps(double value) { // cm/s
+ return value * KNOTS_TO_CPS_RATIO;
+ }
+
+ public static double feetFromMeters(double value) {
+ return value / METERS_TO_FEET_RATIO;
+ }
+
+ public static double metersFromFeet(double value) {
+ return value * METERS_TO_FEET_RATIO;
+ }
+
+ public static double milesFromMeters(double value) {
+ return value / METERS_TO_MILE_RATIO;
+ }
+
+ public static double metersFromMiles(double value) {
+ return value * METERS_TO_MILE_RATIO;
+ }
+
+ public static long msFromHours(long value) {
+ return value * MILLISECONDS_TO_HOURS_RATIO;
+ }
+
+ public static long msFromHours(double value) {
+ return (long) (value * MILLISECONDS_TO_HOURS_RATIO);
+ }
+
+ public static long msFromMinutes(long value) {
+ return value * MILLISECONDS_TO_MINUTES_RATIO;
+ }
+
+}