diff --git a/schema/changelog-5.11.xml b/schema/changelog-5.11.xml
new file mode 100644
index 00000000000..e59df924988
--- /dev/null
+++ b/schema/changelog-5.11.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/schema/changelog-master.xml b/schema/changelog-master.xml
index 183b3fd9323..559d90923d9 100644
--- a/schema/changelog-master.xml
+++ b/schema/changelog-master.xml
@@ -41,5 +41,6 @@
+
diff --git a/src/main/java/org/traccar/BaseProtocolDecoder.java b/src/main/java/org/traccar/BaseProtocolDecoder.java
index 69ca0ccc65b..97762e8ca38 100644
--- a/src/main/java/org/traccar/BaseProtocolDecoder.java
+++ b/src/main/java/org/traccar/BaseProtocolDecoder.java
@@ -125,12 +125,13 @@ protected TimeZone getTimeZone(long deviceId) {
}
protected TimeZone getTimeZone(long deviceId, String defaultTimeZone) {
- TimeZone result = TimeZone.getTimeZone(defaultTimeZone);
String timeZoneName = AttributeUtil.lookup(cacheManager, Keys.DECODER_TIMEZONE, deviceId);
if (timeZoneName != null) {
- result = TimeZone.getTimeZone(timeZoneName);
+ return TimeZone.getTimeZone(timeZoneName);
+ } else if (defaultTimeZone != null) {
+ return TimeZone.getTimeZone(defaultTimeZone);
}
- return result;
+ return null;
}
public DeviceSession getDeviceSession(Channel channel, SocketAddress remoteAddress, String... uniqueIds) {
diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java
index 61a70bac030..ebc40a9b10b 100644
--- a/src/main/java/org/traccar/api/resource/DeviceResource.java
+++ b/src/main/java/org/traccar/api/resource/DeviceResource.java
@@ -15,12 +15,15 @@
*/
package org.traccar.api.resource;
+import jakarta.ws.rs.FormParam;
import org.traccar.api.BaseObjectResource;
+import org.traccar.api.signature.TokenManager;
import org.traccar.broadcast.BroadcastService;
import org.traccar.database.MediaManager;
import org.traccar.helper.LogAction;
import org.traccar.model.Device;
import org.traccar.model.DeviceAccumulators;
+import org.traccar.model.Permission;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.session.ConnectionManager;
@@ -46,7 +49,9 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.util.Collection;
+import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -67,6 +72,9 @@ public class DeviceResource extends BaseObjectResource {
@Inject
private MediaManager mediaManager;
+ @Inject
+ private TokenManager tokenManager;
+
public DeviceResource() {
super(Device.class);
}
@@ -183,4 +191,33 @@ public Response uploadImage(
return Response.status(Response.Status.NOT_FOUND).build();
}
+ @Path("share")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @POST
+ public String shareDevice(
+ @FormParam("deviceId") long deviceId,
+ @FormParam("expiration") Date expiration) throws StorageException, GeneralSecurityException, IOException {
+
+ User user = permissionsService.getUser(getUserId());
+
+ Device device = storage.getObject(Device.class, new Request(
+ new Columns.All(),
+ new Condition.And(
+ new Condition.Equals("id", deviceId),
+ new Condition.Permission(User.class, user.getId(), Device.class))));
+
+ User share = new User();
+ share.setName(device.getName());
+ share.setEmail(user.getEmail() + ":" + device.getUniqueId());
+ share.setExpirationTime(expiration);
+ share.setTemporary(true);
+ share.setReadonly(true);
+
+ share.setId(storage.addObject(share, new Request(new Columns.Exclude("id"))));
+
+ storage.addPermission(new Permission(User.class, share.getId(), Device.class, deviceId));
+
+ return tokenManager.generateToken(share.getId(), expiration);
+ }
+
}
diff --git a/src/main/java/org/traccar/api/resource/PasswordResource.java b/src/main/java/org/traccar/api/resource/PasswordResource.java
index 029e63a0c8e..22b3f0cd3ee 100644
--- a/src/main/java/org/traccar/api/resource/PasswordResource.java
+++ b/src/main/java/org/traccar/api/resource/PasswordResource.java
@@ -75,7 +75,7 @@ public Response update(
@FormParam("token") String token, @FormParam("password") String password)
throws StorageException, GeneralSecurityException, IOException {
- long userId = tokenManager.verifyToken(token);
+ long userId = tokenManager.verifyToken(token).getUserId();
User user = storage.getObject(User.class, new Request(
new Columns.All(), new Condition.Equals("id", userId)));
if (user != null) {
diff --git a/src/main/java/org/traccar/api/resource/SessionResource.java b/src/main/java/org/traccar/api/resource/SessionResource.java
index 90f0ceadedb..dc517277eb9 100644
--- a/src/main/java/org/traccar/api/resource/SessionResource.java
+++ b/src/main/java/org/traccar/api/resource/SessionResource.java
@@ -17,10 +17,10 @@
import org.traccar.api.BaseResource;
import org.traccar.api.security.CodeRequiredException;
+import org.traccar.api.security.LoginResult;
import org.traccar.api.security.LoginService;
import org.traccar.api.signature.TokenManager;
import org.traccar.database.OpenIdProvider;
-import org.traccar.helper.DataConverter;
import org.traccar.helper.LogAction;
import org.traccar.helper.WebHelper;
import org.traccar.model.User;
@@ -33,7 +33,6 @@
import jakarta.annotation.Nullable;
import jakarta.annotation.security.PermitAll;
import jakarta.inject.Inject;
-import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
@@ -49,8 +48,6 @@
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Date;
import java.net.URI;
@@ -61,8 +58,7 @@
public class SessionResource extends BaseResource {
public static final String USER_ID_KEY = "userId";
- public static final String USER_COOKIE_KEY = "user";
- public static final String PASS_COOKIE_KEY = "password";
+ public static final String EXPIRATION_KEY = "expiration";
@Inject
private LoginService loginService;
@@ -82,48 +78,22 @@ public class SessionResource extends BaseResource {
public User get(@QueryParam("token") String token) throws StorageException, IOException, GeneralSecurityException {
if (token != null) {
- User user = loginService.login(token);
+ LoginResult loginResult = loginService.login(token);
+ User user = loginResult.getUser();
if (user != null) {
request.getSession().setAttribute(USER_ID_KEY, user.getId());
+ request.getSession().setAttribute(EXPIRATION_KEY, loginResult.getExpiration());
LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request));
return user;
}
}
Long userId = (Long) request.getSession().getAttribute(USER_ID_KEY);
- if (userId == null) {
-
- Cookie[] cookies = request.getCookies();
- String email = null, password = null;
- if (cookies != null) {
- for (Cookie cookie : cookies) {
- if (cookie.getName().equals(USER_COOKIE_KEY)) {
- byte[] emailBytes = DataConverter.parseBase64(
- URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII));
- email = new String(emailBytes, StandardCharsets.UTF_8);
- } else if (cookie.getName().equals(PASS_COOKIE_KEY)) {
- byte[] passwordBytes = DataConverter.parseBase64(
- URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII));
- password = new String(passwordBytes, StandardCharsets.UTF_8);
- }
- }
- }
- if (email != null && password != null) {
- User user = loginService.login(email, password, null);
- if (user != null) {
- request.getSession().setAttribute(USER_ID_KEY, user.getId());
- LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request));
- return user;
- }
- }
-
- } else {
-
+ if (userId != null) {
User user = permissionsService.getUser(userId);
if (user != null) {
return user;
}
-
}
throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build());
@@ -148,7 +118,7 @@ public User add(
@FormParam("code") Integer code) throws StorageException {
User user;
try {
- user = loginService.login(email, password, code);
+ user = loginService.login(email, password, code).getUser();
} catch (CodeRequiredException e) {
Response response = Response
.status(Response.Status.UNAUTHORIZED)
@@ -177,6 +147,10 @@ public Response remove() {
@POST
public String requestToken(
@FormParam("expiration") Date expiration) throws StorageException, GeneralSecurityException, IOException {
+ Date currentExpiration = (Date) request.getSession().getAttribute(EXPIRATION_KEY);
+ if (currentExpiration != null && currentExpiration.before(expiration)) {
+ expiration = currentExpiration;
+ }
return tokenManager.generateToken(getUserId(), expiration);
}
diff --git a/src/main/java/org/traccar/api/security/LoginResult.java b/src/main/java/org/traccar/api/security/LoginResult.java
new file mode 100644
index 00000000000..1fccc36d1ee
--- /dev/null
+++ b/src/main/java/org/traccar/api/security/LoginResult.java
@@ -0,0 +1,29 @@
+package org.traccar.api.security;
+
+import org.traccar.model.User;
+
+import java.util.Date;
+
+public class LoginResult {
+
+ private final User user;
+ private final Date expiration;
+
+ public LoginResult(User user) {
+ this(user, null);
+ }
+
+ public LoginResult(User user, Date expiration) {
+ this.user = user;
+ this.expiration = expiration;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public Date getExpiration() {
+ return expiration;
+ }
+
+}
diff --git a/src/main/java/org/traccar/api/security/LoginService.java b/src/main/java/org/traccar/api/security/LoginService.java
index 829f5d2fa95..930c4fa46f3 100644
--- a/src/main/java/org/traccar/api/security/LoginService.java
+++ b/src/main/java/org/traccar/api/security/LoginService.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 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.
@@ -58,20 +58,20 @@ public LoginService(
forceOpenId = config.getBoolean(Keys.OPENID_FORCE);
}
- public User login(String token) throws StorageException, GeneralSecurityException, IOException {
+ public LoginResult login(String token) throws StorageException, GeneralSecurityException, IOException {
if (serviceAccountToken != null && serviceAccountToken.equals(token)) {
- return new ServiceAccountUser();
+ return new LoginResult(new ServiceAccountUser());
}
- long userId = tokenManager.verifyToken(token);
+ TokenManager.TokenData tokenData = tokenManager.verifyToken(token);
User user = storage.getObject(User.class, new Request(
- new Columns.All(), new Condition.Equals("id", userId)));
+ new Columns.All(), new Condition.Equals("id", tokenData.getUserId())));
if (user != null) {
checkUserEnabled(user);
}
- return user;
+ return new LoginResult(user, tokenData.getExpiration());
}
- public User login(String email, String password, Integer code) throws StorageException {
+ public LoginResult login(String email, String password, Integer code) throws StorageException {
if (forceOpenId) {
return null;
}
@@ -87,20 +87,20 @@ public User login(String email, String password, Integer code) throws StorageExc
|| !forceLdap && user.isPasswordValid(password)) {
checkUserCode(user, code);
checkUserEnabled(user);
- return user;
+ return new LoginResult(user);
}
} else {
if (ldapProvider != null && ldapProvider.login(email, password)) {
user = ldapProvider.getUser(email);
user.setId(storage.addObject(user, new Request(new Columns.Exclude("id"))));
checkUserEnabled(user);
- return user;
+ return new LoginResult(user);
}
}
return null;
}
- public User login(String email, String name, boolean administrator) throws StorageException {
+ public LoginResult login(String email, String name, boolean administrator) throws StorageException {
User user = storage.getObject(User.class, new Request(
new Columns.All(),
new Condition.Equals("email", email)));
@@ -115,7 +115,7 @@ public User login(String email, String name, boolean administrator) throws Stora
user.setId(storage.addObject(user, new Request(new Columns.Exclude("id"))));
}
checkUserEnabled(user);
- return user;
+ return new LoginResult(user);
}
private void checkUserEnabled(User user) throws SecurityException {
diff --git a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
index cb523177e3e..c33a800154f 100644
--- a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
+++ b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
@@ -38,6 +38,7 @@
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
+import java.util.Date;
public class SecurityRequestFilter implements ContainerRequestFilter {
@@ -82,16 +83,18 @@ public void filter(ContainerRequestContext requestContext) {
if (authHeader != null) {
try {
- User user;
+ LoginResult loginResult;
if (authHeader.startsWith("Bearer ")) {
- user = loginService.login(authHeader.substring(7));
+ loginResult = loginService.login(authHeader.substring(7));
} else {
String[] auth = decodeBasicAuth(authHeader);
- user = loginService.login(auth[0], auth[1], null);
+ loginResult = loginService.login(auth[0], auth[1], null);
}
+ User user = loginResult.getUser();
if (user != null) {
statisticsManager.registerRequest(user.getId());
- securityContext = new UserSecurityContext(new UserPrincipal(user.getId()));
+ securityContext = new UserSecurityContext(
+ new UserPrincipal(user.getId(), loginResult.getExpiration()));
}
} catch (StorageException | GeneralSecurityException | IOException e) {
throw new WebApplicationException(e);
@@ -100,12 +103,13 @@ public void filter(ContainerRequestContext requestContext) {
} else if (request.getSession() != null) {
Long userId = (Long) request.getSession().getAttribute(SessionResource.USER_ID_KEY);
+ Date expiration = (Date) request.getSession().getAttribute(SessionResource.EXPIRATION_KEY);
if (userId != null) {
User user = injector.getInstance(PermissionsService.class).getUser(userId);
if (user != null) {
user.checkDisabled();
statisticsManager.registerRequest(userId);
- securityContext = new UserSecurityContext(new UserPrincipal(userId));
+ securityContext = new UserSecurityContext(new UserPrincipal(userId, expiration));
}
}
diff --git a/src/main/java/org/traccar/api/security/UserPrincipal.java b/src/main/java/org/traccar/api/security/UserPrincipal.java
index 18b84a0e194..83bd06fe99c 100644
--- a/src/main/java/org/traccar/api/security/UserPrincipal.java
+++ b/src/main/java/org/traccar/api/security/UserPrincipal.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 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.
@@ -16,19 +16,26 @@
package org.traccar.api.security;
import java.security.Principal;
+import java.util.Date;
public class UserPrincipal implements Principal {
private final long userId;
+ private final Date expiration;
- public UserPrincipal(long userId) {
+ public UserPrincipal(long userId, Date expiration) {
this.userId = userId;
+ this.expiration = expiration;
}
public Long getUserId() {
return userId;
}
+ public Date getExpiration() {
+ return expiration;
+ }
+
@Override
public String getName() {
return null;
diff --git a/src/main/java/org/traccar/api/signature/TokenManager.java b/src/main/java/org/traccar/api/signature/TokenManager.java
index 3019e12b96e..824433b08c3 100644
--- a/src/main/java/org/traccar/api/signature/TokenManager.java
+++ b/src/main/java/org/traccar/api/signature/TokenManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 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.
@@ -35,11 +35,19 @@ public class TokenManager {
private final ObjectMapper objectMapper;
private final CryptoManager cryptoManager;
- public static class Data {
+ public static class TokenData {
@JsonProperty("u")
private long userId;
@JsonProperty("e")
private Date expiration;
+
+ public long getUserId() {
+ return userId;
+ }
+
+ public Date getExpiration() {
+ return expiration;
+ }
}
@Inject
@@ -54,7 +62,7 @@ public String generateToken(long userId) throws IOException, GeneralSecurityExce
public String generateToken(
long userId, Date expiration) throws IOException, GeneralSecurityException, StorageException {
- Data data = new Data();
+ TokenData data = new TokenData();
data.userId = userId;
if (expiration != null) {
data.expiration = expiration;
@@ -65,13 +73,13 @@ public String generateToken(
return Base64.encodeBase64URLSafeString(cryptoManager.sign(encoded));
}
- public long verifyToken(String token) throws IOException, GeneralSecurityException, StorageException {
+ public TokenData verifyToken(String token) throws IOException, GeneralSecurityException, StorageException {
byte[] encoded = cryptoManager.verify(Base64.decodeBase64(token));
- Data data = objectMapper.readValue(encoded, Data.class);
+ TokenData data = objectMapper.readValue(encoded, TokenData.class);
if (data.expiration.before(new Date())) {
throw new SecurityException("Token has expired");
}
- return data.userId;
+ return data;
}
}
diff --git a/src/main/java/org/traccar/database/OpenIdProvider.java b/src/main/java/org/traccar/database/OpenIdProvider.java
index 1f5a2f48149..93297f7ab37 100644
--- a/src/main/java/org/traccar/database/OpenIdProvider.java
+++ b/src/main/java/org/traccar/database/OpenIdProvider.java
@@ -189,7 +189,8 @@ public URI handleCallback(URI requestUri, HttpServletRequest request)
throw new GeneralSecurityException("Your OpenID Groups do not permit access to Traccar.");
}
- User user = loginService.login(userInfo.getEmailAddress(), userInfo.getName(), administrator);
+ User user = loginService.login(
+ userInfo.getEmailAddress(), userInfo.getName(), administrator).getUser();
request.getSession().setAttribute(SessionResource.USER_ID_KEY, user.getId());
LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request));
diff --git a/src/main/java/org/traccar/model/User.java b/src/main/java/org/traccar/model/User.java
index 757064ba221..8cfee0f48bb 100644
--- a/src/main/java/org/traccar/model/User.java
+++ b/src/main/java/org/traccar/model/User.java
@@ -261,6 +261,16 @@ public void setTotpKey(String totpKey) {
this.totpKey = totpKey;
}
+ private boolean temporary;
+
+ public boolean getTemporary() {
+ return temporary;
+ }
+
+ public void setTemporary(boolean temporary) {
+ this.temporary = temporary;
+ }
+
@QueryIgnore
public String getPassword() {
return null;
diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
index cf7cd12d302..7ee47dd8653 100644
--- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
@@ -480,28 +480,21 @@ private Object decodeBasic(Channel channel, SocketAddress remoteAddress, ByteBuf
buf.readUnsignedShort(); // type
deviceSession = getDeviceSession(channel, remoteAddress, imei);
- if (deviceSession != null && !deviceSession.contains(DeviceSession.KEY_TIMEZONE)) {
- deviceSession.set(DeviceSession.KEY_TIMEZONE, getTimeZone(deviceSession.getDeviceId()));
- }
-
- if (dataLength > 10) {
- int extensionBits = buf.readUnsignedShort();
- int hours = (extensionBits >> 4) / 100;
- int minutes = (extensionBits >> 4) % 100;
- int offset = (hours * 60 + minutes) * 60;
- if ((extensionBits & 0x8) != 0) {
- offset = -offset;
- }
- if (deviceSession != null) {
- TimeZone timeZone = deviceSession.get(DeviceSession.KEY_TIMEZONE);
- if (timeZone.getRawOffset() == 0) {
- timeZone.setRawOffset(offset * 1000);
- deviceSession.set(DeviceSession.KEY_TIMEZONE, timeZone);
+ if (deviceSession != null) {
+ TimeZone timeZone = getTimeZone(deviceSession.getDeviceId(), null);
+ if (timeZone == null && dataLength > 10) {
+ int extensionBits = buf.readUnsignedShort();
+ int hours = (extensionBits >> 4) / 100;
+ int minutes = (extensionBits >> 4) % 100;
+ int offset = (hours * 60 + minutes) * 60;
+ if ((extensionBits & 0x8) != 0) {
+ offset = -offset;
}
+ timeZone = TimeZone.getTimeZone("UTC");
+ timeZone.setRawOffset(offset * 1000);
}
- }
+ deviceSession.set(DeviceSession.KEY_TIMEZONE, timeZone);
- if (deviceSession != null) {
sendResponse(channel, false, type, buf.getShort(buf.writerIndex() - 6), null);
}
diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
index 6e837337354..88120912081 100644
--- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
@@ -770,12 +770,15 @@ private Position decodeLocation2(DeviceSession deviceSession, ByteBuf buf, int t
int battery = buf.readUnsignedByte();
if (battery <= 100) {
position.set(Position.KEY_BATTERY_LEVEL, battery);
- } else if (battery == 0xAA) {
+ } else if (battery == 0xAA || battery == 0xAB) {
position.set(Position.KEY_CHARGE, true);
}
- position.setNetwork(new Network(CellTower.fromCidLac(
- getConfig(), buf.readUnsignedInt(), buf.readUnsignedShort())));
+ long cid = buf.readUnsignedInt();
+ int lac = buf.readUnsignedShort();
+ if (cid > 0 && lac > 0) {
+ position.setNetwork(new Network(CellTower.fromCidLac(getConfig(), cid, lac)));
+ }
int product = buf.readUnsignedByte();
int status = buf.readUnsignedShort();
@@ -787,6 +790,9 @@ private Position decodeLocation2(DeviceSession deviceSession, ByteBuf buf, int t
}
} else if (product == 3) {
position.set(Position.KEY_BLOCKED, BitUtil.check(status, 5));
+ if (BitUtil.check(alarm, 0)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED);
+ }
if (BitUtil.check(alarm, 1)) {
position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER);
}
@@ -796,6 +802,12 @@ private Position decodeLocation2(DeviceSession deviceSession, ByteBuf buf, int t
if (BitUtil.check(alarm, 3)) {
position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY);
}
+ if (BitUtil.check(alarm, 5)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_ENTER);
+ }
+ if (BitUtil.check(alarm, 6)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_EXIT);
+ }
}
position.set(Position.KEY_STATUS, status);
@@ -807,6 +819,28 @@ private Position decodeLocation2(DeviceSession deviceSession, ByteBuf buf, int t
case 0x02:
position.setAltitude(buf.readShort());
break;
+ case 0x10:
+ position.set("wakeSource", buf.readUnsignedByte());
+ break;
+ case 0x0A:
+ if (length == 3) {
+ buf.readUnsignedShort(); // mcc
+ buf.readUnsignedByte(); // mnc
+ } else {
+ buf.skipBytes(length);
+ }
+ break;
+ case 0x0B:
+ position.set("lockCommand", buf.readUnsignedByte());
+ if (length >= 5 && length <= 6) {
+ position.set("lockCard", buf.readUnsignedInt());
+ } else if (length >= 7) {
+ position.set("lockPassword", buf.readCharSequence(6, StandardCharsets.US_ASCII).toString());
+ }
+ if (length % 2 == 0) {
+ position.set("unlockResult", buf.readUnsignedByte());
+ }
+ break;
case 0x0C:
int x = buf.readUnsignedShort();
if (x > 0x8000) {
@@ -822,6 +856,9 @@ private Position decodeLocation2(DeviceSession deviceSession, ByteBuf buf, int t
}
position.set("tilt", String.format("[%d,%d,%d]", x, y, z));
break;
+ case 0xFC:
+ position.set(Position.KEY_GEOFENCE, buf.readUnsignedByte());
+ break;
default:
buf.skipBytes(length);
break;
diff --git a/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java
index 1b6d471b4c3..eca7e2d1127 100644
--- a/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java
@@ -105,15 +105,9 @@ static void decodeBinaryLocation(ByteBuf buf, Position position) {
double longitude = convertCoordinate(BcdUtil.readInteger(buf, 9));
byte flags = buf.readByte();
- position.setValid((flags & 0x1) == 0x1);
- if ((flags & 0x2) == 0) {
- latitude = -latitude;
- }
- position.setLatitude(latitude);
- if ((flags & 0x4) == 0) {
- longitude = -longitude;
- }
- position.setLongitude(longitude);
+ position.setValid(BitUtil.check(flags, 0));
+ position.setLatitude(BitUtil.check(flags, 1) ? latitude : -latitude);
+ position.setLongitude(BitUtil.check(flags, 2) ? longitude : -longitude);
position.setSpeed(BcdUtil.readInteger(buf, 2));
position.setCourse(buf.readUnsignedByte() * 2.0);
diff --git a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java
index fcc96fbf10d..d53675b7fcd 100644
--- a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2023 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.
@@ -42,7 +42,7 @@ public RstProtocolDecoder(Protocol protocol) {
.expression("(.{5});") // firmware
.number("(d{9});") // serial number
.number("(d+);") // index
- .number("(d+);") // type
+ .number("d+;") // type
.groupBegin()
.number("(dd)-(dd)-(dddd) ") // event date
.number("(dd):(dd):(dd);") // event time
@@ -87,7 +87,6 @@ protected Object decode(
String firmware = parser.next();
String serial = parser.next();
int index = parser.nextInt();
- int type = parser.nextInt();
if (channel != null) {
String response = "RST;A;" + model + ";" + firmware + ";" + serial + ";" + index + ";6;FIM;";
@@ -115,9 +114,16 @@ protected Object decode(
position.set(Position.KEY_SATELLITES, parser.nextInt());
position.set(Position.KEY_HDOP, parser.nextInt());
- position.set(Position.PREFIX_IN + 1, parser.nextHexInt());
- position.set(Position.PREFIX_IN + 2, parser.nextHexInt());
- position.set(Position.PREFIX_IN + 3, parser.nextHexInt());
+
+ int inputs1 = parser.nextHexInt();
+ int inputs2 = parser.nextHexInt();
+ int inputs3 = parser.nextHexInt();
+ position.set(Position.PREFIX_IN + 1, inputs1);
+ position.set(Position.PREFIX_IN + 2, inputs2);
+ position.set(Position.PREFIX_IN + 3, inputs3);
+
+ position.set(Position.KEY_IGNITION, BitUtil.check(inputs2, 7));
+
position.set(Position.PREFIX_OUT + 1, parser.nextHexInt());
position.set(Position.PREFIX_OUT + 2, parser.nextHexInt());
position.set(Position.KEY_POWER, parser.nextDouble());
@@ -125,10 +131,7 @@ protected Object decode(
position.set(Position.KEY_ODOMETER, parser.nextInt());
position.set(Position.KEY_RSSI, parser.nextInt());
position.set(Position.PREFIX_TEMP + 1, (int) parser.nextHexInt().byteValue());
-
- int status = (parser.nextHexInt() << 8) + parser.nextHexInt();
- position.set(Position.KEY_IGNITION, BitUtil.check(status, 7));
- position.set(Position.KEY_STATUS, status);
+ position.set(Position.KEY_STATUS, (parser.nextHexInt() << 8) + parser.nextHexInt());
return position;
diff --git a/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java
index b18359b3f2f..9e7518ce5a7 100644
--- a/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java
@@ -41,7 +41,8 @@ public T55ProtocolDecoder(Protocol protocol) {
}
private static final Pattern PATTERN_GPRMC = new PatternBuilder()
- .text("$GPRMC,")
+ .text("$")
+ .expression("G[PLN]RMC,")
.number("(dd)(dd)(dd).?d*,") // time (hhmmss)
.expression("([AV]),") // validity
.number("(dd)(dd.d+),") // latitude
@@ -64,7 +65,8 @@ public T55ProtocolDecoder(Protocol protocol) {
.compile();
private static final Pattern PATTERN_GPGGA = new PatternBuilder()
- .text("$GPGGA,")
+ .text("$")
+ .expression("G[PLN]GGA,")
.number("(dd)(dd)(dd).?d*,") // time (hhmmss)
.number("(d+)(dd.d+),") // latitude
.expression("([NS]),")
@@ -444,9 +446,9 @@ protected Object decode(
}
} else if (sentence.matches("^[0-9A-F]+$")) {
getDeviceSession(channel, remoteAddress, sentence);
- } else if (sentence.startsWith("$GPRMC")) {
+ } else if (sentence.startsWith("RMC", 3)) {
return decodeGprmc(deviceSession, sentence, remoteAddress, channel);
- } else if (sentence.startsWith("$GPGGA") && deviceSession != null) {
+ } else if (sentence.startsWith("GGA", 3) && deviceSession != null) {
return decodeGpgga(deviceSession, sentence);
} else if (sentence.startsWith("$GPRMA") && deviceSession != null) {
return decodeGprma(deviceSession, sentence);
diff --git a/src/main/java/org/traccar/schedule/ScheduleManager.java b/src/main/java/org/traccar/schedule/ScheduleManager.java
index 07cdb1fe1a8..38e8f281c41 100644
--- a/src/main/java/org/traccar/schedule/ScheduleManager.java
+++ b/src/main/java/org/traccar/schedule/ScheduleManager.java
@@ -39,6 +39,7 @@ public ScheduleManager(Injector injector) {
public void start() {
executor = Executors.newSingleThreadScheduledExecutor();
var tasks = List.of(
+ TaskDeleteTemporary.class,
TaskReports.class,
TaskDeviceInactivityCheck.class,
TaskWebSocketKeepalive.class,
diff --git a/src/main/java/org/traccar/schedule/TaskDeleteTemporary.java b/src/main/java/org/traccar/schedule/TaskDeleteTemporary.java
new file mode 100644
index 00000000000..0cead59fbae
--- /dev/null
+++ b/src/main/java/org/traccar/schedule/TaskDeleteTemporary.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2023 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.schedule;
+
+import jakarta.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.model.User;
+import org.traccar.storage.Storage;
+import org.traccar.storage.StorageException;
+import org.traccar.storage.query.Condition;
+import org.traccar.storage.query.Request;
+
+import java.util.Date;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class TaskDeleteTemporary implements ScheduleTask {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(TaskDeleteTemporary.class);
+
+ private static final long CHECK_PERIOD_HOURS = 1;
+
+ private final Storage storage;
+
+ @Inject
+ public TaskDeleteTemporary(Storage storage) {
+ this.storage = storage;
+ }
+
+ @Override
+ public void schedule(ScheduledExecutorService executor) {
+ executor.scheduleAtFixedRate(this, CHECK_PERIOD_HOURS, CHECK_PERIOD_HOURS, TimeUnit.HOURS);
+ }
+
+ @Override
+ public void run() {
+ try {
+ storage.removeObject(User.class, new Request(
+ new Condition.And(
+ new Condition.Equals("temporary", true),
+ new Condition.Compare("expirationTime", "<", "time", new Date()))));
+ } catch (StorageException e) {
+ LOGGER.warn("Failed to delete temporary users", e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/schedule/TaskReports.java b/src/main/java/org/traccar/schedule/TaskReports.java
index 30f20f4373e..e0fa6f8d678 100644
--- a/src/main/java/org/traccar/schedule/TaskReports.java
+++ b/src/main/java/org/traccar/schedule/TaskReports.java
@@ -51,7 +51,7 @@ public class TaskReports implements ScheduleTask {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskReports.class);
- private static final long CHECK_PERIOD_MINUTES = 1;
+ private static final long CHECK_PERIOD_MINUTES = 15;
private final Storage storage;
private final Injector injector;
diff --git a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
index 22c34c7ac71..164635109d0 100644
--- a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
@@ -11,6 +11,16 @@ public void testDecode() throws Exception {
var decoder = inject(new HuabaoProtocolDecoder(null));
+ verifyAttribute(decoder, binary(
+ "7e55018c418560090010174701022106242122348476113550490700001c06000000074e0000000000000308100000100102020200000a030000000b0803363839363037650c0600000001000afa7e"),
+ "unlockResult", 0x65);
+
+ verifyPosition(decoder, binary(
+ "7e55018c378580120300032a06052117594022348474113550560705981e0400000002370ac30c0c28660308000000100101020200000a0301cc000c0600a2ffa7ff5e1b7e"));
+
+ verifyPosition(decoder, binary(
+ "7e55028c37850011000200c008052106305122348621113550170700001e080000000aaa0000000000000300000000100100020200140a030000000c06003bffa8ffc3c77e"));
+
verifyAttribute(decoder, binary(
"7E020000C2000000001862003F0000000400108042015A322206C869480017007B012E230918081550661D01CC1A0000099DC25727B0130000099DC26B27B00100000C40F89427B0711438393836303436393130323139303037383135336A0114503FF8AF05826BC8CA24E124F732B7C780C5484D6318C0A840412262D4BCEC26CA0234CCB85455D5114F12B650FA841FBEC0B03C67A21F501CAE6C384595028DA76B010060110065000012345678903930303137333833630B0012345678900E5C3080006804000003D46902016F6D7E"),
Position.KEY_DRIVER_UNIQUE_ID, "\u00909001738");
diff --git a/src/test/java/org/traccar/protocol/RstProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/RstProtocolDecoderTest.java
index 13095765b8a..0e8aefe518e 100644
--- a/src/test/java/org/traccar/protocol/RstProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/RstProtocolDecoderTest.java
@@ -14,6 +14,10 @@ public void testDecode() throws Exception {
verifyNull(decoder, text(
"RST;A;RST-MINIv2;V7.04;008051261;124;29;04-04-2021 17:27:26;04-04-2021 17:27:26;-1.280811;-47.931755;7353;79;1;14;7315;26;10;0;1855;0;0;0;0;5;5;-1.280821;-47.931747;04-04-2021 17:52:23;6;-1.280863;-47.931770;04-04-2021 18:12:19;5;-1.280844;-47.931763;04-04-2021 17:28:02;5;-1.280900;-47.931770;04-04-2021 19:04:27;4;-1.280843;-47.931747;04-04-2021 18:21:45;04-04-2021 19:29:59;04-04-2021 19:29:59;-1.280770;-47.931595;1;15;0;0;0;0;FIM;"));
+ verifyAttribute(decoder, text(
+ "RST;A;RST-MINI-4Gv3;V9.10;009521405;13;1;21-11-2023 20:04:18;21-11-2023 20:04:18;-12.923627;-38.388287;1;165;29;1;5;2;00;B0;00;1A;02;11.89;3.90;73;31;FE;0000;01;40;00800061;0;184;2;4;4;6;434.0000;2;0;-49;1815;37391;724;255;263;00000000;FIM;"),
+ Position.KEY_IGNITION, true);
+
verifyPosition(decoder, text(
"RST;L;RST-MINIv2;V7.02;008068078;61;1;27-01-2020 21:36:33;27-01-2020 21:36:33;-16.696159;-49.284275;0;67;786;1;15;0;00;B0;00;19;06;12.42;4.16;79;20;FE;0000;01;E0;00800020;0;467;FIM;"));
diff --git a/src/test/java/org/traccar/protocol/T55ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/T55ProtocolDecoderTest.java
index 7b9841d6819..91020714cfd 100644
--- a/src/test/java/org/traccar/protocol/T55ProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/T55ProtocolDecoderTest.java
@@ -17,6 +17,15 @@ public void testDecode() throws Exception {
verifyPosition(decoder, text(
"$PUBX,00,130209.00,3650.51159,N,01346.10602,E,785.947,D3,4.1,5.2,0.163,87.43,-0.054,7.0,0.88,1.21,0.88,24,01012,0*6D"));
+ verifyPosition(decoder, text(
+ "$GNRMC,164414.90,A,4650.5156500,N,01246.1059604,E,0.018,,091123,,,A,V*15"));
+
+ verifyPosition(decoder, text(
+ "$GNGGA,164414.90,4650.5156500,N,01246.1059604,E,1,12,0.84,740.729,M,44.804,M,,*4E"));
+
+ verifyNull(decoder, text(
+ "$GNGLL,4650.5156500,N,01246.1059604,E,164414.90,A,A*77"));
+
verifyPosition(decoder, text(
"QZE,868994033976700,35,28062020,113553,22.13673,114.57263,0,22,A,0"));