aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/release.yml8
-rw-r--r--build.gradle4
-rw-r--r--debug.xml3
-rw-r--r--schema/changelog-5.3.xml16
-rw-r--r--setup/default.xml4
-rwxr-xr-xsetup/package.sh5
-rw-r--r--src/main/java/org/traccar/MainEventHandler.java7
-rw-r--r--src/main/java/org/traccar/MainModule.java12
-rw-r--r--src/main/java/org/traccar/api/resource/PasswordResource.java8
-rw-r--r--src/main/java/org/traccar/api/resource/ReportResource.java2
-rw-r--r--src/main/java/org/traccar/api/resource/ServerResource.java2
-rw-r--r--src/main/java/org/traccar/api/security/LoginService.java7
-rw-r--r--src/main/java/org/traccar/api/signature/CryptoManager.java98
-rw-r--r--src/main/java/org/traccar/api/signature/KeystoreModel.java44
-rw-r--r--src/main/java/org/traccar/config/Keys.java7
-rw-r--r--src/main/java/org/traccar/helper/Parser.java13
-rw-r--r--src/main/java/org/traccar/mail/LogMailManager.java44
-rw-r--r--src/main/java/org/traccar/mail/MailManager.java31
-rw-r--r--src/main/java/org/traccar/mail/SmtpMailManager.java (renamed from src/main/java/org/traccar/database/MailManager.java)9
-rw-r--r--src/main/java/org/traccar/model/Calendar.java6
-rw-r--r--src/main/java/org/traccar/model/Device.java16
-rw-r--r--src/main/java/org/traccar/model/Disableable.java39
-rw-r--r--src/main/java/org/traccar/model/User.java6
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorMail.java2
-rw-r--r--src/main/java/org/traccar/protocol/ArknavProtocol.java11
-rw-r--r--src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java15
-rw-r--r--src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java8
-rw-r--r--src/main/java/org/traccar/protocol/StartekProtocolDecoder.java38
-rw-r--r--src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java256
-rw-r--r--src/main/java/org/traccar/protocol/UproProtocolDecoder.java3
-rw-r--r--src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java20
-rw-r--r--src/main/java/org/traccar/session/ConnectionManager.java12
-rw-r--r--src/main/java/org/traccar/storage/DatabaseStorage.java28
-rw-r--r--src/main/java/org/traccar/storage/QueryBuilder.java3
-rw-r--r--src/main/java/org/traccar/web/WebServer.java7
-rw-r--r--src/test/java/org/traccar/protocol/Gps103ProtocolDecoderTest.java6
-rw-r--r--src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java6
-rw-r--r--src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java4
-rw-r--r--src/test/java/org/traccar/protocol/Xexun2ProtocolDecoderTest.java4
-rw-r--r--templates/full/passwordReset.vm2
40 files changed, 581 insertions, 235 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index beeafe58f..655d47b80 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -21,7 +21,7 @@ jobs:
working-directory: ./traccar-web
- uses: actions/setup-java@v3
with:
- distribution: zulu
+ distribution: temurin
java-version: 11
cache: gradle
- run: ./gradlew build
@@ -46,9 +46,9 @@ jobs:
working-directory: ./setup
run: |
wget -q http://files.jrsoftware.org/is/5/isetup-5.5.6.exe
- wget -q https://github.com/ojdkbuild/ojdkbuild/releases/download/java-11-openjdk-11.0.13.8-1/java-11-openjdk-11.0.13.8-1.windows.ojdkbuild.x86_64.zip
- wget -q https://github.com/ojdkbuild/contrib_jdk11u-ci/releases/download/jdk-11.0.13%2B8/jdk-11.0.13-ojdkbuild-linux-x64.zip
- wget -q https://github.com/ojdkbuild/contrib_jdk11u-arm32-ci/releases/download/jdk-11.0.13%2B8/jdk-11.0.13-ojdkbuild-linux-armhf.zip
+ wget -q https://github.com/ojdkbuild/ojdkbuild/releases/download/java-11-openjdk-11.0.15.9-1/java-11-openjdk-11.0.15.9-1.windows.ojdkbuild.x86_64.zip
+ wget -q https://github.com/ojdkbuild/contrib_jdk11u-ci/releases/download/jdk-11.0.15%2B10/jdk-11.0.15-ojdkbuild-linux-x64.zip
+ wget -q https://github.com/ojdkbuild/contrib_jdk11u-arm32-ci/releases/download/jdk-11.0.15%2B10/jdk-11.0.15-ojdkbuild-linux-armhf.zip
./package.sh ${{ github.event.inputs.version }}
- name: Upload installers
working-directory: ./setup
diff --git a/build.gradle b/build.gradle
index 36fc2f6df..135654c9d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,8 +12,8 @@ repositories {
ext {
guiceVersion = "5.0.1"
jettyVersion = "10.0.7" // jetty 11 javax to jakarta
- jerseyVersion = "2.35" // jersey 3 javax to jakarta
- jacksonVersion = "2.12.2" // same version as jersey-media-json-jackson dependency
+ jerseyVersion = "2.36" // jersey 3 javax to jakarta
+ jacksonVersion = "2.13.3" // same version as jersey-media-json-jackson dependency
protobufVersion = "3.19.3"
}
diff --git a/debug.xml b/debug.xml
index a597d83c5..e02c1b15d 100644
--- a/debug.xml
+++ b/debug.xml
@@ -7,7 +7,6 @@
<entry key='config.default'>./setup/default.xml</entry>
<entry key='web.path'>./traccar-web/web</entry>
- <entry key='web.sanitize'>false</entry>
<entry key='web.debug'>true</entry>
<entry key='web.console'>true</entry>
@@ -16,6 +15,8 @@
<entry key='logger.console'>true</entry>
<entry key='logger.queries'>true</entry>
+ <entry key='mail.debug'>true</entry>
+
<entry key='database.driver'>org.h2.Driver</entry>
<entry key='database.url'>jdbc:h2:./target/database</entry>
<entry key='database.user'>sa</entry>
diff --git a/schema/changelog-5.3.xml b/schema/changelog-5.3.xml
index b574a67d2..a689bc236 100644
--- a/schema/changelog-5.3.xml
+++ b/schema/changelog-5.3.xml
@@ -16,6 +16,22 @@
<column name="fixedemail" type="BOOLEAN" defaultValueBoolean="false" />
</addColumn>
+ <addColumn tableName="tc_devices">
+ <column name="expirationtime" type="TIMESTAMP" />
+ </addColumn>
+
+ <createTable tableName="tc_keystore">
+ <column autoIncrement="true" name="id" type="INT">
+ <constraints primaryKey="true" />
+ </column>
+ <column name="publickey" type="BLOB">
+ <constraints nullable="false" />
+ </column>
+ <column name="privatekey" type="BLOB">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+
</changeSet>
</databaseChangeLog>
diff --git a/setup/default.xml b/setup/default.xml
index dea638d7a..0abc96431 100644
--- a/setup/default.xml
+++ b/setup/default.xml
@@ -11,8 +11,8 @@
-->
<entry key='web.port'>8082</entry>
- <entry key='web.path'>./web</entry>
- <entry key='web.sanitize'>true</entry>
+ <entry key='web.path'>./modern</entry>
+ <entry key='web.sanitize'>false</entry>
<entry key='web.persistSession'>false</entry>
<entry key='geocoder.enable'>true</entry>
diff --git a/setup/package.sh b/setup/package.sh
index e5f976b79..40dac475d 100755
--- a/setup/package.sh
+++ b/setup/package.sh
@@ -81,13 +81,14 @@ else
fi
prepare () {
- mkdir -p out/{conf,data,lib,logs,web,schema,templates}
+ mkdir -p out/{conf,data,lib,logs,legacy,modern,schema,templates}
cp ../target/tracker-server.jar out
cp ../target/lib/* out/lib
cp ../schema/* out/schema
cp -r ../templates/* out/templates
- cp -r ../traccar-web/web/* out/web
+ cp -r ../traccar-web/web/* out/legacy
+ cp -r ../traccar-web/modern/build/* out/modern
cp default.xml out/conf
cp traccar.xml out/conf
diff --git a/src/main/java/org/traccar/MainEventHandler.java b/src/main/java/org/traccar/MainEventHandler.java
index 981888577..52eb43faf 100644
--- a/src/main/java/org/traccar/MainEventHandler.java
+++ b/src/main/java/org/traccar/MainEventHandler.java
@@ -159,10 +159,9 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter {
LOGGER.info("[{}] disconnected", NetworkUtil.session(ctx.channel()));
closeChannel(ctx.channel());
- if (BasePipelineFactory.getHandler(ctx.pipeline(), HttpRequestDecoder.class) == null
- && !connectionlessProtocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName())) {
- connectionManager.deviceDisconnected(ctx.channel());
- }
+ boolean supportsOffline = BasePipelineFactory.getHandler(ctx.pipeline(), HttpRequestDecoder.class) == null
+ && !connectionlessProtocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName());
+ connectionManager.deviceDisconnected(ctx.channel(), supportsOffline);
}
@Override
diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java
index b9543af25..b8ff21472 100644
--- a/src/main/java/org/traccar/MainModule.java
+++ b/src/main/java/org/traccar/MainModule.java
@@ -63,6 +63,9 @@ import org.traccar.handler.GeocoderHandler;
import org.traccar.handler.GeolocationHandler;
import org.traccar.handler.SpeedLimitHandler;
import org.traccar.helper.SanitizerModule;
+import org.traccar.mail.LogMailManager;
+import org.traccar.mail.MailManager;
+import org.traccar.mail.SmtpMailManager;
import org.traccar.notification.EventForwarder;
import org.traccar.session.cache.CacheManager;
import org.traccar.sms.HttpSmsClient;
@@ -128,6 +131,15 @@ public class MainModule extends AbstractModule {
return null;
}
+ @Provides
+ public static MailManager provideMailManager(Config config, StatisticsManager statisticsManager) {
+ if (config.getBoolean(Keys.MAIL_DEBUG)) {
+ return new LogMailManager();
+ } else {
+ return new SmtpMailManager(config, statisticsManager);
+ }
+ }
+
@Singleton
@Provides
public static LdapProvider provideLdapProvider(Config config) {
diff --git a/src/main/java/org/traccar/api/resource/PasswordResource.java b/src/main/java/org/traccar/api/resource/PasswordResource.java
index 643471797..91c2d8ecf 100644
--- a/src/main/java/org/traccar/api/resource/PasswordResource.java
+++ b/src/main/java/org/traccar/api/resource/PasswordResource.java
@@ -16,7 +16,7 @@
package org.traccar.api.resource;
import org.traccar.api.BaseResource;
-import org.traccar.database.MailManager;
+import org.traccar.mail.MailManager;
import org.traccar.model.User;
import org.traccar.notification.TextTemplateFormatter;
import org.traccar.storage.StorageException;
@@ -58,7 +58,8 @@ public class PasswordResource extends BaseResource {
if (user != null) {
String token = UUID.randomUUID().toString().replaceAll("-", "");
user.set(PASSWORD_RESET_TOKEN, token);
- storage.updateObject(user, new Request(new Columns.Exclude("id"), new Condition.Equals("id", "id")));
+ storage.updateObject(user, new Request(
+ new Columns.Include("attributes"), new Condition.Equals("id", "id")));
var velocityContext = textTemplateFormatter.prepareContext(permissionsService.getServer(), user);
velocityContext.put("token", token);
@@ -79,7 +80,8 @@ public class PasswordResource extends BaseResource {
if (user != null) {
user.getAttributes().remove(PASSWORD_RESET_TOKEN);
user.setPassword(password);
- storage.updateObject(user, new Request(new Columns.Exclude("id"), new Condition.Equals("id", "id")));
+ storage.updateObject(user, new Request(
+ new Columns.Include("attributes", "hashedPassword", "salt"), new Condition.Equals("id", "id")));
return Response.ok().build();
}
return Response.status(Response.Status.NOT_FOUND).build();
diff --git a/src/main/java/org/traccar/api/resource/ReportResource.java b/src/main/java/org/traccar/api/resource/ReportResource.java
index 6176013c1..70177dd4d 100644
--- a/src/main/java/org/traccar/api/resource/ReportResource.java
+++ b/src/main/java/org/traccar/api/resource/ReportResource.java
@@ -19,7 +19,7 @@ package org.traccar.api.resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.api.BaseResource;
-import org.traccar.database.MailManager;
+import org.traccar.mail.MailManager;
import org.traccar.helper.LogAction;
import org.traccar.model.Event;
import org.traccar.model.Position;
diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java
index 4fc76a0d7..e35cd7d95 100644
--- a/src/main/java/org/traccar/api/resource/ServerResource.java
+++ b/src/main/java/org/traccar/api/resource/ServerResource.java
@@ -16,7 +16,7 @@
package org.traccar.api.resource;
import org.traccar.api.BaseResource;
-import org.traccar.database.MailManager;
+import org.traccar.mail.MailManager;
import org.traccar.geocoder.Geocoder;
import org.traccar.helper.Log;
import org.traccar.helper.LogAction;
diff --git a/src/main/java/org/traccar/api/security/LoginService.java b/src/main/java/org/traccar/api/security/LoginService.java
index 9938cf6dc..104a6fac3 100644
--- a/src/main/java/org/traccar/api/security/LoginService.java
+++ b/src/main/java/org/traccar/api/security/LoginService.java
@@ -83,12 +83,7 @@ public class LoginService {
if (user == null) {
throw new SecurityException("Unknown account");
}
- if (user.getDisabled()) {
- throw new SecurityException("Account is disabled");
- }
- if (user.getExpirationTime() != null && System.currentTimeMillis() > user.getExpirationTime().getTime()) {
- throw new SecurityException("Account has expired");
- }
+ user.checkDisabled();
}
}
diff --git a/src/main/java/org/traccar/api/signature/CryptoManager.java b/src/main/java/org/traccar/api/signature/CryptoManager.java
new file mode 100644
index 000000000..ea59dcd70
--- /dev/null
+++ b/src/main/java/org/traccar/api/signature/CryptoManager.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2022 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.api.signature;
+
+import org.traccar.helper.DataConverter;
+import org.traccar.storage.Storage;
+import org.traccar.storage.StorageException;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Request;
+
+import javax.inject.Inject;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+public class CryptoManager {
+
+ private final Storage storage;
+
+ private PublicKey publicKey;
+ private PrivateKey privateKey;
+
+ @Inject
+ public CryptoManager(Storage storage) {
+ this.storage = storage;
+ }
+
+ public String sign(String data) throws GeneralSecurityException, StorageException {
+ if (privateKey == null) {
+ initializeKeys();
+ }
+ Signature signature = Signature.getInstance("SHA256withECDSA");
+ signature.initSign(privateKey);
+ signature.update(data.getBytes());
+ return data + '.' + DataConverter.printBase64(signature.sign());
+ }
+
+ public String verify(String data) throws GeneralSecurityException, StorageException {
+ if (publicKey == null) {
+ initializeKeys();
+ }
+ Signature signature = Signature.getInstance("SHA256withECDSA");
+ signature.initVerify(publicKey);
+
+ int delimiter = data.lastIndexOf('.');
+ String originalData = data.substring(0, delimiter);
+
+ signature.update(originalData.getBytes());
+ if (!signature.verify(DataConverter.parseBase64(data.substring(delimiter + 1)))) {
+ throw new SecurityException("Invalid signature");
+ }
+ return originalData;
+ }
+
+ private void initializeKeys() throws StorageException, GeneralSecurityException {
+ KeystoreModel model = storage.getObject(KeystoreModel.class, new Request(new Columns.All()));
+ if (model != null) {
+ publicKey = KeyFactory.getInstance("EC")
+ .generatePublic(new X509EncodedKeySpec(model.getPublicKey()));
+ privateKey = KeyFactory.getInstance("EC")
+ .generatePrivate(new PKCS8EncodedKeySpec(model.getPrivateKey()));
+ } else {
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
+ generator.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
+ KeyPair pair = generator.generateKeyPair();
+
+ publicKey = pair.getPublic();
+ privateKey = pair.getPrivate();
+
+ model = new KeystoreModel();
+ model.setPublicKey(publicKey.getEncoded());
+ model.setPrivateKey(privateKey.getEncoded());
+ storage.addObject(model, new Request(new Columns.Exclude("id")));
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/api/signature/KeystoreModel.java b/src/main/java/org/traccar/api/signature/KeystoreModel.java
new file mode 100644
index 000000000..7f3140e81
--- /dev/null
+++ b/src/main/java/org/traccar/api/signature/KeystoreModel.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 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.api.signature;
+
+import org.traccar.model.BaseModel;
+import org.traccar.storage.StorageName;
+
+@StorageName("tc_keystore")
+public class KeystoreModel extends BaseModel {
+
+ private byte[] publicKey;
+
+ public byte[] getPublicKey() {
+ return publicKey;
+ }
+
+ public void setPublicKey(byte[] publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ private byte[] privateKey;
+
+ public byte[] getPrivateKey() {
+ return privateKey;
+ }
+
+ public void setPrivateKey(byte[] privateKey) {
+ this.privateKey = privateKey;
+ }
+
+}
diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java
index 2190f82f7..e97e104a1 100644
--- a/src/main/java/org/traccar/config/Keys.java
+++ b/src/main/java/org/traccar/config/Keys.java
@@ -801,6 +801,13 @@ public final class Keys {
"templates");
/**
+ * Log emails instead of sending them via SMTP. Intended for testing purposes only.
+ */
+ public static final ConfigKey<Boolean> MAIL_DEBUG = new BooleanConfigKey(
+ "mail.debug",
+ List.of(KeyType.CONFIG));
+
+ /**
* Force SMTP settings from the config file and ignore user attributes.
*/
public static final ConfigKey<Boolean> MAIL_SMTP_IGNORE_USER_CONFIG = new BooleanConfigKey(
diff --git a/src/main/java/org/traccar/helper/Parser.java b/src/main/java/org/traccar/helper/Parser.java
index 22e98ded1..aa39e1ad7 100644
--- a/src/main/java/org/traccar/helper/Parser.java
+++ b/src/main/java/org/traccar/helper/Parser.java
@@ -48,13 +48,14 @@ public class Parser {
}
public boolean hasNext(int number) {
- String value = matcher.group(position);
- if (value != null && !value.isEmpty()) {
- return true;
- } else {
- position += number;
- return false;
+ for (int i = position; i < position + number; i++) {
+ String value = matcher.group(i);
+ if (value != null && !value.isEmpty()) {
+ return true;
+ }
}
+ position += number;
+ return false;
}
public String next() {
diff --git a/src/main/java/org/traccar/mail/LogMailManager.java b/src/main/java/org/traccar/mail/LogMailManager.java
new file mode 100644
index 000000000..b6b912d6c
--- /dev/null
+++ b/src/main/java/org/traccar/mail/LogMailManager.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 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.mail;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.model.User;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+
+public class LogMailManager implements MailManager {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(LogMailManager.class);
+
+ @Override
+ public boolean getEmailEnabled() {
+ return true;
+ }
+
+ @Override
+ public void sendMessage(User user, String subject, String body) throws MessagingException {
+ sendMessage(user, subject, body, null);
+ }
+
+ @Override
+ public void sendMessage(User user, String subject, String body, MimeBodyPart attachment) throws MessagingException {
+ LOGGER.info("\nTo: " + user.getEmail() + "\nSubject: " + subject + "\nBody:\n" + body);
+ }
+
+}
diff --git a/src/main/java/org/traccar/mail/MailManager.java b/src/main/java/org/traccar/mail/MailManager.java
new file mode 100644
index 000000000..69efbed32
--- /dev/null
+++ b/src/main/java/org/traccar/mail/MailManager.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2022 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.mail;
+
+import org.traccar.model.User;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+
+public interface MailManager {
+
+ boolean getEmailEnabled();
+
+ void sendMessage(User user, String subject, String body) throws MessagingException;
+
+ void sendMessage(User user, String subject, String body, MimeBodyPart attachment) throws MessagingException;
+
+}
diff --git a/src/main/java/org/traccar/database/MailManager.java b/src/main/java/org/traccar/mail/SmtpMailManager.java
index ec1681dcb..4a0b7048f 100644
--- a/src/main/java/org/traccar/database/MailManager.java
+++ b/src/main/java/org/traccar/mail/SmtpMailManager.java
@@ -14,15 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.database;
+package org.traccar.mail;
import org.traccar.config.Config;
import org.traccar.config.ConfigKey;
import org.traccar.config.Keys;
+import org.traccar.database.StatisticsManager;
import org.traccar.model.User;
import org.traccar.notification.PropertiesProvider;
-import javax.inject.Inject;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
@@ -37,15 +37,14 @@ import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;
-public final class MailManager {
+public final class SmtpMailManager implements MailManager {
private static final String CONTENT_TYPE = "text/html; charset=utf-8";
private final Config config;
private final StatisticsManager statisticsManager;
- @Inject
- public MailManager(Config config, StatisticsManager statisticsManager) {
+ public SmtpMailManager(Config config, StatisticsManager statisticsManager) {
this.config = config;
this.statisticsManager = statisticsManager;
}
diff --git a/src/main/java/org/traccar/model/Calendar.java b/src/main/java/org/traccar/model/Calendar.java
index 102c0be52..c1f98a957 100644
--- a/src/main/java/org/traccar/model/Calendar.java
+++ b/src/main/java/org/traccar/model/Calendar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
* Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -49,13 +49,13 @@ public class Calendar extends ExtendedModel {
private byte[] data;
public byte[] getData() {
- return data.clone();
+ return data;
}
public void setData(byte[] data) throws IOException, ParserException {
CalendarBuilder builder = new CalendarBuilder();
calendar = builder.build(new ByteArrayInputStream(data));
- this.data = data.clone();
+ this.data = data;
}
private net.fortuna.ical4j.model.Calendar calendar;
diff --git a/src/main/java/org/traccar/model/Device.java b/src/main/java/org/traccar/model/Device.java
index 57ba07624..f21e5ca84 100644
--- a/src/main/java/org/traccar/model/Device.java
+++ b/src/main/java/org/traccar/model/Device.java
@@ -23,7 +23,7 @@ import org.traccar.storage.QueryIgnore;
import org.traccar.storage.StorageName;
@StorageName("tc_devices")
-public class Device extends GroupedModel {
+public class Device extends GroupedModel implements Disableable {
private String name;
@@ -140,12 +140,26 @@ public class Device extends GroupedModel {
private boolean disabled;
+ @Override
public boolean getDisabled() {
return disabled;
}
+ @Override
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
+ private Date expirationTime;
+
+ @Override
+ public Date getExpirationTime() {
+ return expirationTime;
+ }
+
+ @Override
+ public void setExpirationTime(Date expirationTime) {
+ this.expirationTime = expirationTime;
+ }
+
}
diff --git a/src/main/java/org/traccar/model/Disableable.java b/src/main/java/org/traccar/model/Disableable.java
new file mode 100644
index 000000000..1145d6279
--- /dev/null
+++ b/src/main/java/org/traccar/model/Disableable.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 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.model;
+
+import java.util.Date;
+
+public interface Disableable {
+
+ boolean getDisabled();
+
+ void setDisabled(boolean disabled);
+
+ Date getExpirationTime();
+
+ void setExpirationTime(Date expirationTime);
+
+ default void checkDisabled() throws SecurityException {
+ if (getDisabled()) {
+ throw new SecurityException(getClass().getSimpleName() + " is disabled");
+ }
+ if (getExpirationTime() != null && System.currentTimeMillis() > getExpirationTime().getTime()) {
+ throw new SecurityException(getClass().getSimpleName() + " has expired");
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/model/User.java b/src/main/java/org/traccar/model/User.java
index 0aa67168f..3db20c753 100644
--- a/src/main/java/org/traccar/model/User.java
+++ b/src/main/java/org/traccar/model/User.java
@@ -24,7 +24,7 @@ import org.traccar.storage.StorageName;
import java.util.Date;
@StorageName("tc_users")
-public class User extends ExtendedModel implements UserRestrictions {
+public class User extends ExtendedModel implements UserRestrictions, Disableable {
private String name;
@@ -155,20 +155,24 @@ public class User extends ExtendedModel implements UserRestrictions {
private boolean disabled;
+ @Override
public boolean getDisabled() {
return disabled;
}
+ @Override
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
private Date expirationTime;
+ @Override
public Date getExpirationTime() {
return expirationTime;
}
+ @Override
public void setExpirationTime(Date expirationTime) {
this.expirationTime = expirationTime;
}
diff --git a/src/main/java/org/traccar/notificators/NotificatorMail.java b/src/main/java/org/traccar/notificators/NotificatorMail.java
index 647832166..75571cfc4 100644
--- a/src/main/java/org/traccar/notificators/NotificatorMail.java
+++ b/src/main/java/org/traccar/notificators/NotificatorMail.java
@@ -16,7 +16,7 @@
*/
package org.traccar.notificators;
-import org.traccar.database.MailManager;
+import org.traccar.mail.MailManager;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.model.User;
diff --git a/src/main/java/org/traccar/protocol/ArknavProtocol.java b/src/main/java/org/traccar/protocol/ArknavProtocol.java
index ed38f26d7..4f443aa3a 100644
--- a/src/main/java/org/traccar/protocol/ArknavProtocol.java
+++ b/src/main/java/org/traccar/protocol/ArknavProtocol.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2022 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.
@@ -38,6 +38,15 @@ public class ArknavProtocol extends BaseProtocol {
pipeline.addLast(new ArknavProtocolDecoder(ArknavProtocol.this));
}
});
+ addServer(new TrackerServer(config, getName(), true) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new StringDecoder());
+ pipeline.addLast(new StringEncoder());
+ pipeline.addLast(new ArknavProtocolDecoder(ArknavProtocol.this));
+ }
+ });
+
}
}
diff --git a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java
index b63bcd0c0..28efa3c30 100644
--- a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java
@@ -56,9 +56,12 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder {
.groupEnd()
.expression("([^,]+)?,") // rfid
.groupBegin()
- .text("L,,,")
+ .text("L,")
+ .groupBegin()
+ .text(",,")
.number("(x+),,") // lac
.number("(x+),,,") // cid
+ .groupEnd("?")
.or()
.text("F,")
.groupBegin()
@@ -218,13 +221,11 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder {
}
if (parser.hasNext(2)) {
-
- getLastLocation(position, null);
-
position.setNetwork(new Network(CellTower.fromLacCid(
getConfig(), parser.nextHexInt(0), parser.nextHexInt(0))));
+ }
- } else {
+ if (parser.hasNext(20)) {
String utcHours = parser.next();
String utcMinutes = parser.next();
@@ -262,6 +263,10 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder {
position.set("fuel2", parser.nextDouble());
position.set(Position.PREFIX_TEMP + 1, parser.nextInt());
+ } else {
+
+ getLastLocation(position, null);
+
}
return position;
diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java
index 88b7d12c8..8a58ebc5e 100644
--- a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java
@@ -204,11 +204,11 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.PREFIX_ADC + i, parser.nextHexInt());
}
- String deviceModel = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel();
- if (deviceModel == null) {
- deviceModel = "";
+ String model = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel();
+ if (model == null) {
+ model = "";
}
- switch (deviceModel.toUpperCase()) {
+ switch (model.toUpperCase()) {
case "MVT340":
case "MVT380":
position.set(Position.KEY_BATTERY, parser.nextHexInt() * 3.0 * 2.0 / 1024.0);
diff --git a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
index 53c02f28c..b2fcd5452 100644
--- a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
@@ -42,6 +42,7 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
.number("d+,") // length
.number("(d+),") // imei
.expression("(.+)") // content
+ .number("xx") // checksum
.compile();
private static final Pattern PATTERN_POSITION = new PatternBuilder()
@@ -73,22 +74,26 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
.groupBegin()
.text(",")
.number("d,") // extended
- .expression("([^,]+)?,") // fuel
+ .expression("([^,]+)?") // fuel
+ .groupBegin()
+ .text(",")
.expression("([^,]+)?") // temperature
.groupBegin()
.text(",")
- .number("(d+)|") // rpm
- .number("(d+)|") // engine load
- .number("d+|") // maf flow
- .number("d+|") // intake pressure
- .number("d+|") // intake temperature
- .number("(d+)|") // throttle
- .number("(d+)|") // coolant temperature
- .number("(d+)|") // instant fuel
- .number("(d+)") // fuel level
+ .groupBegin()
+ .number("(d+)?|") // rpm
+ .number("(d+)?|") // engine load
+ .number("d*|") // maf flow
+ .number("d*|") // intake pressure
+ .number("d*|") // intake temperature
+ .number("(d+)?|") // throttle
+ .number("(d+)?|") // coolant temperature
+ .number("(d+)?|") // instant fuel
+ .number("(d+)[%L]").optional() // fuel level
+ .groupEnd("?")
+ .groupEnd("?")
.groupEnd("?")
.groupEnd("?")
- .any()
.compile();
private String decodeAlarm(int value) {
@@ -122,9 +127,6 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
}
String content = parser.next();
- if (content.charAt(content.length() - 2 - 1) != '|') {
- content = content.substring(0, content.length() - 2);
- }
if (content.length() < 100) {
Position position = new Position(getProtocolName());
@@ -223,8 +225,12 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_RPM, parser.nextInt());
position.set(Position.KEY_ENGINE_LOAD, parser.nextInt());
position.set(Position.KEY_THROTTLE, parser.nextInt());
- position.set(Position.KEY_COOLANT_TEMP, parser.nextInt() - 40);
- position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextInt() * 0.1);
+ if (parser.hasNext()) {
+ position.set(Position.KEY_COOLANT_TEMP, parser.nextInt() - 40);
+ }
+ if (parser.hasNext()) {
+ position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextInt() * 0.1);
+ }
position.set(Position.KEY_FUEL_LEVEL, parser.nextInt());
}
diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
index 77047fe26..2b1196d55 100644
--- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2022 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.
@@ -20,6 +20,7 @@ import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
+import org.traccar.model.Device;
import org.traccar.session.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
@@ -38,11 +39,15 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
private static final int IMAGE_PACKET_MAX = 2048;
+ private static final Map<Integer, Map<Set<String>, BiConsumer<Position, ByteBuf>>> PARAMETERS = new HashMap<>();
+
private final boolean connectionless;
private boolean extended;
private final Map<Long, ByteBuf> photos = new HashMap<>();
@@ -184,185 +189,160 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
}
}
- private long readValue(ByteBuf buf, int length, boolean signed) {
+ private long readValue(ByteBuf buf, int length) {
switch (length) {
case 1:
- return signed ? buf.readByte() : buf.readUnsignedByte();
+ return buf.readUnsignedByte();
case 2:
- return signed ? buf.readShort() : buf.readUnsignedShort();
+ return buf.readUnsignedShort();
case 4:
- return signed ? buf.readInt() : buf.readUnsignedInt();
+ return buf.readUnsignedInt();
default:
return buf.readLong();
}
}
- private void decodeOtherParameter(Position position, int id, ByteBuf buf, int length) {
- switch (id) {
- case 1:
- case 2:
- case 3:
- case 4:
- position.set("di" + id, readValue(buf, length, false));
- break;
- case 9:
- position.set(Position.PREFIX_ADC + 1, readValue(buf, length, false));
- break;
- case 10:
- position.set(Position.PREFIX_ADC + 2, readValue(buf, length, false));
- break;
- case 16:
- position.set(Position.KEY_ODOMETER, readValue(buf, length, false));
- break;
- case 17:
- position.set("axisX", readValue(buf, length, true));
- break;
- case 18:
- position.set("axisY", readValue(buf, length, true));
- break;
- case 19:
- position.set("axisZ", readValue(buf, length, true));
- break;
- case 21:
- position.set(Position.KEY_RSSI, readValue(buf, length, false));
- break;
- case 25:
- case 26:
- case 27:
- case 28:
- position.set(Position.PREFIX_TEMP + (id - 24 + 4), readValue(buf, length, true) * 0.1);
- break;
- case 66:
- position.set(Position.KEY_POWER, readValue(buf, length, false) * 0.001);
- break;
- case 67:
- position.set(Position.KEY_BATTERY, readValue(buf, length, false) * 0.001);
- break;
- case 72:
- case 73:
- case 74:
- position.set(Position.PREFIX_TEMP + (id - 71), readValue(buf, length, true) * 0.1);
- break;
- case 78:
- long driverUniqueId = readValue(buf, length, false);
- if (driverUniqueId != 0) {
- position.set(Position.KEY_DRIVER_UNIQUE_ID, String.format("%016X", driverUniqueId));
- }
- break;
- case 80:
- position.set("workMode", readValue(buf, length, false));
- break;
- case 90:
- position.set(Position.KEY_DOOR, readValue(buf, length, false));
- break;
- case 115:
- position.set(Position.KEY_COOLANT_TEMP, readValue(buf, length, true) * 0.1);
- break;
- case 179:
- position.set(Position.PREFIX_OUT + 1, readValue(buf, length, false) == 1);
- break;
- case 180:
- position.set(Position.PREFIX_OUT + 2, readValue(buf, length, false) == 1);
- break;
- case 181:
- position.set(Position.KEY_PDOP, readValue(buf, length, false) * 0.1);
- break;
- case 182:
- position.set(Position.KEY_HDOP, readValue(buf, length, false) * 0.1);
- break;
- case 199:
- position.set(Position.KEY_ODOMETER_TRIP, readValue(buf, length, false));
- break;
- case 236:
- if (readValue(buf, length, false) == 1) {
- position.set(Position.KEY_ALARM, Position.ALARM_GENERAL);
- }
- break;
- case 239:
- position.set(Position.KEY_IGNITION, readValue(buf, length, false) == 1);
- break;
- case 240:
- position.set(Position.KEY_MOTION, readValue(buf, length, false) == 1);
- break;
- case 241:
- position.set(Position.KEY_OPERATOR, readValue(buf, length, false));
- break;
- case 253:
- switch ((int) readValue(buf, length, false)) {
- case 1:
- position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION);
- break;
- case 2:
- position.set(Position.KEY_ALARM, Position.ALARM_BRAKING);
- break;
- case 3:
- position.set(Position.KEY_ALARM, Position.ALARM_CORNERING);
- break;
- default:
- break;
- }
- break;
- default:
- position.set(Position.PREFIX_IO + id, readValue(buf, length, false));
- break;
- }
+ private static void register(int id, Set<String> models, BiConsumer<Position, ByteBuf> handler) {
+ PARAMETERS.computeIfAbsent(id, key -> new HashMap<>()).put(models, handler);
+ }
+
+ static {
+ var fmbXXX = Set.of(
+ "FMB001", "FMB010", "FMB002", "FMB020", "FMB003", "FMB110", "FMB120", "FMB122", "FMB125", "FMB130",
+ "FMB140", "FMU125", "FMB900", "FMB920", "FMB962", "FMB964", "FM3001", "FMB202", "FMB204", "FMB206",
+ "FMT100", "MTB100", "FMP100", "MSP500");
+
+ register(1, null, (p, b) -> p.set(Position.PREFIX_IN + 1, b.readUnsignedByte() > 0));
+ register(2, null, (p, b) -> p.set(Position.PREFIX_IN + 2, b.readUnsignedByte() > 0));
+ register(3, null, (p, b) -> p.set(Position.PREFIX_IN + 3, b.readUnsignedByte() > 0));
+ register(4, null, (p, b) -> p.set(Position.PREFIX_IN + 4, b.readUnsignedByte() > 0));
+ register(9, fmbXXX, (p, b) -> p.set(Position.PREFIX_ADC + 1, b.readUnsignedShort() * 0.001));
+ register(10, fmbXXX, (p, b) -> p.set(Position.PREFIX_ADC + 2, b.readUnsignedShort() * 0.001));
+ register(11, fmbXXX, (p, b) -> p.set(Position.KEY_ICCID, String.valueOf(b.readLong())));
+ register(12, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_USED, b.readUnsignedInt() * 0.001));
+ register(13, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_CONSUMPTION, b.readUnsignedShort() * 0.01));
+ register(16, null, (p, b) -> p.set(Position.KEY_ODOMETER, b.readUnsignedInt()));
+ register(17, null, (p, b) -> p.set("axisX", b.readShort()));
+ register(18, null, (p, b) -> p.set("axisY", b.readShort()));
+ register(19, null, (p, b) -> p.set("axisZ", b.readShort()));
+ register(21, null, (p, b) -> p.set(Position.KEY_RSSI, b.readUnsignedByte()));
+ register(24, fmbXXX, (p, b) -> p.setSpeed(UnitsConverter.knotsFromKph(b.readUnsignedShort())));
+ register(25, null, (p, b) -> p.set("bleTemp1", b.readShort() * 0.01));
+ register(26, null, (p, b) -> p.set("bleTemp2", b.readShort() * 0.01));
+ register(27, null, (p, b) -> p.set("bleTemp3", b.readShort() * 0.01));
+ register(28, null, (p, b) -> p.set("bleTemp4", b.readShort() * 0.01));
+ register(66, null, (p, b) -> p.set(Position.KEY_POWER, b.readUnsignedShort() * 0.001));
+ register(67, null, (p, b) -> p.set(Position.KEY_BATTERY, b.readUnsignedShort() * 0.001));
+ register(68, fmbXXX, (p, b) -> p.set("batteryCurrent", b.readUnsignedShort() * 0.001));
+ register(72, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 1, b.readShort() * 0.1));
+ register(73, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 2, b.readShort() * 0.1));
+ register(74, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 3, b.readShort() * 0.1));
+ register(75, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 4, b.readShort() * 0.1));
+ register(78, null, (p, b) -> {
+ long driverUniqueId = b.readLong();
+ if (driverUniqueId > 0) {
+ p.set(Position.KEY_DRIVER_UNIQUE_ID, String.format("%016X", driverUniqueId));
+ }
+ });
+ register(80, null, (p, b) -> p.set("dataMode", b.readUnsignedByte()));
+ register(90, null, (p, b) -> p.set(Position.KEY_DOOR, b.readUnsignedShort()));
+ register(115, fmbXXX, (p, b) -> p.set(Position.KEY_COOLANT_TEMP, b.readShort() * 0.1));
+ register(179, null, (p, b) -> p.set(Position.PREFIX_OUT + 1, b.readUnsignedByte() > 0));
+ register(180, null, (p, b) -> p.set(Position.PREFIX_OUT + 2, b.readUnsignedByte() > 0));
+ register(181, null, (p, b) -> p.set(Position.KEY_PDOP, b.readUnsignedShort() * 0.1));
+ register(182, null, (p, b) -> p.set(Position.KEY_HDOP, b.readUnsignedShort() * 0.1));
+ register(199, null, (p, b) -> p.set(Position.KEY_ODOMETER_TRIP, b.readUnsignedInt()));
+ register(200, fmbXXX, (p, b) -> p.set("sleepMode", b.readUnsignedByte()));
+ register(205, null, (p, b) -> p.set("cid", b.readUnsignedShort()));
+ register(206, null, (p, b) -> p.set("lac", b.readUnsignedShort()));
+ register(236, null, (p, b) -> {
+ p.set(Position.KEY_ALARM, b.readUnsignedByte() > 0 ? Position.ALARM_GENERAL : null);
+ });
+ register(239, null, (p, b) -> p.set(Position.KEY_IGNITION, b.readUnsignedByte() > 0));
+ register(240, null, (p, b) -> p.set(Position.KEY_MOTION, b.readUnsignedByte() > 0));
+ register(241, null, (p, b) -> p.set(Position.KEY_OPERATOR, b.readUnsignedInt()));
+ register(253, null, (p, b) -> {
+ switch (b.readUnsignedByte()) {
+ case 1:
+ p.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION);
+ break;
+ case 2:
+ p.set(Position.KEY_ALARM, Position.ALARM_BRAKING);
+ break;
+ case 3:
+ p.set(Position.KEY_ALARM, Position.ALARM_CORNERING);
+ break;
+ default:
+ break;
+ }
+ });
}
private void decodeGh3000Parameter(Position position, int id, ByteBuf buf, int length) {
switch (id) {
case 1:
- position.set(Position.KEY_BATTERY_LEVEL, readValue(buf, length, false));
+ position.set(Position.KEY_BATTERY_LEVEL, readValue(buf, length));
break;
case 2:
- position.set("usbConnected", readValue(buf, length, false) == 1);
+ position.set("usbConnected", readValue(buf, length) == 1);
break;
case 5:
- position.set("uptime", readValue(buf, length, false));
+ position.set("uptime", readValue(buf, length));
break;
case 20:
- position.set(Position.KEY_HDOP, readValue(buf, length, false) * 0.1);
+ position.set(Position.KEY_HDOP, readValue(buf, length) * 0.1);
break;
case 21:
- position.set(Position.KEY_VDOP, readValue(buf, length, false) * 0.1);
+ position.set(Position.KEY_VDOP, readValue(buf, length) * 0.1);
break;
case 22:
- position.set(Position.KEY_PDOP, readValue(buf, length, false) * 0.1);
+ position.set(Position.KEY_PDOP, readValue(buf, length) * 0.1);
break;
case 67:
- position.set(Position.KEY_BATTERY, readValue(buf, length, false) * 0.001);
+ position.set(Position.KEY_BATTERY, readValue(buf, length) * 0.001);
break;
case 221:
- position.set("button", readValue(buf, length, false));
+ position.set("button", readValue(buf, length));
break;
case 222:
- if (readValue(buf, length, false) == 1) {
+ if (readValue(buf, length) == 1) {
position.set(Position.KEY_ALARM, Position.ALARM_SOS);
}
break;
case 240:
- position.set(Position.KEY_MOTION, readValue(buf, length, false) == 1);
+ position.set(Position.KEY_MOTION, readValue(buf, length) == 1);
break;
case 244:
- position.set(Position.KEY_ROAMING, readValue(buf, length, false) == 1);
+ position.set(Position.KEY_ROAMING, readValue(buf, length) == 1);
break;
default:
- position.set(Position.PREFIX_IO + id, readValue(buf, length, false));
+ position.set(Position.PREFIX_IO + id, readValue(buf, length));
break;
}
}
- private void decodeParameter(Position position, int id, ByteBuf buf, int length, int codec) {
+ private void decodeParameter(Position position, int id, ByteBuf buf, int length, int codec, String model) {
if (codec == CODEC_GH3000) {
decodeGh3000Parameter(position, id, buf, length);
} else {
- decodeOtherParameter(position, id, buf, length);
+ boolean decoded = false;
+ for (var entry : PARAMETERS.getOrDefault(id, new HashMap<>()).entrySet()) {
+ if (entry.getKey() == null || model != null && entry.getKey().contains(model)) {
+ entry.getValue().accept(position, buf);
+ decoded = true;
+ break;
+ }
+ }
+ if (!decoded) {
+ position.set(Position.PREFIX_IO + id, readValue(buf, length));
+ }
}
}
private void decodeNetwork(Position position) {
- long cid = position.getLong(Position.PREFIX_IO + 205);
- int lac = position.getInteger(Position.PREFIX_IO + 206);
- if (cid != 0 && lac != 0) {
+ Integer cid = (Integer) position.getAttributes().remove("cid");
+ Integer lac = (Integer) position.getAttributes().remove("lac");
+ if (cid != null && lac != null) {
CellTower cellTower = CellTower.fromLacCid(getConfig(), lac, cid);
long operator = position.getInteger(Position.KEY_OPERATOR);
if (operator >= 1000) {
@@ -387,7 +367,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
}
}
- private void decodeLocation(Position position, ByteBuf buf, int codec) {
+ private void decodeLocation(Position position, ByteBuf buf, int codec, String model) {
int globalMask = 0x0f;
@@ -484,7 +464,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
if (BitUtil.check(globalMask, 1)) {
int cnt = readExtByte(buf, codec, CODEC_8_EXT);
for (int j = 0; j < cnt; j++) {
- decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 1, codec);
+ decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 1, codec, model);
}
}
@@ -492,7 +472,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
if (BitUtil.check(globalMask, 2)) {
int cnt = readExtByte(buf, codec, CODEC_8_EXT);
for (int j = 0; j < cnt; j++) {
- decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 2, codec);
+ decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 2, codec, model);
}
}
@@ -500,7 +480,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
if (BitUtil.check(globalMask, 3)) {
int cnt = readExtByte(buf, codec, CODEC_8_EXT);
for (int j = 0; j < cnt; j++) {
- decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 4, codec);
+ decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 4, codec, model);
}
}
@@ -508,7 +488,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
if (codec == CODEC_8 || codec == CODEC_8_EXT || codec == CODEC_16) {
int cnt = readExtByte(buf, codec, CODEC_8_EXT);
for (int j = 0; j < cnt; j++) {
- decodeOtherParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 8);
+ decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 8, codec, model);
}
}
@@ -578,10 +558,10 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
int count = buf.readUnsignedByte();
DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei);
-
if (deviceSession == null) {
return null;
}
+ String model = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel();
for (int i = 0; i < count; i++) {
Position position = new Position(getProtocolName());
@@ -603,7 +583,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
} else if (codec == CODEC_12) {
decodeSerial(channel, remoteAddress, deviceSession, position, buf);
} else {
- decodeLocation(position, buf, codec);
+ decodeLocation(position, buf, codec, model);
}
if (!position.getOutdated() || !position.getAttributes().isEmpty()) {
diff --git a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java
index bdc6bf24e..ed714e464 100644
--- a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java
@@ -67,12 +67,11 @@ public class UproProtocolDecoder extends BaseProtocolDecoder {
DateBuilder dateBuilder = new DateBuilder()
.setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0));
- position.setValid(true);
position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN));
position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN));
int flags = parser.nextInt(0);
- position.setValid(BitUtil.check(flags, 0));
+ position.setValid(!BitUtil.check(flags, 0));
if (!BitUtil.check(flags, 1)) {
position.setLatitude(-position.getLatitude());
}
diff --git a/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java
index a572f8622..28e7fbda3 100644
--- a/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java
@@ -176,7 +176,25 @@ public class Xexun2ProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
position.setLongitude(convertCoordinate(buf.readDouble()));
position.setLatitude(convertCoordinate(buf.readDouble()));
-
+ }
+ if (BitUtil.check(positionMask, 7)) {
+ int dataLength = buf.readUnsignedShort();
+ if (dataLength > 0) {
+ int dataType = buf.readUnsignedByte();
+ int dataEndIndex = buf.readerIndex() + buf.readUnsignedShort();
+ if (dataType == 'G') {
+ position.setFixTime(position.getDeviceTime());
+ position.setLongitude(convertCoordinate(buf.readDouble()));
+ position.setLatitude(convertCoordinate(buf.readDouble()));
+ position.setValid(buf.readUnsignedByte() > 0);
+ position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
+ buf.readUnsignedByte(); // satellite signal-to-noise ratio
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1));
+ position.setCourse(buf.readUnsignedShort() * 0.1);
+ position.setAltitude(buf.readFloat());
+ }
+ buf.readerIndex(dataEndIndex);
+ }
}
}
if (BitUtil.check(mask, 3)) {
diff --git a/src/main/java/org/traccar/session/ConnectionManager.java b/src/main/java/org/traccar/session/ConnectionManager.java
index 9888cca2b..262a302af 100644
--- a/src/main/java/org/traccar/session/ConnectionManager.java
+++ b/src/main/java/org/traccar/session/ConnectionManager.java
@@ -136,7 +136,9 @@ public class ConnectionManager implements BroadcastInterface {
device = addUnknownDevice(uniqueIds[0]);
}
- if (device != null && !device.getDisabled()) {
+ if (device != null) {
+ device.checkDisabled();
+
DeviceSession oldSession = sessionsByDeviceId.remove(device.getId());
if (oldSession != null) {
Endpoint oldEndpoint = new Endpoint(oldSession.getChannel(), oldSession.getRemoteAddress());
@@ -160,7 +162,7 @@ public class ConnectionManager implements BroadcastInterface {
return deviceSession;
} else {
- LOGGER.warn((device == null ? "Unknown" : "Disabled") + " device - " + String.join(" ", uniqueIds)
+ LOGGER.warn("Unknown device - " + String.join(" ", uniqueIds)
+ " (" + ((InetSocketAddress) remoteAddress).getHostString() + ")");
return null;
}
@@ -187,12 +189,14 @@ public class ConnectionManager implements BroadcastInterface {
}
}
- public void deviceDisconnected(Channel channel) {
+ public void deviceDisconnected(Channel channel, boolean supportsOffline) {
Endpoint endpoint = new Endpoint(channel, channel.remoteAddress());
Map<String, DeviceSession> endpointSessions = sessionsByEndpoint.remove(endpoint);
if (endpointSessions != null) {
for (DeviceSession deviceSession : endpointSessions.values()) {
- updateDevice(deviceSession.getDeviceId(), Device.STATUS_OFFLINE, null);
+ if (supportsOffline) {
+ updateDevice(deviceSession.getDeviceId(), Device.STATUS_OFFLINE, null);
+ }
sessionsByDeviceId.remove(deviceSession.getDeviceId());
cacheManager.removeDevice(deviceSession.getDeviceId());
}
diff --git a/src/main/java/org/traccar/storage/DatabaseStorage.java b/src/main/java/org/traccar/storage/DatabaseStorage.java
index eec72b510..8ca464147 100644
--- a/src/main/java/org/traccar/storage/DatabaseStorage.java
+++ b/src/main/java/org/traccar/storage/DatabaseStorage.java
@@ -355,41 +355,45 @@ public class DatabaseStorage extends Storage {
result.append("SELECT DISTINCT ");
if (!expandDevices) {
- result.append(groupStorageName).append('.');
+ if (outputKey.equals("groupId")) {
+ result.append("all_groups.");
+ } else {
+ result.append(groupStorageName).append('.');
+ }
}
result.append(outputKey);
result.append(" FROM ");
result.append(groupStorageName);
result.append(" INNER JOIN (");
- result.append("SELECT id as parentid, id as groupid FROM ");
+ result.append("SELECT id as parentId, id as groupId FROM ");
result.append(getStorageName(Group.class));
result.append(" UNION ");
- result.append("SELECT groupid as parentid, id as groupid FROM ");
+ result.append("SELECT groupId as parentId, id as groupId FROM ");
result.append(getStorageName(Group.class));
- result.append(" WHERE groupid IS NOT NULL");
+ result.append(" WHERE groupId IS NOT NULL");
result.append(" UNION ");
- result.append("SELECT g2.groupid as parentid, g1.id as groupid FROM ");
+ result.append("SELECT g2.groupId as parentId, g1.id as groupId FROM ");
result.append(getStorageName(Group.class));
result.append(" AS g2");
result.append(" INNER JOIN ");
result.append(getStorageName(Group.class));
- result.append(" AS g1 ON g2.id = g1.groupid");
- result.append(" WHERE g2.groupid IS NOT NULL");
+ result.append(" AS g1 ON g2.id = g1.groupId");
+ result.append(" WHERE g2.groupId IS NOT NULL");
result.append(") AS all_groups ON ");
result.append(groupStorageName);
- result.append(".groupid = all_groups.parentid");
+ result.append(".groupId = all_groups.parentId");
if (expandDevices) {
result.append(" INNER JOIN (");
- result.append("SELECT groupid as parentid, id as deviceid FROM ");
+ result.append("SELECT groupId as parentId, id as deviceId FROM ");
result.append(getStorageName(Device.class));
- result.append(" WHERE groupid IS NOT NULL");
- result.append(") AS devices ON all_groups.groupid = devices.parentid");
+ result.append(" WHERE groupId IS NOT NULL");
+ result.append(") AS devices ON all_groups.groupId = devices.parentId");
}
result.append(" WHERE ");
- result.append(conditionKey); // TODO handle search for device / group
+ result.append(conditionKey);
result.append(" = :");
result.append(conditionKey);
diff --git a/src/main/java/org/traccar/storage/QueryBuilder.java b/src/main/java/org/traccar/storage/QueryBuilder.java
index 910ebf170..a58ebe2b4 100644
--- a/src/main/java/org/traccar/storage/QueryBuilder.java
+++ b/src/main/java/org/traccar/storage/QueryBuilder.java
@@ -288,7 +288,8 @@ public final class QueryBuilder {
Method[] methods = object.getClass().getMethods();
for (Method method : methods) {
- if (method.getName().startsWith("get") && method.getParameterTypes().length == 0) {
+ if (method.getName().startsWith("get") && method.getParameterTypes().length == 0
+ && !method.getName().equals("getClass")) {
String name = method.getName().substring(3);
try {
if (method.getReturnType().equals(boolean.class)) {
diff --git a/src/main/java/org/traccar/web/WebServer.java b/src/main/java/org/traccar/web/WebServer.java
index 68eee78e7..704a4b3cd 100644
--- a/src/main/java/org/traccar/web/WebServer.java
+++ b/src/main/java/org/traccar/web/WebServer.java
@@ -64,6 +64,7 @@ import java.io.IOException;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
@@ -104,9 +105,9 @@ public class WebServer implements LifecycleObject {
@Override
protected void handleErrorPage(
HttpServletRequest request, Writer writer, int code, String message) throws IOException {
- if (code == HttpStatus.NOT_FOUND_404 && request.getPathInfo().startsWith("/modern")) {
- writer.write(Files.readString(
- Paths.get(config.getString(Keys.WEB_PATH), "modern", "index.html")));
+ Path index = Paths.get(config.getString(Keys.WEB_PATH), "index.html");
+ if (code == HttpStatus.NOT_FOUND_404 && Files.exists(index)) {
+ writer.write(Files.readString(index));
} else {
writer.write("<!DOCTYPE><html><head><title>Error</title></head><html><body>"
+ code + " - " + HttpStatus.getMessage(code) + "</body></html>");
diff --git a/src/test/java/org/traccar/protocol/Gps103ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gps103ProtocolDecoderTest.java
index 425fcd8ae..cf5786d75 100644
--- a/src/test/java/org/traccar/protocol/Gps103ProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/Gps103ProtocolDecoderTest.java
@@ -11,6 +11,10 @@ public class Gps103ProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new Gps103ProtocolDecoder(null));
+ verifyAttribute(decoder, text(
+ "imei:865456055519122,sensor alarm,2208011920,,L,;"),
+ Position.KEY_ALARM, Position.ALARM_VIBRATION);
+
verifyPosition(decoder, text(
"imei:864035050002451,tracker,201223064947,,F,064947,A,1935.70640,N,09859.94436,W,0.025,;"));
@@ -155,7 +159,7 @@ public class Gps103ProtocolDecoderTest extends ProtocolTest {
"359586015829802"));
// No GPS signal
- verifyNull(decoder, text(
+ verifyNotNull(decoder, text(
"imei:359586015829802,tracker,000000000,13554900601,L,;"));
verifyPosition(decoder, text(
diff --git a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java
index e8eecae96..072c19942 100644
--- a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java
@@ -12,8 +12,12 @@ public class StartekProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new StartekProtocolDecoder(null));
verifyAttribute(decoder, text(
+ "&&x164,869926040743375,000,0,,220705205955,A,33.326001,44.445318,10,1.2,0,57,8,925,418|40|038C|000083CD,31,00000015,00,00,0016|016A|0000|0000,1,,,686|33||44|99|14|124|11|8D"),
+ Position.KEY_FUEL_CONSUMPTION, 1.1);
+
+ verifyAttribute(decoder, text(
"&&R187,860294046453690,000,0,,220105160656,A,22.994986,72.499711,15,0.9,2,222,55,121135784,404|98|147B|0000376A,24,0000001F,02,00,052E|01A3|0000|0000,1,010000|020000,,853|6|10|105|73|41|125|34|52"),
- Position.KEY_FUEL_LEVEL, 52);
+ Position.KEY_FUEL_LEVEL, null);
verifyPosition(decoder, text(
"&&o142,860262050066062,000,27,,211111070826,V,28.653435,-106.077455,0,0.0,0,151,1412,918,0|0|4708|01402D19,6,0000001A,02,00,04C0|016C|0000|0000,1,,,BB"));
diff --git a/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java
index 3464a6fee..8d32eebb1 100644
--- a/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java
@@ -53,7 +53,7 @@ public class UproProtocolDecoderTest extends ProtocolTest {
verifyPosition(decoder, buffer(
"*MG201693502000034964,AB&A0800253335360507036975710000091116&P0730000032d2a94d&B0000000000&N13&Z12&U_P\0\0\0\u0004\0\0\0\0\0\0\0\0\0\0"),
- position("2016-11-09 08:00:25.000", true, -33.58934, -70.61626));
+ position("2016-11-09 08:00:25.000", false, -33.58934, -70.61626));
verifyNull(decoder, buffer(
"*MG20113800138000,AH"));
@@ -69,7 +69,7 @@ public class UproProtocolDecoderTest extends ProtocolTest {
verifyPosition(decoder, buffer(
"*AI200905300036,AH&A0317264913209801844913060000251115&B0500000000&C0;4?72:9&F0000"),
- position("2015-11-25 03:17:26.000", false, 49.22016, 18.74855));
+ position("2015-11-25 03:17:26.000", true, 49.22016, 18.74855));
verifyPosition(decoder, buffer(
"*AI2000905300036,AS&A1647304913209801844913060000251115&B0400000000&C0;4?72:9&F0000"));
diff --git a/src/test/java/org/traccar/protocol/Xexun2ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Xexun2ProtocolDecoderTest.java
index 840c38b52..48ba1a691 100644
--- a/src/test/java/org/traccar/protocol/Xexun2ProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/Xexun2ProtocolDecoderTest.java
@@ -25,6 +25,10 @@ public class Xexun2ProtocolDecoderTest extends ProtocolTest {
verifyPositions(decoder, binary(
"FAAF00140CF18626490454584530002BF2DD0200130013D360EFD7F514006402010D46322C4A450BA026D460EFD7FA14006402010D46322C4A450BA026FAAF"));
+ verifyPositions(decoder, binary(
+ "FAAF0014000C8622050512345670002DF3A001002A0062D9047400005E0280001E47001B400D4BA732DF505E40B4153AAF78FEF00109000000000042B36666FAAF"),
+ position("2022-07-21 07:47:00.000", true, 51.68715, 0.06103));
+
}
}
diff --git a/templates/full/passwordReset.vm b/templates/full/passwordReset.vm
index fe692ba1d..d380790dc 100644
--- a/templates/full/passwordReset.vm
+++ b/templates/full/passwordReset.vm
@@ -3,6 +3,6 @@
<html>
<body>
To reset password please click on the following link:<br>
-<a href="$webUrl?passwordReset=$token">$webUrl?passwordReset=$token</a><br>
+<a href="$webUrl/reset-password?passwordReset=$token">$webUrl/reset-password?passwordReset=$token</a><br>
</body>
</html>