diff options
Diffstat (limited to 'src/main/java/org/traccar/helper')
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; + } + +} |