Browse Source

lwm2m: add observation, composite observation, cancel observation

pull/9903/head
nick 3 years ago
parent
commit
e03005d780
  1. 281
      application/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java
  2. 12
      application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java
  3. 16
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  4. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java
  5. 3
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  6. 41
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java
  7. 40
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java
  8. 34
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java
  9. 446
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java
  10. 5
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java
  11. 283
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java
  12. 2
      application/src/test/resources/lwm2m/5.xml
  13. 2
      application/src/test/resources/lwm2m/9.xml
  14. 2
      common/transport/lwm2m/pom.xml
  15. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  16. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java
  17. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  18. 21
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  19. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java
  20. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  21. 111
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java
  22. 10
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java
  23. 40
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MCancelObserveCompositeCallback.java
  24. 32
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MCancelObserveCompositeRequest.java
  25. 39
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MObserveCompositeCallback.java
  26. 51
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MObserveCompositeRequest.java
  27. 55
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java
  28. 33
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java
  29. 37
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcCancelObserveCompositeCallback.java
  30. 40
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcObserveResponseCompositeCallback.java
  31. 577
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java
  32. 326
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java
  33. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java
  34. 29
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
  35. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java
  36. 94
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java

281
application/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java

@ -0,0 +1,281 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* 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.eclipse.leshan.server.observation;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.observation.CompositeObservation;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.observation.SingleObservation;
import org.eclipse.leshan.core.peer.LwM2mPeer;
import org.eclipse.leshan.core.response.ObserveCompositeResponse;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint;
import org.eclipse.leshan.server.endpoint.LwM2mServerEndpointsProvider;
import org.eclipse.leshan.server.profile.ClientProfile;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.registration.RegistrationStore;
import org.eclipse.leshan.server.registration.RegistrationUpdate;
import org.eclipse.leshan.server.registration.UpdatedRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Implementation of the {@link ObservationService} accessing the persisted observation via the provided
* {@link RegistrationStore}.
*
* When a new observation is added or changed or canceled, the registered listeners are notified.
*/
@Slf4j
public class ObservationServiceImpl implements ObservationService, LwM2mNotificationReceiver {
private final Logger LOG = LoggerFactory.getLogger(ObservationServiceImpl.class);
private final RegistrationStore registrationStore;
private final LwM2mServerEndpointsProvider endpointProvider;
private final boolean updateRegistrationOnNotification;
private final List<ObservationListener> listeners = new CopyOnWriteArrayList<>();;
/**
* Creates an instance of {@link ObservationServiceImpl}
*/
public ObservationServiceImpl(RegistrationStore store, LwM2mServerEndpointsProvider endpointProvider) {
this(store, endpointProvider, false);
}
/**
* Creates an instance of {@link ObservationServiceImpl}
*
* @param updateRegistrationOnNotification will activate registration update on observe notification.
*
* @since 1.1
*/
public ObservationServiceImpl(RegistrationStore store, LwM2mServerEndpointsProvider endpointProvider,
boolean updateRegistrationOnNotification) {
this.registrationStore = store;
this.updateRegistrationOnNotification = updateRegistrationOnNotification;
this.endpointProvider = endpointProvider;
}
@Override
public int cancelObservations(Registration registration) {
// check registration id
String registrationId = registration.getId();
if (registrationId == null)
return 0;
Collection<Observation> observations = registrationStore.removeObservations(registrationId);
if (observations == null)
return 0;
for (Observation observation : observations) {
cancel(observation);
}
return observations.size();
}
@Override
public int cancelObservations(Registration registration, String nodePath) {
if (registration == null || registration.getId() == null || nodePath == null || nodePath.isEmpty())
return 0;
Set<Observation> observations = getObservationsForCancel(registration.getId(), nodePath);
for (Observation observation : observations) {
cancelObservation(observation);
}
return observations.size();
}
@Override
public int cancelCompositeObservations(Registration registration, String[] nodePaths) {
if (registration == null || registration.getId() == null || nodePaths == null || nodePaths.length == 0)
return 0;
Set<Observation> observations = getCompositeObservationsForCancel(registration.getId(), nodePaths);
for (Observation observation : observations) {
cancelObservation(observation);
}
return observations.size();
}
@Override
public void cancelObservation(Observation observation) {
if (observation == null)
return;
registrationStore.removeObservation(observation.getRegistrationId(), observation.getId());
cancel(observation);
}
private void cancel(Observation observation) {
List<LwM2mServerEndpoint> endpoints = endpointProvider.getEndpoints();
for (LwM2mServerEndpoint lwM2mEndpoint : endpoints) {
lwM2mEndpoint.cancelObservation(observation);
}
for (ObservationListener listener : listeners) {
listener.cancelled(observation);
}
}
@Override
public Set<Observation> getObservations(Registration registration) {
return getObservations(registration.getId());
}
private Set<Observation> getObservations(String registrationId) {
if (registrationId == null)
return Collections.emptySet();
return new HashSet<>(registrationStore.getObservations(registrationId));
}
private Set<Observation> getCompositeObservationsForCancel(String registrationId, String[] nodePaths) {
if (registrationId == null || nodePaths == null)
return Collections.emptySet();
// array of String to array of LWM2M path
List<LwM2mPath> lwPaths = new ArrayList<>(nodePaths.length);
for (int i = 0; i < nodePaths.length; i++) {
lwPaths.add(new LwM2mPath(nodePaths[i]));
}
// search composite-observation
Set<Observation> result = new HashSet<>();
for (Observation obs : getObservations(registrationId)) {
if (obs instanceof CompositeObservation) {
if (lwPaths.equals(((CompositeObservation) obs).getPaths())) {
result.add(obs);
}
}
}
return result;
}
private Set<Observation> getObservationsForCancel(String registrationId, String nodePath) {
if (registrationId == null || nodePath == null)
return Collections.emptySet();
Set<Observation> result = new HashSet<>();
LwM2mPath lwPath = new LwM2mPath(nodePath);
for (Observation obs : getObservations(registrationId)) {
if (obs instanceof SingleObservation) {
LwM2mPath lwPathObs = ((SingleObservation) obs).getPath();
if (lwPath.equals(lwPathObs) || lwPathObs.startWith(lwPath)) { // nodePath = "3", lwPathObs = "3/0/9": cancel for tne all lwPathObs
result.add(obs);
} else if (!lwPath.equals(lwPathObs) && lwPath.startWith(lwPathObs)) { // nodePath = "3/0/9", lwPathObs = "3": error...
String errorMsg = String.format(
"Unexpected error: There is registration with id %s for observation path %s, that includes this observation path %s",
registrationId, lwPath, lwPathObs);
throw new IllegalStateException(errorMsg);
}
}
}
return result;
}
@Override
public void addListener(ObservationListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(ObservationListener listener) {
listeners.remove(listener);
}
private Registration updateRegistrationOnRegistration(Observation observation, LwM2mPeer sender,
ClientProfile profile) {
if (updateRegistrationOnNotification) {
RegistrationUpdate regUpdate = new RegistrationUpdate(observation.getRegistrationId(), sender, null, null,
null, null, null, null, null, null, null, null);
UpdatedRegistration updatedRegistration = registrationStore.updateRegistration(regUpdate);
if (updatedRegistration == null || updatedRegistration.getUpdatedRegistration() == null) {
String errorMsg = String.format(
"Unexpected error: There is no registration with id %s for this observation %s",
observation.getRegistrationId(), observation);
LOG.error(errorMsg);
throw new IllegalStateException(errorMsg);
}
return updatedRegistration.getUpdatedRegistration();
}
return profile.getRegistration();
}
// ********** NotificationListener interface **********//
@Override
public void onNotification(SingleObservation observation, LwM2mPeer sender, ClientProfile profile,
ObserveResponse response) {
try {
Registration updatedRegistration = updateRegistrationOnRegistration(observation, sender, profile);
for (ObservationListener listener : listeners) {
listener.onResponse(observation, updatedRegistration, response);
}
} catch (Exception e) {
for (ObservationListener listener : listeners) {
listener.onError(observation, profile.getRegistration(), e);
}
}
}
@Override
public void onNotification(CompositeObservation observation, LwM2mPeer sender, ClientProfile profile,
ObserveCompositeResponse response) {
try {
Registration updatedRegistration = updateRegistrationOnRegistration(observation, sender, profile);
for (ObservationListener listener : listeners) {
listener.onResponse(observation, updatedRegistration, response);
}
} catch (Exception e) {
for (ObservationListener listener : listeners) {
listener.onError(observation, profile.getRegistration(), e);
}
}
}
@Override
public void onError(Observation observation, LwM2mPeer sender, ClientProfile profile, Exception error) {
for (ObservationListener listener : listeners) {
listener.onError(observation, profile.getRegistration(), error);
}
}
@Override
public void newObservation(Observation observation, Registration registration) {
for (ObservationListener listener : listeners) {
listener.newObservation(observation, registration);
}
}
@Override
public void cancelled(Observation observation) {
for (ObservationListener listener : listeners) {
listener.cancelled(observation);
}
}
}

12
application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java

@ -198,16 +198,16 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest {
Assert.assertEquals("PUBLIC_KEY", noSecLwM2MBootstrapServerCredential.getServerPublicKey());
Assert.assertEquals(Integer.valueOf(123), noSecLwM2MBootstrapServerCredential.getShortServerId());
Assert.assertTrue(noSecLwM2MBootstrapServerCredential.isBootstrapServerIs());
Assert.assertFalse(noSecLwM2MBootstrapServerCredential.isBootstrapServerIs());
Assert.assertEquals("localhost", noSecLwM2MBootstrapServerCredential.getHost());
Assert.assertEquals(Integer.valueOf(5687), noSecLwM2MBootstrapServerCredential.getPort());
Assert.assertEquals(Integer.valueOf(5685), noSecLwM2MBootstrapServerCredential.getPort());
TelemetryMappingConfiguration observeAttr = transportConfiguration.getObserveAttr();
Assert.assertEquals("batteryLevel", observeAttr.getKeyName().get("/3_1.0/0/9"));
Assert.assertEquals("batteryLevel", observeAttr.getKeyName().get("/3_1.2/0/9"));
Assert.assertTrue(observeAttr.getObserve().isEmpty());
Assert.assertTrue(observeAttr.getAttribute().isEmpty());
Assert.assertFalse(observeAttr.getTelemetry().isEmpty());
Assert.assertTrue(observeAttr.getTelemetry().contains("/3_1.0/0/9"));
Assert.assertTrue(observeAttr.getTelemetry().contains("/3_1.2/0/9"));
Assert.assertTrue(observeAttr.getAttributeLwm2m().isEmpty());
removeDeviceProfileAndDoBasicAssert(deviceProfile);
@ -366,9 +366,9 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest {
AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential();
bootstrapServerCredential.setServerPublicKey("PUBLIC_KEY");
bootstrapServerCredential.setShortServerId(123);
bootstrapServerCredential.setBootstrapServerIs(true);
bootstrapServerCredential.setBootstrapServerIs(false);
bootstrapServerCredential.setHost("localhost");
bootstrapServerCredential.setPort(5687);
bootstrapServerCredential.setPort(5685);
bootstrap.add(bootstrapServerCredential);
transportConfiguration.setBootstrap(bootstrap);

16
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -145,8 +145,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
" \"telemetry\": [],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
public static final String OBSERVE_ATTRIBUTES_WITH_PARAMS =
public static String OBSERVE_ATTRIBUTES_WITH_PARAMS =
" {\n" +
" \"keyName\": {\n" +
@ -403,17 +402,24 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
}
protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception {
await("ObserveReadAll after start client: countObserve " + cntObserve)
await("ObserveReadAll after start client/test: countObserve " + cntObserve)
.atMost(40, TimeUnit.SECONDS)
.until(() -> cntObserve == getCntObserveAll(deviceIdStr));
}
protected int getCntObserveAll(String deviceIdStr) throws Exception {
protected Integer getCntObserveAll(String deviceIdStr) throws Exception {
String actualResultBefore = sendObserve("ObserveReadAll", null, deviceIdStr);
ObjectNode rpcActualResultBefore = JacksonUtil.fromString(actualResultBefore, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText());
JsonElement element = JsonUtils.parse(rpcActualResultBefore.get("value").asText());
return element.isJsonArray() ? ((JsonArray)element).size() : 0;
return element.isJsonArray() ? ((JsonArray)element).size() : null;
}
protected void sendCancelObserveAllWithAwait(String deviceIdStr) throws Exception {
String actualResultCancelAll = sendObserve("ObserveCancelAll", null, deviceIdStr);
ObjectNode rpcActualResultCancelAll = JacksonUtil.fromString(actualResultCancelAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancelAll.get("result").asText());
awaitObserveReadAll(0, deviceId);
}
protected String sendObserve(String method, String params, String deviceIdStr) throws Exception {

2
application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java

@ -34,6 +34,7 @@ public class Lwm2mTestHelper {
public static final int RESOURCE_ID_2 = 2;
public static final int RESOURCE_ID_3 = 3;
public static final int RESOURCE_ID_4 = 4;
public static final int RESOURCE_ID_5 = 5;
public static final int RESOURCE_ID_6 = 6;
public static final int RESOURCE_ID_7 = 7;
public static final int RESOURCE_ID_8 = 8;
@ -41,6 +42,7 @@ public class Lwm2mTestHelper {
public static final int RESOURCE_ID_11 = 11;
public static final int RESOURCE_ID_14 = 14;
public static final int RESOURCE_ID_15 = 15;
public static final int RESOURCE_INSTANCE_ID_0 = 0;
public static final int RESOURCE_INSTANCE_ID_2 = 2;
public static final String RESOURCE_ID_NAME_3_9 = "batteryLevel";

3
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java

@ -58,7 +58,8 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
executorService.scheduleWithFixedDelay(() -> {
fireResourceChange(9);
}
, 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
, 1, 1, TimeUnit.SECONDS); // 30 MIN
// , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();

41
application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java

@ -34,6 +34,47 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg
protected static final String CLIENT_ENDPOINT_OTA5 = "Ota5_Device";
protected static final String CLIENT_ENDPOINT_OTA9 = "Ota9_Device";
protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA =
" {\n" +
" \"keyName\": {\n" +
" \"/5_1.2/0/3\": \"state\",\n" +
" \"/5_1.2/0/5\": \"updateResult\",\n" +
" \"/5_1.2/0/6\": \"pkgname\",\n" +
" \"/5_1.2/0/7\": \"pkgversion\",\n" +
" \"/5_1.2/0/9\": \"firmwareUpdateDeliveryMethod\",\n" +
" \"/9_1.1/0/0\": \"pkgname\",\n" +
" \"/9_1.1/0/1\": \"pkgversion\",\n" +
" \"/9_1.1/0/7\": \"updateState\",\n" +
" \"/9_1.1/0/9\": \"updateResult\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"/5_1.2/0/3\",\n" +
" \"/5_1.2/0/5\",\n" +
" \"/5_1.2/0/6\",\n" +
" \"/5_1.2/0/7\",\n" +
" \"/5_1.2/0/9\",\n" +
" \"/9_1.1/0/0\",\n" +
" \"/9_1.1/0/1\",\n" +
" \"/9_1.1/0/7\",\n" +
" \"/9_1.1/0/9\"\n" +
" ],\n" +
" \"attribute\": [],\n" +
" \"telemetry\": [\n" +
" \"/5_1.2/0/3\",\n" +
" \"/5_1.2/0/5\",\n" +
" \"/5_1.2/0/6\",\n" +
" \"/5_1.2/0/7\",\n" +
" \"/5_1.2/0/9\",\n" +
" \"/9_1.1/0/0\",\n" +
" \"/9_1.1/0/1\",\n" +
" \"/9_1.1/0/7\",\n" +
" \"/9_1.1/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
public AbstractOtaLwM2MIntegrationTest() {
setResources(this.RESOURCES_OTA);
}

40
application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java

@ -51,46 +51,6 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfil
@Slf4j
public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA =
" {\n" +
" \"keyName\": {\n" +
" \"/5_1.0/0/3\": \"state\",\n" +
" \"/5_1.0/0/5\": \"updateResult\",\n" +
" \"/5_1.0/0/6\": \"pkgname\",\n" +
" \"/5_1.0/0/7\": \"pkgversion\",\n" +
" \"/5_1.0/0/9\": \"firmwareUpdateDeliveryMethod\",\n" +
" \"/9_1.0/0/0\": \"pkgname\",\n" +
" \"/9_1.0/0/1\": \"pkgversion\",\n" +
" \"/9_1.0/0/7\": \"updateState\",\n" +
" \"/9_1.0/0/9\": \"updateResult\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"/5_1.0/0/3\",\n" +
" \"/5_1.0/0/5\",\n" +
" \"/5_1.0/0/6\",\n" +
" \"/5_1.0/0/7\",\n" +
" \"/5_1.0/0/9\",\n" +
" \"/9_1.0/0/0\",\n" +
" \"/9_1.0/0/1\",\n" +
" \"/9_1.0/0/7\",\n" +
" \"/9_1.0/0/9\"\n" +
" ],\n" +
" \"attribute\": [],\n" +
" \"telemetry\": [\n" +
" \"/5_1.0/0/3\",\n" +
" \"/5_1.0/0/5\",\n" +
" \"/5_1.0/0/6\",\n" +
" \"/5_1.0/0/7\",\n" +
" \"/5_1.0/0/9\",\n" +
" \"/9_1.0/0/0\",\n" +
" \"/9_1.0/0/1\",\n" +
" \"/9_1.0/0/7\",\n" +
" \"/9_1.0/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
private List<OtaPackageUpdateStatus> expectedStatuses;
@Test

34
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* 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.thingsboard.server.transport.lwm2m.rpc;
import org.junit.Before;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public abstract class AbstractRpcLwM2MIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest{
private final String[] RESOURCES_RPC_MULTIPLE_19 = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"};
public AbstractRpcLwM2MIntegrationObserveTest() {
setResources(this.RESOURCES_RPC_MULTIPLE_19);
}
@Before
public void initTest () throws Exception {
awaitObserveReadAll(2, deviceId);
}
}

446
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java

@ -0,0 +1,446 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* 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.thingsboard.server.transport.lwm2m.rpc.sql;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.leshan.core.ResponseCode;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_15;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_5;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_7;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MIntegrationObserveTest {
/**
* ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9", "19/1/0/0"]} - Ok
* @throws Exception
*/
@Test
public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mResourceInstance() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_7) + "=LwM2mSingleResource"));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_3) + "=LwM2mSingleResource"));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_5) + "=LwM2mSingleResource"));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(idVer_3_0_9) + "=LwM2mSingleResource"));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0_0) + "=LwM2mResourceInstance"));
}
/**
* ObserveComposite {"ids":["19/1/0/0", "5/0"]} - Ok
* @throws Exception
*/
@Test
public void testObserveComposite_ObjectInstanceWithOtherObjectResourceInstance_Result_CONTENT_Ok() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
String expectedIdVer5_0 = objectInstanceIdVer_5;
String expectedIds = "[\"" + expectedIdVer19_1_0 + "\", \"" + expectedIdVer5_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actual= rpcActualResult.get("value").asText();
assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0) + "=LwM2mMultipleResource"));
assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer5_0) + "=LwM2mObjectInstance"));
}
/**
* ObserveComposite {"ids":["5/0/7", "5/0/2"]} - Ok
* "5/0/2" - Execute^ result == null
* @throws Exception
*/
@Test
public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_If_Error_Null() throws Exception {
// sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_2 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_7) + "=LwM2mSingleResource"));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_2) + "=null"));
}
/**
* ObserveComposite {"ids":["5/0/7", "5/0/2"]} - Ok
* "5/0" contains "5/0/2"
* @throws Exception
*/
@Test
public void testObserveComposite_Result_BAD_REQUEST_ONE_PATH_CONTAINCE_OTHER() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer5_0 = objectInstanceIdVer_5;
String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2;
String expectedIds = "[\"" + expectedIdVer5_0 + "\", \"" + expectedIdVer5_0_2 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String actual= rpcActualResult.get("error").asText();
String expected = "Invalid path list : /5/0 and /5/0/2 are overlapped paths";
assertTrue(expected.equals(actual));
}
/**
* Previous -> "3/0/9"
* ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9"]} - CONTENT
* @throws Exception
*/
@Test
public void testObserveCompositeThereAreObservationOneResource_Result_CONTENT_Value_ObservationAddIfAbsent() throws Exception {
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expectedResult = "/3/0/9=LwM2mSingleResource [id=9";
assertTrue(rpcActualResult.get("value").asText().contains(expectedResult));
}
/**
* ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9", "19/1/0"]} - Ok
* @throws Exception
*/
@Test
public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mMultipleResource() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_7) + "=LwM2mSingleResource"));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_3) + "=LwM2mSingleResource"));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_5 + "=LwM2mSingleResource")));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(idVer_3_0_9) + "=LwM2mSingleResource"));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0) + "=LwM2mMultipleResource"));
}
/**
* ObserveComposite with keyName {"keys":["batteryLevel", "UtfOffset", "dataRead", "dataWrite"]} - Ok
* @throws Exception
*/
@Test
public void testObserveCompositeWithKeyName_Result_CONTENT_Value_SingleResources() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9;
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14;
String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0;
String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0;
String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]";
String actualResult = sendCompositeRPCByKeys("ObserveComposite", expectedKeys);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText();
String expectedIdVer3_0_14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
String expectedIdVer19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0;
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer3_0_14)));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer19_0_0)));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0)));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(idVer_3_0_9)));
}
/**
* ObserveComposite with keyName {"keys":["batteryLevel", "UtfOffset", "dataRead", "dataWrite"]} - - BAD_REQUEST
* @throws Exception
*/
@Test
public void testObserveCompositeWithKeyNameThereAreObservationOneResource_Result_CONTENT_Value_ObservationAddIfAbsent() throws Exception {
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9;
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14;
String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0;
String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0;
String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]";
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
String actualResult = sendCompositeRPCByKeys("ObserveComposite", expectedKeys);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actual = rpcActualResult.get("value").asText();
assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0) + "=LwM2mMultipleResource"));
}
/**
* ObserveReadAll
* {"result":"CONTENT","value":"[\"CompositeObservation: [/19/1/0\",\"/19/0/0\",\"/3/0/14\",\"/3/0/9]\"]"} - Ok
* @throws Exception
*/
@Test
public void testObserveReadAll_AfterCompositeObservation_Result_CONTENT_Value_SingleObservation_Only() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9;
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14;
String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0;
String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0;
String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]";
String actualResult = sendCompositeRPCByKeys("ObserveComposite", expectedKeys);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValues = rpcActualResultReadAll.get("value").asText();
String expectedIdVer3_0_14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer3_0_14)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0)));
}
/**
* ObserveReadAll
* {"result":"CONTENT","value":"{"result":"CONTENT","value":"["SingleObservation:/3/0/9","SingleObservation:/3/0/14","SingleObservation:/19/1/0/0","SingleObservation:/19/0/0"]"} - Ok
* @throws Exception
*/
@Test
public void testObserveReadAll_Result_CONTENT_Value_SingleObservation_Only() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer3_0_14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String actualResult3_0_9 = sendObserve("Observe", idVer_3_0_9);
ObjectNode rpcActualResult3_0_9 = JacksonUtil.fromString(actualResult3_0_9, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult3_0_9.get("result").asText());
String actualResult3_0_14 = sendObserve("Observe", expectedIdVer3_0_14);
ObjectNode rpcActualResult3_0_14 = JacksonUtil.fromString(actualResult3_0_14, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult3_0_14.get("result").asText());
String actualResult19_1_0_0 = sendObserve("Observe", expectedIdVer19_1_0_0);
ObjectNode rpcActualResult19_1_0_0 = JacksonUtil.fromString(actualResult19_1_0_0, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult19_1_0_0.get("result").asText());
String actualResult19_0_0 = sendObserve("Observe", idVer_19_0_0);
ObjectNode rpcActualResult19_0_0 = JacksonUtil.fromString(actualResult19_0_0, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult19_0_0.get("result").asText());
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValues = rpcActualResultReadAll.get("value").asText();
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer3_0_14)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0_0)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0)));
}
/**
* ObserveReadAll
* {"result":"CONTENT","value":"[\"CompositeObservation: [/19/1/0\",\"/19/0/0\",\"/3/0/14\",\"/3/0/9]\"]"} - Ok
* @throws Exception
*/
@Test
public void testObserveReadAll_AfterCompositeObservation_WithResourceNotReadable_Result_CONTENT_Value_SingleObservation_Only() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_2= objectInstanceIdVer_5 + "/" + RESOURCE_ID_2;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_2 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_2) + "=null"));
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
actualValues = rpcActualResultReadAll.get("value").asText();
assertFalse(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_2)));
}
/**
* ObserveComposite {"ids":["/5/0/7", "/5/0/5", "/5/0/3", "/3/0/9", "/19/1/0/0"]} - Ok
* ObserveCompositeCancel {"ids":["/5/0/7", "/5/0/5", "/5/0/3", "/3/0/9", "/19/1/0/0"]} - Ok
* @throws Exception
*/
@Test
public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_5() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
// ObserveComposite
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// ObserveCompositeCancel
actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("5", rpcActualResult.get("value").asText());
assertEquals(0, (Object) getCntObserveAll(deviceId));
}
/**
* ObserveComposite {"ids":["/3", "/5/0/3", "/19/1/0/0"]} - Ok
* ObserveCompositeCancel {"ids":["/3", "/5/0/3", "/19/1/0/0"]} - Ok
* @throws Exception
*/
@Test
public void testObserveCompositeOneObjectAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_3() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
// ObserveComposite
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// ObserveCompositeCancel
actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("3", rpcActualResult.get("value").asText());
assertEquals(0, (Object) getCntObserveAll(deviceId));
}
/**
* ObserveComposite {"ids":["/3/0/9", "/3/0/14", "/5/0/3", "/19/1/0/0"]} - Ok
* ObserveCompositeCancel {"ids":["/3", "/19/1/0/0"]} - Ok
* last Observation
* @throws Exception
*/
@Test
public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_OneObjectAnyResource_Result_Content_Count_4() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
// ObserveComposite
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
awaitObserveReadAll(5, deviceId);
// ObserveCompositeCancel
expectedIds = "[\"" + objectInstanceIdVer_5 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("4", rpcActualResult.get("value").asText());
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValues = rpcActualResultReadAll.get("value").asText();
assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer3_0_9) + "\"]", actualValues);
}
/**
* ObserveComposite {"ids":["/3/0/9", "/3/0/14", "/5/0/3", "/3/0/15", "/19/1/0/0"]} - Ok
* ObserveCompositeCancel {"ids":["/3/0/9", "/19/1/0/0", "/3]} - Ok
* last Observation
* @throws Exception
*/
@Test
public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_OneResource_OneObjectAnyResource_Result_Content_Count_4() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
// ObserveComposite
sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer3_0_14 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14;
String expectedIdVer3_0_15= objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_15;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer3_0_14 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_15 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// ObserveCompositeCancel
expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\", \"" + objectIdVer_3 + "\"]";
actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("4", rpcActualResult.get("value").asText());
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValues = rpcActualResultReadAll.get("value").asText();
assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer5_0_3) + "\"]", actualValues);
}
private String sendObserve(String method, String params) throws Exception {
String sendRpcRequest;
if (params == null) {
sendRpcRequest = "{\"method\": \"" + method + "\"}";
}
else {
sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}";
}
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk());
}
private String sendCompositeRPCByIds(String method, String paths) throws Exception {
String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"ids\":" + paths + "}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendCompositeRPCByKeys(String method, String keys) throws Exception {
String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
}

5
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java

@ -35,6 +35,7 @@ import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVerId;
public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest {
@ -132,8 +133,10 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration
String expectedObjectInstanceId = pathIdVerToObjectId(expectedInstance);
LwM2mPath expectedPath = new LwM2mPath(expectedObjectInstanceId);
int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
String ver = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().version;
String expected = expectedInstance + "/" + expectedResource;
String actualResult = sendDiscover(expected);
String expectedVerId = convertObjectIdToVerId(expected, ver);
String actualResult = sendDiscover(expectedVerId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expectedResourceId = "<" + expectedObjectInstanceId + "/" + expectedResource + ">";

283
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java

@ -17,84 +17,141 @@ package org.thingsboard.server.transport.lwm2m.rpc.sql;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.LwM2m.Version;
import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.server.registration.Registration;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
import java.util.Optional;
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
@Slf4j
public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest {
public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationObserveTest {
@SpyBean
DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest;
@Test
public void testObserveReadAll_Count_2_CancelAll_Count_0_Ok() throws Exception {
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValuesReadAll = rpcActualResult.get("value").asText();
assertEquals(2, actualValuesReadAll.split(",").length);
String expected = "\"SingleObservation:/19/0/0\"";
assertTrue(actualValuesReadAll.contains(expected));
expected = "\"SingleObservation:/3/0/9\"";
assertTrue(actualValuesReadAll.contains(expected));
}
/**
* ObserveReadAll&ObserveCancelAll
* Observe "3_1.2/0/9"
* @throws Exception
*/
@Test
public void testObserveReadAllNothingObservation_Result_CONTENT_Value_Count_0() throws Exception {
awaitObserveReadAll(2, deviceId);
String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String actualResult = sendRpcObserve("Observe", idVer_3_0_0);
public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String idVer_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9;
String actualResult = sendRpcObserve("Observe", idVer_3_0_9);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
assertEquals(3, getCntObserveAll(deviceId));
actualResult = sendRpcObserve("ObserveCancelAll", null);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
int cntObserveCancelAll = Integer.parseInt(rpcActualResult.get("value").asText());
assertTrue(cntObserveCancelAll > 0);
assertEquals(0, getCntObserveAll(deviceId));
assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get());
int cntUpdate = 3;
verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
.onUpdateValueAfterReadResponse(Mockito.any(Registration.class), eq(idVer_3_0_9), Mockito.any(ReadResponse.class));
}
/**
* Observe {"id":"/3/0/0"}
* Observe "3_1.2/0"
* @throws Exception
*/
@Test
public void testObserveSingleResourceWithout_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception {
String expectedId = objectInstanceIdVer_9 + "/" + RESOURCE_ID_0;
String actualResult = sendRpcObserve("Observe", fromVersionedIdToObjectId(expectedId));
public void testObserveOneObjectInstance_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String idVer_3_0 = objectInstanceIdVer_3;
String actualResult = sendRpcObserve("Observe", idVer_3_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get());
int cntUpdate = 3;
verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
.updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9));
}
/**
* Observe {"id":"/3_1.0/0/14"}
* Observe "3_1.2"
* @throws Exception
*/
@Test
public void testObserveSingleResourceWith_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception {
String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
String actualResult = sendRpcObserve("Observe", expectedId);
public void testObserveOneObject_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String idVer_3_0 = objectInstanceIdVer_3;
String actualResult = sendRpcObserve("Observe", idVer_3_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get());
int cntUpdate = 3;
verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
.updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9));
}
/**
* Observe {"id":"/3_1.1/0/13"}
* Repeated request on Observe
* Observe {"id":"/3_1.2/0/0"}
* @throws Exception
*/
@Test
public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe1_0() throws Exception {
public void testObserveRepeated_Result_CONTENT_AddIfAbsent() throws Exception {
String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String actualResult = sendRpcObserve("Observe", idVer_3_0_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
actualResult = sendRpcObserve("Observe", idVer_3_0_0);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected = "LwM2mSingleResource [id=0";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
}
/**
* Observe {"id":"/3_1.?/0/13"}
* @throws Exception
*/
@Test
public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe_Ver() throws Exception {
String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String)path).contains("_")).findFirst().get();
LwM2mPath expectedPath = new LwM2mPath(expectedInstance);
int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
String expectedId = "/" + expectedPath.getObjectId() + "_1.2" + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource;
String ver = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().version;
String expectedId = "/" + expectedPath.getObjectId() + "_" + Version.MAX + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource;
String actualResult = sendRpcObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Specified resource id " + expectedId +" is not valid version! Must be version: 1.0";
String expected = "Specified resource id " + expectedId +" is not valid version! Must be version: " + ver;
assertEquals(expected, rpcActualResult.get("error").asText());
}
@ -128,12 +185,11 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
}
/**
* Repeated request on Observe
* Observe {"id":"/5/0/0"}
* Observe resource Write -> "/5/0/02"
* @throws Exception
*/
@Test
public void testObserveRSourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception {
public void testObserveResourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception {
String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_0;
sendRpcObserve("Observe", expectedId);
String actualResult = sendRpcObserve("Observe", expectedId);
@ -141,54 +197,173 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText());
}
/**
* Observe resource Execute -> "/5/0/2"
* @throws Exception
*/
@Test
public void testObserveExecuteResource_Result_METHOD_NOT_ALLOWED() throws Exception {
String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2;
sendRpcObserve("Observe", expectedId);
String actual = sendRpcObserve("Observe", expectedId);
ObjectNode rpcActual = JacksonUtil.fromString(actual, ObjectNode.class);
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActual.get("result").asText());
}
/**
* Repeated request on Observe
* Observe {"id":"/3_1.2/0/9"}
* Observe {"id":"/3_1.2/0/0"}
* @throws Exception
*/
@Test
public void testObserveRepeatedRequestObserveOnDevice_Result_BAD_REQUEST_ErrorMsg_AlreadyRegistered() throws Exception {
public void testObserveRepeatedRequestObserveOnDevice_Result_CONTENT_PutIfAbsent() throws Exception {
String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String actualResult = sendRpcObserve("Observe", idVer_3_0_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
actualResult = sendRpcObserve("Observe", idVer_3_0_0);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Observation is already registered!";
assertEquals(expected, rpcActualResult.get("error").asText());
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected = "LwM2mSingleResource [id=0";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
}
/**
* ObserveReadAll
* Observe {"id":["3"]} - Ok
* PreviousObservation contains "3/0/0"
* @throws Exception
*/
@Test
public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveReadAll() throws Exception {
String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
sendRpcObserve("Observe", expectedId);
public void testObserve_Result_CONTENT_ONE_PATH_PreviousObservation_CONTAINCE_OTHER_CurrentObservation() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
// "3/0/9"
String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String actualResult3_0_0 = sendRpcObserve("Observe", idVer_3_0_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult3_0_0, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// "3"
String actualResult3 = sendRpcObserve("Observe", objectIdVer_3);
rpcActualResult = JacksonUtil.fromString(actualResult3, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// PreviousObservation "3/0/0" change to CurrentObservation "3"
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValuesReadAll = rpcActualResultReadAll.get("value").asText();
log.warn("ObserveReadAll: [{}]", actualValuesReadAll);
assertEquals(3, actualValuesReadAll.split(",").length);
rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValuesReadAll = rpcActualResult.get("value").asText();
assertEquals(1, actualValuesReadAll.split(",").length);
String expected = "\"SingleObservation:/3\"";
assertTrue(actualValuesReadAll.contains(expected));
}
/**
* Observe {"id":["3/0/9"]} - Ok
* PreviousObservation contains "3"
* @throws Exception
*/
@Test
public void testObserve_Result_CONTENT_ONE_PATH_CurrentObservation_CONTAINCE_OTHER_PreviousObservation() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
// "3"
String actualResult3 = sendRpcObserve("Observe", objectIdVer_3);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult3, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// "3/0/0"
String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String actualResult3_0_0 = sendRpcObserve("Observe", idVer_3_0_0);
rpcActualResult = JacksonUtil.fromString(actualResult3_0_0, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// PreviousObservation "3" contains CurrentObservation "3/0/0"
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null);
rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValuesReadAll = rpcActualResult.get("value").asText();
assertEquals(1, actualValuesReadAll.split(",").length);
String expected = "\"SingleObservation:/3\"";
assertTrue(actualValuesReadAll.contains(expected));
}
/**
* ObserveCancel {"id":"/3/0/3"}
* ObserveCancel {"id":"/5/0/3"}
* Observe {"id":"/3/0/9"}
* ObserveCancel {"id":"/3/0/9"}
*/
@Test
public void testObserveCancelOneResource_Result_CONTENT_Value_Count_1() throws Exception {
sendRpcObserve("ObserveCancelAll", null);
String expectedId_3_0_3 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_3;
String expectedId_5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
sendRpcObserve("Observe", expectedId_3_0_3);
sendRpcObserve("Observe", expectedId_5_0_3);
String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_3);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
public void testObserveResource_ObserveCancelResource_Result_CONTENT_Count_1() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9;
sendRpcObserve("Observe", expectedId_3_0_9);
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValuesReadAll = rpcActualResult.get("value").asText();
assertEquals(1, actualValuesReadAll.split(",").length);
String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_9) + "\"";
assertTrue(actualValuesReadAll.contains(expected));
// cancel observe "/3_1.2/0/9"
String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_9);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("1", rpcActualResult.get("value").asText());
}
/**
* Observe {"id":"/3"}
* ObserveCancel {"id":"/3/0/9"}
*/
@Test
public void testObserveObject_ObserveCancelOneResource_Result_INTERNAL_SERVER_ERROR_Than_Cancel_ObserveObject_Result_CONTENT_Count_1() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedId_3 = objectIdVer_3;
sendRpcObserve("Observe", expectedId_3);
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValuesReadAll = rpcActualResult.get("value").asText();
assertEquals(1, actualValuesReadAll.split(",").length);
String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3) + "\"";
assertTrue(actualValuesReadAll.contains(expected));
// cancel observe "/3_1.2/0/9"
String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9;
String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_9);
String expectedValue = "for observation path " + fromVersionedIdToObjectId(expectedId_3_0_9) + ", that includes this observation path " + fromVersionedIdToObjectId(objectIdVer_3);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("error").asText().contains(expectedValue));
// cancel observe "/3_1.2"
actualResult = sendRpcObserve("ObserveCancel", expectedId_3);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("1", rpcActualResult.get("value").asText());
}
/**
* Observe {"id":"/3/0/9"}
* ObserveCancel {"id":"/3"}
*/
@Test
public void testObserveResource_ObserveCancelObject_Result_CONTENT_Count_1() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9;
sendRpcObserve("Observe", expectedId_3_0_9);
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValuesReadAll = rpcActualResult.get("value").asText();
assertEquals(1, actualValuesReadAll.split(",").length);
String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_9) + "\"";
assertTrue(actualValuesReadAll.contains(expected));
// cancel observe "/3_1.2"
String expectedId_3 = objectIdVer_3;
String actualResult = sendRpcObserve("ObserveCancel", expectedId_3);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("1", rpcActualResult.get("value").asText());
}

2
application/src/test/resources/lwm2m/5.xml

@ -70,7 +70,7 @@ A LwM2M Server MUST support block-wise transfer. Other protocols, such as HTTP/H
<ObjectID>5</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:5</ObjectURN>
<LWM2MVersion>1.0</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<ObjectVersion>1.2</ObjectVersion>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>

2
application/src/test/resources/lwm2m/9.xml

@ -64,7 +64,7 @@ LEGAL DISCLAIMER
<ObjectID>9</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:9</ObjectURN>
<LWM2MVersion>1.0</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<ObjectVersion>1.1</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>

2
common/transport/lwm2m/pom.xml

@ -97,7 +97,7 @@
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java

@ -131,6 +131,7 @@ public class LwM2MTransportBootstrapService {
// Set Californium Configuration
endpointsBuilder.setConfiguration(serverCoapConfig);
serverConfig.setCoapConfig(serverCoapConfig);
// Create CoAP endpoint

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java

@ -50,7 +50,7 @@ import java.util.stream.Collectors;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.BOOTSTRAP_DEFAULT_SHORT_ID;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.BOOTSTRAP_DEFAULT_SHORT_ID_0;
@Slf4j
public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTaskProvider {
@ -106,7 +106,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask
"Bootstrap Discover return error {} : to continue bootstrap session without autoIdForSecurityObject mode. {}",
discoverResponse, session);
}
if (this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID) == null) {
if (this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID_0) == null) {
log.error(
"Unable to find bootstrap server instance in Security Object (0) in response {}: unable to continue bootstrap session with autoIdForSecurityObject mode. {}",
discoverResponse, session);
@ -170,7 +170,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask
lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(serverId, path.getObjectInstanceId());
} else {
if (!this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(0)) {
this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(BOOTSTRAP_DEFAULT_SHORT_ID, path.getObjectInstanceId());
this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(BOOTSTRAP_DEFAULT_SHORT_ID_0, path.getObjectInstanceId());
} else {
log.error("Invalid bootstrapSecurityInstance by [{}]", path.getObjectInstanceId());
}
@ -244,7 +244,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask
Integer bootstrapServerIdNew = null;
// handle security
int lwm2mSecurityInstanceId = 0;
int bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID);
int bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID_0);
for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfig.security).values()) {
if (security.bootstrapServer) {
requestsWrite.add(toWriteRequest(bootstrapSecurityInstanceId, security, contentFormat));

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java

@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.config;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.config.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
@ -105,6 +106,10 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
@Setter
private List<TbProperty> networkConfig;
@Getter
@Setter
private Configuration coapConfig;
@Bean
@ConfigurationProperties(prefix = "transport.lwm2m.server.security.credentials")
public SslCredentialsConfig lwm2mServerCredentials() {

21
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java

@ -93,9 +93,10 @@ public class LwM2mServerListener {
@Override
public void cancelled(Observation observation) {
//TODO: should be able to use CompositeObservation
log.trace("Canceled Observation {}.", ((SingleObservation)observation).getPath());
}
log.info("Canceled Observation [RegistrationId:{}: {}].", observation.getRegistrationId(), observation instanceof SingleObservation ?
"SingleObservation: " + ((SingleObservation) observation).getPath() :
"CompositeObservation: " + ((CompositeObservation) observation).getPaths());
}
@Override
public void onResponse(SingleObservation observation, Registration registration, ObserveResponse response) {
@ -106,21 +107,25 @@ public class LwM2mServerListener {
@Override
public void onResponse(CompositeObservation observation, Registration registration, ObserveCompositeResponse response) {
throw new RuntimeException("Not implemented yet!");
log.trace("Update Composite Observation [{}: {}].", observation.getRegistrationId(), observation.getPaths());
service.onUpdateValueAfterReadCompositeResponse(registration, response);
}
@Override
public void onError(Observation observation, Registration registration, Exception error) {
if (error != null) {
//TODO: should be able to use CompositeObservation
log.debug("Unable to handle notification of [{}:{}] [{}]", observation.getRegistrationId(), ((SingleObservation)observation).getPath(), error.getMessage());
var path = observation instanceof SingleObservation ? "Single Observation Cancel: " + ((SingleObservation) observation).getPath() : "Composite Observation Cancel: " + ((CompositeObservation) observation).getPaths();
var msgError = path + ": " + error.getMessage();
log.trace("Unable to handle notification [RegistrationId:{}]: [{}].", observation.getRegistrationId(), msgError);
service.onErrorObservation(registration, msgError);
}
}
@Override
public void newObservation(Observation observation, Registration registration) {
//TODO: should be able to use CompositeObservation
log.trace("Successful start newObservation {}.", ((SingleObservation)observation).getPath());
log.trace("Successful start newObservation [RegistrationId:{}: {}].", observation.getRegistrationId(), observation instanceof SingleObservation ?
"Single: " + ((SingleObservation) observation).getPath() :
"Composite: " + ((CompositeObservation) observation).getPaths());
}
};

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java

@ -55,7 +55,7 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
@Override
public void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg attributeUpdateNotification) {
log.trace("[{}] Received attributes update notification to device", sessionId);
log.info("[{}] Received attributes update notification to device", sessionId);
this.attributesService.onAttributesUpdate(attributeUpdateNotification, this.sessionInfo);
}

6
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java

@ -16,10 +16,10 @@
package org.thingsboard.server.transport.lwm2m.server;
import com.google.gson.JsonParser;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
@ -34,8 +34,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;

111
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java

@ -31,6 +31,7 @@ import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.observation.CompositeObservation;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.observation.SingleObservation;
import org.eclipse.leshan.core.request.CompositeDownlinkRequest;
@ -40,6 +41,7 @@ import org.eclipse.leshan.core.request.DeleteRequest;
import org.eclipse.leshan.core.request.DiscoverRequest;
import org.eclipse.leshan.core.request.DownlinkRequest;
import org.eclipse.leshan.core.request.ExecuteRequest;
import org.eclipse.leshan.core.request.ObserveCompositeRequest;
import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.request.ReadCompositeRequest;
import org.eclipse.leshan.core.request.ReadRequest;
@ -55,6 +57,7 @@ import org.eclipse.leshan.core.response.DeleteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.LwM2mResponse;
import org.eclipse.leshan.core.response.ObserveCompositeResponse;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.core.response.ReadCompositeResponse;
import org.eclipse.leshan.core.response.ReadResponse;
@ -73,6 +76,8 @@ import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MCancelObserveCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MObserveCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MReadCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcWriteCompositeRequest;
@ -81,10 +86,14 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@ -172,23 +181,19 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
try {
validateVersionedId(client, request);
LwM2mPath resultIds = new LwM2mPath(request.getObjectId());
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration());
//TODO: should be able to use CompositeObservation
if (observations.stream().noneMatch(observation -> ((SingleObservation)observation).getPath().equals(resultIds))) {
ObserveRequest downlink;
ContentFormat contentFormat = getReadRequestContentFormat(client, request, modelProvider);
if (resultIds.isResource()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
} else if (resultIds.isObjectInstance()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId());
} else {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId());
}
log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId());
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
ObserveRequest downlink;
ContentFormat contentFormat = getReadRequestContentFormat(client, request, modelProvider);
if (resultIds.isResourceInstance()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), resultIds.getResourceInstanceId());
} else if (resultIds.isResource()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
} else if (resultIds.isObjectInstance()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId());
} else {
callback.onValidationError(resultIds.toString(), "Observation is already registered!");
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId());
}
log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId());
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} catch (InvalidRequestException e) {
callback.onValidationError(request.toString(), e.getMessage());
}
@ -197,11 +202,72 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
@Override
public void sendObserveAllRequest(LwM2mClient client, TbLwM2MObserveAllRequest request, DownlinkRequestCallback<TbLwM2MObserveAllRequest, Set<String>> callback) {
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration());
//TODO: should be able to use CompositeObservation
Set<String> paths = observations.stream().map(observation -> ((SingleObservation)observation).getPath().toString()).collect(Collectors.toUnmodifiableSet());
Set<String> paths = new LinkedHashSet<>();
observations.stream().forEach(observation -> {
if (observation instanceof SingleObservation) {
paths.add("SingleObservation:" + ((SingleObservation) observation).getPath().toString());
} else {
List<LwM2mPath> listPath = ((CompositeObservation) observation).getPaths();
List<String> pathsComposite = listPath.stream().map(lwM2mPath -> (lwM2mPath.toString())).collect(Collectors.toList());
Set <String> pathsCompositeSort = new TreeSet<>();
if (pathsComposite.size() == 1) {
pathsCompositeSort.add("CompositeObservation: [" + pathsComposite.get(0) + "]");
} else if (pathsComposite.size() > 1) {
List <String> sort = new LinkedList<>();
sort.addAll(pathsComposite);
sort.set(0, "CompositeObservation: [" + sort.get(0));
sort.set(pathsComposite.size()-1, sort.get(pathsComposite.size()-1) + "]");
pathsCompositeSort = new LinkedHashSet<>(sort);
}
paths.addAll(pathsCompositeSort);
}
});
callback.onSuccess(request, paths);
}
/**
* if resource (SingleObservation or in CompositeObservation) is already registered - return BAD REQUEST
*/
@Override
public void sendObserveCompositeRequest(LwM2mClient client, TbLwM2MObserveCompositeRequest request, DownlinkRequestCallback<ObserveCompositeRequest,
ObserveCompositeResponse> callback) {
try {
log.trace("[{}] Send Composite observation: [{}].", client.getEndpoint(), request.getObjectIds());
ContentFormat compositeContentFormat = this.findFirstContentFormatForComposite(client.getClientSupportContentFormats());
ObserveCompositeRequest downlink = new ObserveCompositeRequest(compositeContentFormat, compositeContentFormat, request.getObjectIds());
sendCompositeRequest(client, downlink, this.config.getTimeout(), callback);
} catch (InvalidRequestException e) {
callback.onValidationError(request.toString(), e.getMessage());
}
}
@Override
public void sendCancelObserveCompositeRequest(LwM2mClient client, TbLwM2MCancelObserveCompositeRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveCompositeRequest, Integer> callback) {
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration());
List<LwM2mPath> listPath = LwM2mPath.getLwM2mPathList(Arrays.asList(request.getObjectIds()));
Optional<Observation> observationOpt = Optional.ofNullable(observations.stream().filter(observation -> observation instanceof CompositeObservation && ((CompositeObservation) observation).getPaths().equals(listPath)).findFirst().orElse(null));
int cnt = 0;
if (observationOpt.isPresent()) {
cnt = context.getServer().getObservationService().cancelCompositeObservations(client.getRegistration(), request.getObjectIds());
callback.onSuccess(request, cnt);
} else {
Set<String> lwPaths = new HashSet<>();
observations.forEach(obs -> {
LwM2mPath lwPathObs = ((SingleObservation) obs).getPath();
listPath.forEach(nodePath -> {
if(nodePath.equals(lwPathObs) || lwPathObs.startWith(nodePath)){ // nodePath = "3", lwPathObs = "3/0/9": cancel for tne all lwPathObs
lwPaths.add(nodePath.toString());
}
});
});
for (String nodePath : lwPaths) {
cnt += context.getServer().getObservationService().cancelObservations(client.getRegistration(), nodePath);
}
}
callback.onSuccess(request, cnt);
}
@Override
public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback<TbLwM2MDiscoverAllRequest, List<String>> callback) {
callback.onSuccess(request, Arrays.stream(client.getRegistration().getSortedObjectLinks()).map(Link::toCoreLinkFormat).collect(Collectors.toList()));
@ -258,7 +324,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
}
@Override
public void sendCancelAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback<TbLwM2MCancelAllRequest, Integer> callback) {
public void sendCancelObserveAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback<TbLwM2MCancelAllRequest, Integer> callback) {
int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration());
callback.onSuccess(request, observeCancelCnt);
}
@ -709,4 +775,13 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
});
return compositeContentFormat.get() != null ? compositeContentFormat.get() : defaultValue;
}
private static <T> boolean containsObserveComposite(List<T> l1, List<T> l2) {
for (T elem : l1) {
if (l2.contains(elem)) {
return true;
}
}
return false;
}
}

10
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java

@ -19,6 +19,7 @@ import org.eclipse.leshan.core.request.CreateRequest;
import org.eclipse.leshan.core.request.DeleteRequest;
import org.eclipse.leshan.core.request.DiscoverRequest;
import org.eclipse.leshan.core.request.ExecuteRequest;
import org.eclipse.leshan.core.request.ObserveCompositeRequest;
import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.request.ReadCompositeRequest;
import org.eclipse.leshan.core.request.ReadRequest;
@ -29,6 +30,7 @@ import org.eclipse.leshan.core.response.CreateResponse;
import org.eclipse.leshan.core.response.DeleteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.ObserveCompositeResponse;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.core.response.ReadCompositeResponse;
import org.eclipse.leshan.core.response.ReadResponse;
@ -36,6 +38,8 @@ import org.eclipse.leshan.core.response.WriteAttributesResponse;
import org.eclipse.leshan.core.response.WriteCompositeResponse;
import org.eclipse.leshan.core.response.WriteResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MCancelObserveCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MObserveCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MReadCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcWriteCompositeRequest;
@ -58,7 +62,11 @@ public interface LwM2mDownlinkMsgHandler {
void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback);
void sendCancelAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback<TbLwM2MCancelAllRequest, Integer> callback);
void sendCancelObserveAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback<TbLwM2MCancelAllRequest, Integer> callback);
void sendObserveCompositeRequest(LwM2mClient client, TbLwM2MObserveCompositeRequest request, DownlinkRequestCallback<ObserveCompositeRequest, ObserveCompositeResponse> callback);
void sendCancelObserveCompositeRequest(LwM2mClient client, TbLwM2MCancelObserveCompositeRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveCompositeRequest, Integer> callback);
void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback<DiscoverRequest, DiscoverResponse> callback);

40
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MCancelObserveCompositeCallback.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* 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.thingsboard.server.transport.lwm2m.server.downlink.composite;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.AbstractTbLwM2MRequestCallback;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_INFO;
@Slf4j
public class TbLwM2MCancelObserveCompositeCallback extends AbstractTbLwM2MRequestCallback<TbLwM2MCancelObserveCompositeRequest, Integer> {
private final String [] versionedIds;
public TbLwM2MCancelObserveCompositeCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String [] versionedIds) {
super(logService, client);
this.versionedIds = versionedIds;
}
@Override
public void onSuccess(TbLwM2MCancelObserveCompositeRequest request, Integer canceledSubscriptionsCount) {
log.trace("[{}] Cancel composite observation of [{}] successful: {}", client.getEndpoint(), this.versionedIds, canceledSubscriptionsCount);
logService.log(client, String.format("[%s]: Cancel Composite Observe for [%s] successful. Result: [%s]", LOG_LWM2M_INFO, this.versionedIds, canceledSubscriptionsCount));
}
}

32
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MCancelObserveCompositeRequest.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* 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.thingsboard.server.transport.lwm2m.server.downlink.composite;
import lombok.Builder;
import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType;
public class TbLwM2MCancelObserveCompositeRequest extends AbstractTbLwM2MTargetedDownlinkCompositeRequest {
@Builder
private TbLwM2MCancelObserveCompositeRequest(String [] versionedIds, long timeout) {
super(versionedIds, timeout);
}
@Override
public LwM2MOperationType getType() {
return LwM2MOperationType.OBSERVE_COMPOSITE_CANCEL;
}
}

39
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MObserveCompositeCallback.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* 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.thingsboard.server.transport.lwm2m.server.downlink.composite;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.ObserveCompositeRequest;
import org.eclipse.leshan.core.response.ObserveCompositeResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MUplinkTargetedCallback;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
@Slf4j
public class TbLwM2MObserveCompositeCallback extends TbLwM2MUplinkTargetedCallback<ObserveCompositeRequest, ObserveCompositeResponse> {
public TbLwM2MObserveCompositeCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String[] versionedIds) {
super(handler, logService, client, versionedIds);
}
@Override
public void onSuccess(ObserveCompositeRequest request, ObserveCompositeResponse response) {
super.onSuccess(request, response);
handler.onUpdateValueAfterReadCompositeResponse(client.getRegistration(), response);
}
}

51
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MObserveCompositeRequest.java

@ -0,0 +1,51 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* 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.thingsboard.server.transport.lwm2m.server.downlink.composite;
import lombok.Builder;
import lombok.Getter;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.response.ObserveCompositeResponse;
import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType;
import org.thingsboard.server.transport.lwm2m.server.downlink.HasContentFormat;
import java.util.Optional;
public class TbLwM2MObserveCompositeRequest extends AbstractTbLwM2MTargetedDownlinkCompositeRequest<ObserveCompositeResponse> implements HasContentFormat {
private final Optional<ContentFormat> requestContentFormatOpt;
@Getter
private final ContentFormat responseContentFormat;
@Builder
private TbLwM2MObserveCompositeRequest(String [] versionedIds, long timeout, ContentFormat requestContentFormat, ContentFormat responseContentFormat) {
super(versionedIds, timeout);
this.requestContentFormatOpt = Optional.ofNullable(requestContentFormat);
this.responseContentFormat = responseContentFormat;
}
@Override
public LwM2MOperationType getType() {
return LwM2MOperationType.OBSERVE_COMPOSITE;
}
@Override
public Optional<ContentFormat> getRequestContentFormat() {
return this.requestContentFormatOpt;
}
}

55
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java

@ -233,19 +233,19 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
@Override
public void onCurrentFirmwareNameUpdate(LwM2mClient client, String name) {
log.debug("[{}] Current fw name: {}", client.getEndpoint(), name);
log.trace("[{}] Current fw name: {}", client.getEndpoint(), name);
getOrInitFwInfo(client).setCurrentName(name);
}
@Override
public void onCurrentSoftwareNameUpdate(LwM2mClient client, String name) {
log.debug("[{}] Current sw name: {}", client.getEndpoint(), name);
log.trace("[{}] Current sw name: {}", client.getEndpoint(), name);
getOrInitSwInfo(client).setCurrentName(name);
}
@Override
public void onFirmwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) {
log.debug("[{}] Current fw strategy: {}", client.getEndpoint(), configuration.getFwUpdateStrategy());
log.trace("[{}] Current fw strategy: {}", client.getEndpoint(), configuration.getFwUpdateStrategy());
startFirmwareUpdateIfNeeded(client, initFwStrategy(client, configuration));
}
@ -258,7 +258,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
@Override
public void onCurrentSoftwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) {
log.debug("[{}] Current sw strategy: {}", client.getEndpoint(), configuration.getSwUpdateStrategy());
log.trace("[{}] Current sw strategy: {}", client.getEndpoint(), configuration.getSwUpdateStrategy());
startSoftwareUpdateIfNeeded(client, initSwStrategy(client, configuration));
}
@ -271,21 +271,21 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
@Override
public void onCurrentFirmwareVersion3Update(LwM2mClient client, String version) {
log.debug("[{}] Current fw version(3): {}", client.getEndpoint(), version);
log.trace("[{}] Current fw version(3): {}", client.getEndpoint(), version);
LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.setCurrentVersion3(version);
}
@Override
public void onCurrentFirmwareVersionUpdate(LwM2mClient client, String version) {
log.debug("[{}] Current fw version(5): {}", client.getEndpoint(), version);
log.trace("[{}] Current fw version(5): {}", client.getEndpoint(), version);
LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.setCurrentVersion(version);
}
@Override
public void onCurrentFirmwareStateUpdate(LwM2mClient client, Long stateCode) {
log.debug("[{}] Current fw state: {}", client.getEndpoint(), stateCode);
log.trace("[{}] Current fw state: {}", client.getEndpoint(), stateCode);
LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client);
FirmwareUpdateState state = FirmwareUpdateState.fromStateFwByCode(stateCode.intValue());
if (FirmwareUpdateState.DOWNLOADED.equals(state)) {
@ -309,7 +309,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
@Override
public void onCurrentFirmwareResultUpdate(LwM2mClient client, Long code) {
log.debug("[{}] Current fw result: {}", client.getEndpoint(), code);
log.trace("[{}] Current fw result: {}", client.getEndpoint(), code);
LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client);
FirmwareUpdateResult result = FirmwareUpdateResult.fromUpdateResultFwByCode(code.intValue());
Optional<OtaPackageUpdateStatus> status = toOtaPackageUpdateStatus(result);
@ -338,26 +338,26 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
@Override
public void onCurrentFirmwareDeliveryMethodUpdate(LwM2mClient client, Long value) {
log.debug("[{}] Current fw delivery method: {}", client.getEndpoint(), value);
log.trace("[{}] Current fw delivery method: {}", client.getEndpoint(), value);
LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.setDeliveryMethod(value.intValue());
}
@Override
public void onCurrentSoftwareVersion3Update(LwM2mClient client, String version) {
log.debug("[{}] Current sw version(3): {}", client.getEndpoint(), version);
log.trace("[{}] Current sw version(3): {}", client.getEndpoint(), version);
getOrInitSwInfo(client).setCurrentVersion3(version);
}
@Override
public void onCurrentSoftwareVersionUpdate(LwM2mClient client, String version) {
log.debug("[{}] Current sw version(9): {}", client.getEndpoint(), version);
log.trace("[{}] Current sw version(9): {}", client.getEndpoint(), version);
getOrInitSwInfo(client).setCurrentVersion(version);
}
@Override
public void onCurrentSoftwareStateUpdate(LwM2mClient client, Long stateCode) {
log.debug("[{}] Current sw state: {}", client.getEndpoint(), stateCode);
log.trace("[{}] Current sw state: {}", client.getEndpoint(), stateCode);
LwM2MClientSwOtaInfo swInfo = getOrInitSwInfo(client);
SoftwareUpdateState state = SoftwareUpdateState.fromUpdateStateSwByCode(stateCode.intValue());
if (SoftwareUpdateState.INITIAL.equals(state)) {
@ -375,7 +375,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
@Override
public void onCurrentSoftwareResultUpdate(LwM2mClient client, Long code) {
log.debug("[{}] Current sw result: {}", client.getEndpoint(), code);
log.trace("[{}] Current sw result: {}", client.getEndpoint(), code);
LwM2MClientSwOtaInfo swInfo = getOrInitSwInfo(client);
SoftwareUpdateResult result = SoftwareUpdateResult.fromUpdateResultSwByCode(code.intValue());
Optional<OtaPackageUpdateStatus> status = toOtaPackageUpdateStatus(result);
@ -417,14 +417,14 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
private void startFirmwareUpdateIfNeeded(LwM2mClient client, LwM2MClientFwOtaInfo fwInfo) {
try {
if (!fwInfo.isSupported() && fwInfo.isAssigned()) {
log.debug("[{}] Fw update is not supported: {}", client.getEndpoint(), fwInfo);
log.trace("[{}] Fw update is not supported: {}", client.getEndpoint(), fwInfo);
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Client does not support firmware update or profile misconfiguration!");
} else if (fwInfo.isUpdateRequired()) {
if (StringUtils.isNotEmpty(fwInfo.getTargetUrl())) {
log.debug("[{}] Starting update to [{}{}][] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl());
log.trace("[{}] Starting update to [{}{}][] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl());
startUpdateUsingUrl(client, FW_URL_ID, fwInfo.getTargetUrl());
} else {
log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion());
log.trace("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion());
startUpdateUsingBinary(client, fwInfo);
}
} else if (fwInfo.getResult() != null && fwInfo.getResult().getCode() > UPDATE_SUCCESSFULLY.getCode()) {
@ -440,18 +440,18 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
private void startSoftwareUpdateIfNeeded(LwM2mClient client, LwM2MClientSwOtaInfo swInfo) {
try {
if (!swInfo.isSupported() && swInfo.isAssigned()) {
log.debug("[{}] Sw update is not supported: {}", client.getEndpoint(), swInfo);
log.trace("[{}] Sw update is not supported: {}", client.getEndpoint(), swInfo);
sendStateUpdateToTelemetry(client, swInfo, OtaPackageUpdateStatus.FAILED, "Client does not support software update or profile misconfiguration!");
} else if (swInfo.isUpdateRequired()) {
if (SoftwareUpdateState.INSTALLED.equals(swInfo.getUpdateState())) {
log.debug("[{}] Attempt to restore the update state: {}", client.getEndpoint(), swInfo.getUpdateState());
log.trace("[{}] Attempt to restore the update state: {}", client.getEndpoint(), swInfo.getUpdateState());
executeSwUninstallForUpdate(client);
} else {
if (StringUtils.isNotEmpty(swInfo.getTargetUrl())) {
log.debug("[{}] Starting update to [{}{}] using URL: {}", client.getEndpoint(), swInfo.getTargetName(), swInfo.getTargetVersion(), swInfo.getTargetUrl());
log.trace("[{}] Starting update to [{}{}] using URL: {}", client.getEndpoint(), swInfo.getTargetName(), swInfo.getTargetVersion(), swInfo.getTargetUrl());
startUpdateUsingUrl(client, SW_PACKAGE_URI_ID, swInfo.getTargetUrl());
} else {
log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), swInfo.getTargetName(), swInfo.getTargetVersion());
log.trace("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), swInfo.getTargetName(), swInfo.getTargetVersion());
startUpdateUsingBinary(client, swInfo);
}
}
@ -566,18 +566,21 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
}
private void executeFwUpdate(LwM2mClient client) {
TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(FW_EXECUTE_ID).timeout(clientContext.getRequestTimeout(client)).build();
downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, FW_EXECUTE_ID));
String fwExecuteVerId = convertObjectIdToVersionedId(FW_EXECUTE_ID, client.getRegistration());
TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(fwExecuteVerId).timeout(clientContext.getRequestTimeout(client)).build();
downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, fwExecuteVerId));
}
private void executeSwInstall(LwM2mClient client) {
TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(SW_INSTALL_ID).timeout(clientContext.getRequestTimeout(client)).build();
downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, SW_INSTALL_ID));
String swInstallVerId = convertObjectIdToVersionedId(SW_INSTALL_ID, client.getRegistration());
TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(swInstallVerId).timeout(clientContext.getRequestTimeout(client)).build();
downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, swInstallVerId));
}
private void executeSwUninstallForUpdate(LwM2mClient client) {
TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(SW_UN_INSTALL_ID).params("1").timeout(clientContext.getRequestTimeout(client)).build();
downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, SW_INSTALL_ID));
String swInInstallVerId = convertObjectIdToVersionedId(SW_UN_INSTALL_ID, client.getRegistration());
TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(swInInstallVerId).params("1").timeout(clientContext.getRequestTimeout(client)).build();
downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, swInInstallVerId));
}
private Optional<String> getAttributeValue(List<TransportProtos.TsKvProto> attrs, String keyName) {

33
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java

@ -58,10 +58,16 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttrib
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteUpdateRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MObserveCompositeCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MCancelObserveCompositeCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MCancelObserveCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MObserveCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MReadCompositeCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MReadCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MWriteResponseCompositeCallback;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcCancelObserveCompositeCallback;
import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcObserveResponseCompositeCallback;
import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcReadCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcReadResponseCompositeCallback;
import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcWriteCompositeRequest;
@ -162,6 +168,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
case WRITE_COMPOSITE:
sendWriteCompositeRequest(client, rpcRequest);
break;
case OBSERVE_COMPOSITE:
sendObserveCompositeRequest(client, rpcRequest);
break;
case OBSERVE_COMPOSITE_CANCEL:
sendCancelObserveCompositeRequest(client, rpcRequest);
break;
default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
}
@ -382,6 +394,25 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
downlinkHandler.sendCancelObserveRequest(client, downlink, rpcCallback);
}
private void sendObserveCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) {
String[] versionedIds = getIdsFromParameters(client, requestMsg);
TbLwM2MObserveCompositeRequest request = TbLwM2MObserveCompositeRequest.builder().versionedIds(versionedIds).timeout(clientContext.getRequestTimeout(client)).build();
var mainCallback = new TbLwM2MObserveCompositeCallback(uplinkHandler, logService, client, versionedIds);
var rpcCallback = new RpcObserveResponseCompositeCallback(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendObserveCompositeRequest(client, request, rpcCallback);
}
private void sendCancelObserveCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) {
String[] versionedIds = getIdsFromParameters(client, requestMsg);
TbLwM2MCancelObserveCompositeRequest request = TbLwM2MCancelObserveCompositeRequest.builder().versionedIds(versionedIds).timeout(clientContext.getRequestTimeout(client)).build();
var mainCallback = new TbLwM2MCancelObserveCompositeCallback(logService, client, versionedIds);
var rpcCallback = new RpcCancelObserveCompositeCallback(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendCancelObserveCompositeRequest(client, request, rpcCallback);
}
private void sendDeleteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
TbLwM2MDeleteRequest downlink = TbLwM2MDeleteRequest.builder().versionedId(versionedId).timeout(clientContext.getRequestTimeout(client)).build();
var mainCallback = new TbLwM2MDeleteCallback(logService, client, versionedId);
@ -393,7 +424,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
TbLwM2MCancelAllRequest downlink = TbLwM2MCancelAllRequest.builder().timeout(clientContext.getRequestTimeout(client)).build();
var mainCallback = new TbLwM2MCancelAllObserveCallback(logService, client);
var rpcCallback = new RpcCancelAllObserveCallback(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback);
downlinkHandler.sendCancelObserveAllRequest(client, downlink, rpcCallback);
}
private String getIdFromParameters(LwM2mClient client, LwM2MRpcRequestHeader header) {

37
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcCancelObserveCompositeCallback.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* 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.thingsboard.server.transport.lwm2m.server.rpc.composite;
import org.eclipse.leshan.core.ResponseCode;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MCancelObserveCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcResponseBody;
import org.thingsboard.server.transport.lwm2m.server.rpc.RpcDownlinkRequestCallbackProxy;
public class RpcCancelObserveCompositeCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelObserveCompositeRequest, Integer> {
public RpcCancelObserveCompositeCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<TbLwM2MCancelObserveCompositeRequest, Integer> callback) {
super(transportService, client, requestMsg, callback);
}
@Override
protected void sendRpcReplyOnSuccess(Integer response) {
reply(LwM2MRpcResponseBody.builder().result(ResponseCode.CONTENT.getName()).value(response.toString()).build());
}
}

40
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcObserveResponseCompositeCallback.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* 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.thingsboard.server.transport.lwm2m.server.rpc.composite;
import org.eclipse.leshan.core.request.LwM2mRequest;
import org.eclipse.leshan.core.response.ObserveCompositeResponse;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback;
import org.thingsboard.server.transport.lwm2m.server.rpc.RpcLwM2MDownlinkCallback;
import java.util.Optional;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.contentToString;
public class RpcObserveResponseCompositeCallback<R extends LwM2mRequest<T>, T extends ObserveCompositeResponse> extends RpcLwM2MDownlinkCallback<R, T> {
public RpcObserveResponseCompositeCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<R, T> callback) {
super(transportService, client, requestMsg, callback);
}
@Override
protected Optional<String> serializeSuccessfulResponse(T response) {
return contentToString(response.getContent());
}
}

577
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java

@ -0,0 +1,577 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* 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.thingsboard.server.transport.lwm2m.server.store;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.californium.core.coap.Token;
import org.eclipse.californium.core.network.TokenGenerator;
import org.eclipse.californium.core.network.TokenGenerator.Scope;
import org.eclipse.leshan.core.Destroyable;
import org.eclipse.leshan.core.Startable;
import org.eclipse.leshan.core.Stoppable;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.observation.CompositeObservation;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.observation.ObservationIdentifier;
import org.eclipse.leshan.core.observation.SingleObservation;
import org.eclipse.leshan.core.peer.LwM2mIdentity;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.util.NamedThreadFactory;
import org.eclipse.leshan.server.registration.Deregistration;
import org.eclipse.leshan.server.registration.ExpirationListener;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.registration.RegistrationStore;
import org.eclipse.leshan.server.registration.RegistrationUpdate;
import org.eclipse.leshan.server.registration.UpdatedRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.eclipse.leshan.core.californium.ObserveUtil.CTX_CF_OBERSATION;
import static org.eclipse.leshan.core.californium.ObserveUtil.extractSerializedObservation;
public class TbInMemoryRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable {
private final Logger LOG = LoggerFactory.getLogger(TbInMemoryRegistrationStore.class);
// Data structure
private final Map<String /* end-point */, Registration> regsByEp = new HashMap<>();
private final Map<InetSocketAddress, Registration> regsByAddr = new HashMap<>();
private final Map<String /* reg-id */, Registration> regsByRegId = new HashMap<>();
private final Map<LwM2mIdentity, Registration> regsByIdentity = new HashMap<>();
private final Map<ObservationIdentifier, Observation> obsByToken = new HashMap<>();
private final Map<String, Set<ObservationIdentifier>> tokensByRegId = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
// Listener use to notify when a registration expires
private ExpirationListener expirationListener;
private final ScheduledExecutorService schedExecutor;
private ScheduledFuture<?> cleanerTask;
private boolean started = false;
private final long cleanPeriod; // in seconds
private final TokenGenerator tokenGenerator;
private final LwM2mVersionedModelProvider modelProvider;
public TbInMemoryRegistrationStore() {
this(null, 2, null); // default clean period : 2s
}
public TbInMemoryRegistrationStore(TokenGenerator tokenGenerator, long cleanPeriodInSec, LwM2mVersionedModelProvider modelProvider) {
this(tokenGenerator, Executors.newScheduledThreadPool(1,
new NamedThreadFactory(String.format("TbInMemoryRegistrationStore Cleaner (%ds)", cleanPeriodInSec))),
cleanPeriodInSec, modelProvider);
}
public TbInMemoryRegistrationStore(TokenGenerator tokenGenerator, ScheduledExecutorService schedExecutor, long cleanPeriodInSec, LwM2mVersionedModelProvider modelProvider) {
this.schedExecutor = schedExecutor;
this.cleanPeriod = cleanPeriodInSec;
this.modelProvider = modelProvider;
this.tokenGenerator = tokenGenerator;
}
/* *************** Leshan Registration API **************** */
@Override
public Deregistration addRegistration(Registration registration) {
try {
lock.writeLock().lock();
Registration registrationRemoved = regsByEp.put(registration.getEndpoint(), registration);
regsByRegId.put(registration.getId(), registration);
regsByIdentity.put(registration.getClientTransportData().getIdentity(), registration);
// If a registration is already associated to this address we don't care as we only want to keep the most
// recent binding.
regsByAddr.put(registration.getSocketAddress(), registration);
if (registrationRemoved != null) {
Collection<Observation> observationsRemoved = unsafeRemoveAllObservations(registrationRemoved.getId());
if (!registrationRemoved.getSocketAddress().equals(registration.getSocketAddress())) {
removeFromMap(regsByAddr, registrationRemoved.getSocketAddress(), registrationRemoved);
}
if (!registrationRemoved.getId().equals(registration.getId())) {
removeFromMap(regsByRegId, registrationRemoved.getId(), registrationRemoved);
}
if (!registrationRemoved.getClientTransportData().getIdentity()
.equals(registration.getClientTransportData().getIdentity())) {
removeFromMap(regsByIdentity, registrationRemoved.getClientTransportData().getIdentity(),
registrationRemoved);
}
return new Deregistration(registrationRemoved, observationsRemoved);
}
} finally {
lock.writeLock().unlock();
}
return null;
}
@Override
public UpdatedRegistration updateRegistration(RegistrationUpdate update) {
try {
lock.writeLock().lock();
Registration registration = getRegistration(update.getRegistrationId());
if (registration == null) {
return null;
} else {
Registration updatedRegistration = update.update(registration);
regsByEp.put(updatedRegistration.getEndpoint(), updatedRegistration);
// If registration is already associated to this address we don't care as we only want to keep the most
// recent binding.
regsByAddr.put(updatedRegistration.getSocketAddress(), updatedRegistration);
if (!registration.getSocketAddress().equals(updatedRegistration.getSocketAddress())) {
removeFromMap(regsByAddr, registration.getSocketAddress(), registration);
}
regsByIdentity.put(updatedRegistration.getClientTransportData().getIdentity(), updatedRegistration);
if (!registration.getClientTransportData().getIdentity()
.equals(updatedRegistration.getClientTransportData().getIdentity())) {
removeFromMap(regsByIdentity, registration.getClientTransportData().getIdentity(), registration);
}
regsByRegId.put(updatedRegistration.getId(), updatedRegistration);
return new UpdatedRegistration(registration, updatedRegistration);
}
} finally {
lock.writeLock().unlock();
}
}
@Override
public Registration getRegistration(String registrationId) {
try {
lock.readLock().lock();
return regsByRegId.get(registrationId);
} finally {
lock.readLock().unlock();
}
}
@Override
public Registration getRegistrationByEndpoint(String endpoint) {
try {
lock.readLock().lock();
return regsByEp.get(endpoint);
} finally {
lock.readLock().unlock();
}
}
@Override
public Registration getRegistrationByAdress(InetSocketAddress address) {
try {
lock.readLock().lock();
return regsByAddr.get(address);
} finally {
lock.readLock().unlock();
}
}
@Override
public Registration getRegistrationByIdentity(LwM2mIdentity identity) {
try {
lock.readLock().lock();
return regsByIdentity.get(identity);
} finally {
lock.readLock().unlock();
}
}
@Override
public Iterator<Registration> getAllRegistrations() {
try {
lock.readLock().lock();
return new ArrayList<>(regsByEp.values()).iterator();
} finally {
lock.readLock().unlock();
}
}
@Override
public Deregistration removeRegistration(String registrationId) {
try {
lock.writeLock().lock();
Registration registration = getRegistration(registrationId);
if (registration != null) {
Collection<Observation> observationsRemoved = unsafeRemoveAllObservations(registration.getId());
regsByEp.remove(registration.getEndpoint());
removeFromMap(regsByAddr, registration.getSocketAddress(), registration);
removeFromMap(regsByRegId, registration.getId(), registration);
removeFromMap(regsByIdentity, registration.getClientTransportData().getIdentity(), registration);
return new Deregistration(registration, observationsRemoved);
}
return null;
} finally {
lock.writeLock().unlock();
}
}
/* *************** Leshan Observation API **************** */
@Override
public Collection<Observation> addObservation(String registrationId, Observation observation, boolean addIfAbsent) {
List<Observation> removed = new ArrayList<>();
try {
lock.writeLock().lock();
if (!regsByRegId.containsKey(registrationId)) {
throw new IllegalStateException(String.format(
"can not add observation %s there is no registration with id %s", observation, registrationId));
}
if (observation instanceof SingleObservation) {
if (validateObserveResource(((SingleObservation)observation).getPath(), registrationId)) {
updateSingleObservation(registrationId, observation, addIfAbsent, removed);
// cancel existing observations for the same path and registration id.
cancelObservation (observation, registrationId, removed);
}
} else {
ContentFormat ct = ((CompositeObservation) observation).getResponseContentFormat();
Map<String, String> ctx = observation.getContext();
String serializedObservation = extractSerializedObservation(observation);
JsonNode nodeSerObs = JacksonUtil.toJsonNode(serializedObservation);
((CompositeObservation)observation).getPaths().forEach(path -> {
if (validateObserveResource(path, registrationId)) {
String serializedObs = createSerializedSingleObservation(nodeSerObs, path.toString());
Observation singleObservation = createSingleObservation(registrationId, path, ct, ctx, serializedObs);
updateSingleObservation(registrationId, singleObservation, addIfAbsent, removed);
// cancel existing observations for the same path and registration id.
cancelObservation (singleObservation, registrationId, removed);
}
});
}
} finally {
lock.writeLock().unlock();
}
return removed;
}
private boolean validateObserveResource(LwM2mPath path, String registrationId){
// check if the resource is readable.
if (path.isResource() || path.isResourceInstance()) {
ObjectModel objectModel = modelProvider.getObjectModel(getRegistration(registrationId)).getObjectModel(path.getObjectId());
ResourceModel resourceModel = objectModel == null ? null : objectModel.resources.get(path.getResourceId());
if (resourceModel == null) {
return false;
} else if (!resourceModel.operations.isReadable()) {
return false;
} else if (path.isResourceInstance() && !resourceModel.multiple) {
return false;
}
}
return true;
}
private void updateSingleObservation (String registrationId, Observation observation, boolean addIfAbsent, List<Observation> removed) {
Observation previousObservation;
ObservationIdentifier id = observation.getId();
if (addIfAbsent) {
if (!obsByToken.containsKey(id))
previousObservation = obsByToken.put(id, observation);
else
previousObservation = obsByToken.get(id);
} else {
previousObservation = obsByToken.put(id, observation);
}
if (!tokensByRegId.containsKey(registrationId)) {
tokensByRegId.put(registrationId, new HashSet<ObservationIdentifier>());
}
tokensByRegId.get(registrationId).add(id);
// log any collisions
if (previousObservation != null) {
removed.add(previousObservation);
LOG.warn("Token collision ? observation [{}] will be replaced by observation [{}] ",
previousObservation, observation);
}
}
private Observation createSingleObservation(String registrationId, LwM2mPath target, ContentFormat ct,
Map<String, String> ctx, String serializedObservation) {
Token token = tokenGenerator.createToken(Scope.SHORT_TERM);
Map<String, String> protocolData = Collections.emptyMap();
if (serializedObservation != null) {
protocolData = new HashMap<>();
protocolData.put(CTX_CF_OBERSATION, serializedObservation);
}
return new SingleObservation(new ObservationIdentifier(token.getBytes()), registrationId, target, ct, ctx, protocolData);
}
private String createSerializedSingleObservation(JsonNode nodeSerObs, String path){
if (nodeSerObs.has("context")){
((ObjectNode) nodeSerObs.get("context")).put("leshan-path", path + "\n");
return JacksonUtil.toString(nodeSerObs);
}
return null;
}
@Override
public Observation removeObservation(String registrationId, ObservationIdentifier observationId) {
try {
lock.writeLock().lock();
Observation observation = unsafeGetObservation(observationId);
if (observation != null && registrationId.equals(observation.getRegistrationId())) {
unsafeRemoveObservation(observationId);
return observation;
}
return null;
} finally {
lock.writeLock().unlock();
}
}
@Override
public Observation getObservation(String registrationId, ObservationIdentifier observationId) {
try {
lock.readLock().lock();
Observation observation = unsafeGetObservation(observationId);
if (observation != null && registrationId.equals(observation.getRegistrationId())) {
return observation;
}
return null;
} finally {
lock.readLock().unlock();
}
}
@Override
public Observation getObservation(ObservationIdentifier observationId) {
try {
lock.readLock().lock();
Observation observation = unsafeGetObservation(observationId);
if (observation != null) {
return observation;
}
return null;
} finally {
lock.readLock().unlock();
}
}
/**
* Prepare for Cancel one Observation
* @param registrationId
* @return
*/
@Override
public Collection<Observation> getObservations(String registrationId) {
try {
lock.readLock().lock();
return unsafeGetObservations(registrationId);
} finally {
lock.readLock().unlock();
}
}
/**
* CancelAllObservation
* @param registrationId
* @return
*/
@Override
public Collection<Observation> removeObservations(String registrationId) {
try {
lock.writeLock().lock();
return unsafeRemoveAllObservations(registrationId);
} finally {
lock.writeLock().unlock();
}
}
/* *************** Observation utility functions **************** */
private Observation unsafeGetObservation(ObservationIdentifier token) {
Observation obs = obsByToken.get(token);
return obs;
}
private void cancelObservation (Observation observation, String registrationId, List<Observation> removed) {
for (Observation obs : unsafeGetObservations(registrationId)) {
cancelExistingObservation(observation, obs, removed);
}
}
private void cancelExistingObservation(Observation observation, Observation obs, List<Observation> removed) {
LwM2mPath pathObservation = ((SingleObservation)observation).getPath();
LwM2mPath pathObs = ((SingleObservation)obs).getPath();
if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObservation = "3", pathObs = "3/0/9"
(pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) {
unsafeRemoveObservation(obs.getId());
removed.add(obs);
} else if (!pathObservation.equals(pathObs) && pathObservation.startWith(pathObs)) { // pathObservation = "3/0/9", pathObs = "3"
unsafeRemoveObservation(observation.getId());
}
}
private void unsafeRemoveObservation(ObservationIdentifier observationId) {
Observation removed = obsByToken.remove(observationId);
if (removed != null) {
String registrationId = removed.getRegistrationId();
Set<ObservationIdentifier> tokens = tokensByRegId.get(registrationId);
tokens.remove(observationId);
if (tokens.isEmpty()) {
tokensByRegId.remove(registrationId);
}
}
}
/**
* CancelAllObservation
* @param registrationId
* @return
*/
private Collection<Observation> unsafeRemoveAllObservations(String registrationId) {
Collection<Observation> removed = new ArrayList<>();
Set<ObservationIdentifier> ids = tokensByRegId.get(registrationId);
if (ids != null) {
for (ObservationIdentifier id : ids) {
Observation observationRemoved = obsByToken.remove(id);
if (observationRemoved != null) {
removed.add(observationRemoved);
}
}
}
tokensByRegId.remove(registrationId);
return removed;
}
private Collection<Observation> unsafeGetObservations(String registrationId) {
Collection<Observation> result = new ArrayList<>();
Set<ObservationIdentifier> ids = tokensByRegId.get(registrationId);
if (ids != null) {
for (ObservationIdentifier id : ids) {
Observation obs = unsafeGetObservation(id);
if (obs != null) {
result.add(obs);
}
}
}
return result;
}
/* *************** Expiration handling **************** */
@Override
public void setExpirationListener(ExpirationListener listener) {
this.expirationListener = listener;
}
/**
* start the registration store, will start regular cleanup of dead registrations.
*/
@Override
public synchronized void start() {
if (!started) {
started = true;
cleanerTask = schedExecutor.scheduleAtFixedRate(new TbInMemoryRegistrationStore.Cleaner(), cleanPeriod, cleanPeriod, TimeUnit.SECONDS);
}
}
/**
* Stop the underlying cleanup of the registrations.
*/
@Override
public synchronized void stop() {
if (started) {
started = false;
if (cleanerTask != null) {
cleanerTask.cancel(false);
cleanerTask = null;
}
}
}
/**
* Destroy "cleanup" scheduler.
*/
@Override
public synchronized void destroy() {
started = false;
schedExecutor.shutdownNow();
try {
schedExecutor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOG.warn("Destroying InMemoryRegistrationStore was interrupted.", e);
}
}
private class Cleaner implements Runnable {
@Override
public void run() {
try {
Collection<Registration> allRegs = new ArrayList<>();
try {
lock.readLock().lock();
allRegs.addAll(regsByEp.values());
} finally {
lock.readLock().unlock();
}
for (Registration reg : allRegs) {
if (!reg.isAlive()) {
// force de-registration
Deregistration removedRegistration = removeRegistration(reg.getId());
expirationListener.registrationExpired(removedRegistration.getRegistration(),
removedRegistration.getObservations());
}
}
} catch (Exception e) {
LOG.warn("Unexpected Exception while registration cleaning", e);
}
}
}
// boolean remove(Object key, Object value) exist only since java8
// So this method is here only while we want to support java 7
protected <K, V> boolean removeFromMap(Map<K, V> map, K key, V value) {
if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
map.remove(key);
return true;
} else
return false;
}
}

326
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java

@ -19,11 +19,11 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.Token;
import org.eclipse.californium.core.network.serialization.UdpDataParser;
import org.eclipse.californium.core.network.serialization.UdpDataSerializer;
import org.eclipse.californium.core.observe.ObservationStoreException;
import org.eclipse.leshan.core.Destroyable;
import org.eclipse.leshan.core.Startable;
import org.eclipse.leshan.core.Stoppable;
import org.eclipse.leshan.core.californium.ObserveUtil;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.observation.CompositeObservation;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.observation.ObservationIdentifier;
import org.eclipse.leshan.core.observation.SingleObservation;
@ -82,6 +82,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
private static final String REG_EP_IDENTITY = "EP:IDENTITY:"; // secondary index key (Identity => Endpoint)
private static final String LOCK_EP = "LOCK:EP:";
private static final byte[] OBS_TKN = "OBS:TKN:".getBytes(UTF_8);
private static final byte[] OBS_TKN_GET_ALL = "OBS:TKN:*".getBytes(UTF_8);
private static final String OBS_TKNS_REGID_IDX = "TKNS:REGID:"; // secondary index (token list by registration)
private static final byte[] EXP_EP = "EXP:EP".getBytes(UTF_8); // a sorted set used for registration expiration
// (expiration date, Endpoint)
@ -477,16 +478,56 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
lock.lock();
// Add and Get previous observation
byte[] previousValue;
byte[] previousValue = null;
// byte[] key;
// byte[] serializeObs;
byte[] key = toKey(OBS_TKN, observation.getId().getBytes());
byte[] serializeObs = serializeObs(observation);
if (addIfAbsent) {
// we analyze the present previous value
Collection<LwM2mPath> previousPaths = getPreviousPaths(observation, connection);
if (observation instanceof SingleObservation) {
if (previousPaths == null) {
connection.stringCommands().set(key, serializeObs);
} else if (!addIfAbsent) {
}
previousValue = connection.get(key);
if (previousValue == null || previousValue.length == 0) {
connection.set(key, serializeObs);
if (observation instanceof CompositeObservation) {
List<LwM2mPath> paths = ((CompositeObservation) observation).getPaths();
if (paths.size()==1) {
key = toKey(OBS_TKN, observation.getId().getBytes());
serializeObs = serializeObs(observation);
previousValue = connection.stringCommands().get(key);
if (previousValue == null || previousValue.length == 0) {
// connection.set(key, serializeObs);
connection.stringCommands().set(key, serializeObs);
}
paths.forEach(pObs -> {
});
}
else {
paths.forEach(pObs -> {
});
}
} else if (observation instanceof SingleObservation) {
// previousValue = connection.get(key);
previousValue = connection.stringCommands().get(key);
if (previousValue == null || previousValue.length == 0) {
connection.stringCommands().set(key, serializeObs);
// connection.set(key, serializeObs);
}
}
}
} else {
previousValue = connection.getSet(key, serializeObs);
// previousValue = connection.getSet(key, serializeObs);
previousValue = connection.stringCommands().getSet(key, serializeObs);
}
// secondary index to get the list by registrationId
@ -501,11 +542,36 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
}
// cancel existing observations for the same path and registration id.
for (Observation obs : getObservations(connection, registrationId)) {
//TODO: should be able to use CompositeObservation
if (((SingleObservation)observation).getPath().equals(((SingleObservation)obs).getPath())
&& !observation.getId().equals(obs.getId())) {
removed.add(obs);
unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes());
if (obs instanceof CompositeObservation){
((CompositeObservation)obs).getPaths().forEach(pObs -> {
if (observation instanceof CompositeObservation) {
((CompositeObservation)observation).getPaths().forEach(pObservation -> {
//TODO include obs every (SingleObservation) in observation Composite
});
log.info("observation obs -> CompositeObservation");
} else if (observation instanceof SingleObservation) {
if (((SingleObservation) observation).getPath().equals(((SingleObservation)obs).getPath())
&& !observation.getId().equals(obs.getId())){
removed.add(obs);
unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes());
}
}
});
} else if (obs instanceof SingleObservation) {
if (observation instanceof CompositeObservation) {
((CompositeObservation)observation).getPaths().forEach(pObservation -> {
//TODO include obs every (SingleObservation) in observation Composite
});
log.info("observation -> CompositeObservation, obs -> SingleObservation");
} else if (observation instanceof SingleObservation) {
if (((SingleObservation) observation).getPath().equals(((SingleObservation)obs).getPath())
&& !observation.getId().equals(obs.getId())){
removed.add(obs);
unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes());
}
}
}
}
} finally {
@ -573,10 +639,11 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
}
}
private Collection<Observation> getObservations(RedisConnection connection, String registrationId) {
Collection<Observation> result = new ArrayList<>();
for (byte[] token : connection.lRange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) {
byte[] obs = connection.get(toKey(OBS_TKN, token));
for (byte[] token : connection.listCommands().lRange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) {
byte[] obs = connection.stringCommands().get(toKey(OBS_TKN, token));
if (obs != null) {
result.add(deserializeObs(obs));
}
@ -584,127 +651,156 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
return result;
}
@Override
public Collection<Observation> removeObservations(String registrationId) {
try (var connection = connectionFactory.getConnection()) {
// check registration exists
Registration registration = getRegistration(connection, registrationId);
if (registration == null)
return Collections.emptyList();
private Collection<LwM2mPath> getPreviousPaths(Observation observation, RedisConnection connection) {
Collection<LwM2mPath> result = new ArrayList<>();
Collection<Observation> observations = getObservations(connection, observation.getRegistrationId());
observations.forEach(obs -> {
// get endpoint and create lock
String endpoint = registration.getEndpoint();
Lock lock = null;
String lockKey = toLockKey(endpoint);
try {
lock = redisLock.obtain(lockKey);
lock.lock();
return unsafeRemoveAllObservations(connection, registrationId);
} finally {
if (lock != null) {
lock.unlock();
}
Collection<LwM2mPath> prevPath = getPreviousPaths( observation, obs);
if (prevPath != null){
result.addAll(prevPath);
}
}
}
/* *************** Californium ObservationStore API **************** */
public org.eclipse.californium.core.observe.Observation putIfAbsent(Token token,
org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException {
return add(obs, true);
});
return result.size() > 0 ? result : null;
}
public org.eclipse.californium.core.observe.Observation put(Token token,
org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException {
return add(obs, false);
}
private org.eclipse.californium.core.observe.Observation add(org.eclipse.californium.core.observe.Observation obs, boolean ifAbsent) throws ObservationStoreException {
String endpoint = ObserveUtil.validateCoapObservation(obs);
org.eclipse.californium.core.observe.Observation previousObservation = null;
try (var connection = connectionFactory.getConnection()) {
Lock lock = null;
String lockKey = toLockKey(endpoint);
try {
lock = redisLock.obtain(lockKey);
lock.lock();
private Collection<LwM2mPath> getPreviousPaths(Observation observation, Observation prevObs) {
Collection<LwM2mPath> prevPath = new ArrayList<>();
if (observation instanceof SingleObservation && prevObs instanceof SingleObservation) {
String registrationId = ObserveUtil.extractRegistrationId(obs);
if (!connection.exists(toRegIdKey(registrationId)))
throw new ObservationStoreException("no registration for this Id");
byte[] key = toKey(OBS_TKN, obs.getRequest().getToken().getBytes());
Observation obsLeshan = buildLwM2mObservationFromCfToLeshanCore(obs);
byte[] serializeObs = serializeObs(obsLeshan);
byte[] previousValue;
if (ifAbsent) {
previousValue = connection.get(key);
if (previousValue == null || previousValue.length == 0) {
connection.set(key, serializeObs);
previousValue = connection.getSet(key, serializeObs);
} else {
return buildCoapObservationFromLeshanCoreToCfCore(deserializeObs(previousValue));
}
} else {
previousValue = connection.getSet(key, serializeObs);
}
} else if (observation instanceof CompositeObservation) {
if (prevObs instanceof CompositeObservation) { // observation instanceof CompositeObservation && prevObs instanceof CompositeObservation
// secondary index to get the list by registrationId
connection.lPush(toKey(OBS_TKNS_REGID_IDX, registrationId), obs.getRequest().getToken().getBytes());
} else { // observation instanceof CompositeObservation && prevObs instanceof SingleObservation
// log any collisions
if (previousValue != null && previousValue.length != 0) {
previousObservation = buildCoapObservationFromLeshanCoreToCfCore(deserializeObs(previousValue));
LOG.warn(
"Token collision ? observation from request [{}] will be replaced by observation from request [{}] ",
previousObservation.getRequest(), obs.getRequest());
}
} finally {
if (lock != null) {
lock.unlock();
}
}
} else { // observation instanceof SingleObservation prevObs instanceof CompositeObservation
}
return previousObservation;
return prevPath.size() > 0 ? prevPath : null;
}
public void remove(Token token) {
@Override
public Collection<Observation> removeObservations(String registrationId) {
try (var connection = connectionFactory.getConnection()) {
byte[] tokenKey = toKey(OBS_TKN, token.getBytes());
// fetch the observation by token
byte[] serializedObs = connection.get(tokenKey);
if (serializedObs == null)
return;
// org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs);
org.eclipse.californium.core.observe.Observation obs = null;
String registrationId = ObserveUtil.extractRegistrationId(obs);
// check registration exists
Registration registration = getRegistration(connection, registrationId);
if (registration == null) {
LOG.warn("Unable to remove observation {}, registration {} does not exist anymore", obs.getRequest(),
registrationId);
return;
}
if (registration == null)
return Collections.emptyList();
// get endpoint and create lock
String endpoint = registration.getEndpoint();
Lock lock = null;
String lockKey = toLockKey(endpoint);
try {
lock = redisLock.obtain(lockKey);
lock.lock();
unsafeRemoveObservation(connection, registrationId, token.getBytes());
return unsafeRemoveAllObservations(connection, registrationId);
} finally {
if (lock != null) {
lock.unlock();
}
}
}
}
/* *************** Californium ObservationStore API **************** */
// public org.eclipse.californium.core.observe.Observation putIfAbsent(Token token,
// org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException {
// return add(obs, true);
// }
// public org.eclipse.californium.core.observe.Observation put(Token token,
// org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException {
// return add(obs, false);
// }
// private org.eclipse.californium.core.observe.Observation add(org.eclipse.californium.core.observe.Observation obs, boolean ifAbsent) throws ObservationStoreException {
// String endpoint = ObserveUtil.validateCoapObservation(obs);
// org.eclipse.californium.core.observe.Observation previousObservation = null;
//
// try (var connection = connectionFactory.getConnection()) {
// Lock lock = null;
// String lockKey = toLockKey(endpoint);
// try {
// lock = redisLock.obtain(lockKey);
// lock.lock();
//
// String registrationId = ObserveUtil.extractRegistrationId(obs);
// if (!connection.exists(toRegIdKey(registrationId)))
// throw new ObservationStoreException("no registration for this Id");
// byte[] key = toKey(OBS_TKN, obs.getRequest().getToken().getBytes());
// Observation obsLeshan = buildLwM2mObservationFromCfToLeshanCore(obs);
// byte[] serializeObs = serializeObs(obsLeshan);
// byte[] previousValue;
// if (ifAbsent) {
// previousValue = connection.get(key);
// if (previousValue == null || previousValue.length == 0) {
// connection.set(key, serializeObs);
// previousValue = connection.getSet(key, serializeObs);
// } else {
// return buildCoapObservationFromLeshanCoreToCfCore(deserializeObs(previousValue));
// }
// } else {
// previousValue = connection.getSet(key, serializeObs);
// }
//
// // secondary index to get the list by registrationId
// connection.lPush(toKey(OBS_TKNS_REGID_IDX, registrationId), obs.getRequest().getToken().getBytes());
//
// // log any collisions
// if (previousValue != null && previousValue.length != 0) {
// previousObservation = buildCoapObservationFromLeshanCoreToCfCore(deserializeObs(previousValue));
// LOG.warn(
// "Token collision ? observation from request [{}] will be replaced by observation from request [{}] ",
// previousObservation.getRequest(), obs.getRequest());
// }
// } finally {
// if (lock != null) {
// lock.unlock();
// }
// }
// }
// return previousObservation;
// }
//
// public void remove(Token token) {
// try (var connection = connectionFactory.getConnection()) {
// byte[] tokenKey = toKey(OBS_TKN, token.getBytes());
//
// // fetch the observation by token
// byte[] serializedObs = connection.get(tokenKey);
// if (serializedObs == null)
// return;
//
//// org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs);
// org.eclipse.californium.core.observe.Observation obs = null;
// String registrationId = ObserveUtil.extractRegistrationId(obs);
// Registration registration = getRegistration(connection, registrationId);
// if (registration == null) {
// LOG.warn("Unable to remove observation {}, registration {} does not exist anymore", obs.getRequest(),
// registrationId);
// return;
// }
//
// String endpoint = registration.getEndpoint();
// Lock lock = null;
// String lockKey = toLockKey(endpoint);
// try {
// lock = redisLock.obtain(lockKey);
// lock.lock();
//
// unsafeRemoveObservation(connection, registrationId, token.getBytes());
// } finally {
// if (lock != null) {
// lock.unlock();
// }
// }
// }
//
// }
public Observation get(Token token) {
try (var connection = connectionFactory.getConnection()) {
byte[] obs = connection.get(toKey(OBS_TKN, token.getBytes()));
@ -745,15 +841,15 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
return observationSerDes.serialize(obs);
}
private org.eclipse.leshan.core.observation.Observation buildLwM2mObservationFromCfToLeshanCore(
org.eclipse.californium.core.observe.Observation observation) {
String serializedObservation = observationSerDesCoap.serialize(observation);
return serializedObservation == null ? null : ObserveUtil.createLwM2mObservation(observation, serializedObservation);
}
private org.eclipse.californium.core.observe.Observation buildCoapObservationFromLeshanCoreToCfCore(Observation obs) {
String serializedObservation = ObserveUtil.extractSerializedObservation(obs);
return serializedObservation == null ? null : observationSerDesCoap.deserialize(serializedObservation);
}
// private Observation buildLwM2mObservationFromCfToLeshanCore(
// org.eclipse.californium.core.observe.Observation observation) {
// String serializedObservation = observationSerDesCoap.serialize(observation);
// return serializedObservation == null ? null : ObserveUtil.createLwM2mObservation(observation, serializedObservation);
// }
// private org.eclipse.californium.core.observe.Observation buildCoapObservationFromLeshanCoreToCfCore(Observation obs) {
// String serializedObservation = ObserveUtil.extractSerializedObservation(obs);
// return serializedObservation == null ? null : observationSerDesCoap.deserialize(serializedObservation);
// }
private Observation deserializeObs(byte[] data) {
return data == null ? null : observationSerDes.deserialize(data);

7
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java

@ -16,7 +16,7 @@
package org.thingsboard.server.transport.lwm2m.server.store;
import lombok.RequiredArgsConstructor;
import org.eclipse.leshan.server.registration.InMemoryRegistrationStore;
import org.eclipse.californium.core.network.RandomTokenGenerator;
import org.eclipse.leshan.server.registration.RegistrationStore;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@ -25,6 +25,7 @@ import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider;
import java.util.Optional;
@ -37,10 +38,12 @@ public class TbLwM2mStoreFactory {
private final LwM2MTransportServerConfig config;
private final LwM2mCredentialsSecurityInfoValidator validator;
private final LwM2mVersionedModelProvider modelProvider;
@Bean
private RegistrationStore registrationStore() {
return redisConfiguration.isPresent() ?
new TbLwM2mRedisRegistrationStore(getConnectionFactory()) : new InMemoryRegistrationStore(config.getCleanPeriodInSec());
new TbLwM2mRedisRegistrationStore(getConnectionFactory()) : new TbInMemoryRegistrationStore(new RandomTokenGenerator(config.getCoapConfig()), config.getCleanPeriodInSec(), modelProvider);
}
@Bean

29
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java

@ -20,6 +20,8 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -100,8 +102,6 @@ import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mSecurityStore;
import org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -341,6 +341,8 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
} else if (v instanceof LwM2mResource) {
this.updateResourcesValue(lwM2MClient, (LwM2mResource) v, k.toString(), Mode.UPDATE, responseCode);
}
} else {
this.onErrorObservation(registration, k + ": value in composite response is null");
}
});
clientContext.update(lwM2MClient);
@ -348,6 +350,12 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
}
}
public void onErrorObservation(Registration registration, String errorMsg) {
LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint());
logService.log(lwM2MClient, LOG_LWM2M_ERROR + ": " + errorMsg);
}
/**
* Sending updated value to thingsboard from SendListener.dataReceived: object, instance, SingleResource or MultipleResource
*
@ -557,7 +565,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
private void updateObjectInstanceResourceValue(LwM2mClient client, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer, int code) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer));
lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> {
String pathRez = pathIds.toString() + "/" + resourceId;
String pathRez = pathIdVer + "/" + resourceId;
this.updateResourcesValue(client, resource, pathRez, Mode.UPDATE, code);
});
}
@ -600,7 +608,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
otaService.onCurrentSoftwareResultUpdate(lwM2MClient, (Long) lwM2mResource.getValue());
}
if (ResponseCode.BAD_REQUEST.getCode() > code) {
this.updateAttrTelemetry(registration, Collections.singleton(path));
this.updateAttrTelemetry(registration, path);
}
} else {
log.error("Fail update path [{}] Resource [{}]", path, lwM2mResource);
@ -617,9 +625,12 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
*
* @param registration - Registration LwM2M Client
*/
private void updateAttrTelemetry(Registration registration, Set<String> paths) {
public void updateAttrTelemetry(Registration registration, String path) {
try {
ResultsAddKeyValueProto results = this.getParametersFromProfile(registration, paths);
ResultsAddKeyValueProto results = this.getParametersFromProfile(registration, path);
if (path.equals("/3_1.2/0/9")) {
log.info("UpdateTelemetry paths [{}] key: [{}] value [{}]", path, results.getResultTelemetries().get(0).getKey(), results.getResultTelemetries().get(0).getLongV());
}
SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration);
if (results != null && sessionInfo != null) {
if (results.getResultAttributes().size() > 0) {
@ -669,13 +680,13 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
* @param registration - Registration LwM2M Client
* @param path -
*/
private ResultsAddKeyValueProto getParametersFromProfile(Registration registration, Set<String> path) {
if (path != null && path.size() > 0) {
private ResultsAddKeyValueProto getParametersFromProfile(Registration registration, String path) {
if (!path.isEmpty()) {
ResultsAddKeyValueProto results = new ResultsAddKeyValueProto();
var profile = clientContext.getProfile(registration);
List<TransportProtos.KeyValueProto> resultAttributes = new ArrayList<>();
profile.getObserveAttr().getAttribute().forEach(pathIdVer -> {
if (path.contains(pathIdVer)) {
if (path.equals(pathIdVer)) {
TransportProtos.KeyValueProto kvAttr = this.getKvToThingsBoard(pathIdVer, registration);
if (kvAttr != null) {
resultAttributes.add(kvAttr);

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java

@ -47,6 +47,7 @@ public interface LwM2mUplinkMsgHandler {
void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response);
void onUpdateValueAfterReadCompositeResponse(Registration registration, ReadCompositeResponse response);
void onErrorObservation(Registration registration, String errorMsg);
void onUpdateValueWithSendRequest(Registration registration, SendRequest sendRequest);

94
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java

@ -79,7 +79,7 @@ public class LwM2MTransportUtil {
public static final String LOG_LWM2M_INFO = "info";
public static final String LOG_LWM2M_ERROR = "error";
public static final String LOG_LWM2M_WARN = "warn";
public static final int BOOTSTRAP_DEFAULT_SHORT_ID = 0;
public static final int BOOTSTRAP_DEFAULT_SHORT_ID_0 = 0;
public enum LwM2MClientStrategy {
CLIENT_STRATEGY_1(1, "Read only resources marked as observation"),
@ -223,6 +223,9 @@ public class LwM2MTransportUtil {
public static String convertObjectIdToVersionedId(String path, Registration registration) {
String ver = String.valueOf(registration.getSupportedObject().get(new LwM2mPath(path).getObjectId()));
return convertObjectIdToVerId(path, ver);
}
public static String convertObjectIdToVerId(String path, String ver) {
ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString();
try {
String[] keyArray = path.split(LWM2M_SEPARATOR_PATH);
@ -245,60 +248,6 @@ public class LwM2MTransportUtil {
}
}
/**
* As example:
* a)Write-Attributes/3/0/9?pmin=1 means the Battery Level value will be notified
* to the Server with a minimum interval of 1sec;
* this value is set at theResource level.
* b)Write-Attributes/3/0/9?pmin means the Battery Level will be notified
* to the Server with a minimum value (pmin) given by the default one
* (resource 2 of Object Server ID=1),
* or with another value if this Attribute has been set at another level
* (Object or Object Instance: see section5.1.1).
* c)Write-Attributes/3/0?pmin=10 means that all Resources of Instance 0 of the Object ‘Device (ID:3)’
* will be notified to the Server with a minimum interval of 10 sec;
* this value is set at the Object Instance level.
* d)Write-Attributes /3/0/9?gt=45&st=10 means the Battery Level will be notified to the Server
* when:
* a.old value is 20 and new value is 35 due to step condition
* b.old value is 45 and new value is 50 due to gt condition
* c.old value is 50 and new value is 40 due to both gt and step conditions
* d.old value is 35 and new value is 20 due to step conditione)
* Write-Attributes /3/0/9?lt=20&gt=85&st=10 means the Battery Level will be notified to the Server
* when:
* a.old value is 17 and new value is 24 due to lt condition
* b.old value is 75 and new value is 90 due to both gt and step conditions
* String uriQueries = "pmin=10&pmax=60";
* AttributeSet attributes = AttributeSet.parse(uriQueries);
* WriteAttributesRequest request = new WriteAttributesRequest(target, attributes);
* Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45"));
* Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10"));
* Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60");
* Attribute [] attrs = {gt, st};
*/
// public static SimpleDownlinkRequest createWriteAttributeRequest(String target, Object params, LwM2mUplinkMsgHandler serviceImpl) {
// AttributeSet attrSet = new AttributeSet(createWriteAttributes(params, serviceImpl, target));
// return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null;
// }
// private static Attribute[] createWriteAttributes(Object params, LwM2mUplinkMsgHandler serviceImpl, String target) {
// List<Attribute> attributeLists = new ArrayList<>();
// Map<String, Object> map = JacksonUtil.convertValue(params, new TypeReference<>() {
// });
// map.forEach((k, v) -> {
// if (StringUtils.trimToNull(v.toString()) != null) {
// Object attrValue = convertWriteAttributes(k, v, serviceImpl, target);
// if (attrValue != null) {
// Attribute attribute = createAttribute(k, attrValue);
// if (attribute != null) {
// attributeLists.add(new Attribute(k, attrValue));
// }
// }
// }
// });
// return attributeLists.toArray(Attribute[]::new);
// }
/**
* "UNSIGNED_INTEGER": // Number -> Integer Example:
* Alarm Timestamp [32-bit unsigned integer]
@ -357,41 +306,6 @@ public class LwM2MTransportUtil {
STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId)));
}
// public static Object convertWriteAttributes(String type, Object value, LwM2mUplinkMsgHandler serviceImpl, String target) {
// switch (type) {
// /** Integer [0:255]; */
// case DIMENSION:
// Long dim = (Long) serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), INTEGER, new LwM2mPath(target));
// return dim >= 0 && dim <= 255 ? dim : null;
// /**String;*/
// case OBJECT_VERSION:
// return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), STRING, new LwM2mPath(target));
// /**INTEGER */
// case MINIMUM_PERIOD:
// case MAXIMUM_PERIOD:
// return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), INTEGER, new LwM2mPath(target));
// /**Float; */
// case GREATER_THAN:
// case LESSER_THAN:
// case STEP:
// if (value.getClass().getSimpleName().equals("String")) {
// value = Double.valueOf((String) value);
// }
// return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), FLOAT, new LwM2mPath(target));
// default:
// return null;
// }
// }
// private static Attribute createAttribute(String key, Object attrValue) {
// try {
// return new Attribute(key, attrValue);
// } catch (Exception e) {
// log.error("CreateAttribute, not valid parameter key: [{}], attrValue: [{}], error: [{}]", key, attrValue, e.getMessage());
// return null;
// }
// }
/**
* @param lwM2MClient -
* @param path -

Loading…
Cancel
Save