Browse Source

lwm2m: update tests lwm2mAttributes

pull/11784/head
nick 2 years ago
parent
commit
8f3abdf139
  1. 3
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  2. 4
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  3. 73
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  4. 729
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java
  5. 71
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java
  6. 3
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java
  7. 12
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java
  8. 175
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java

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

@ -177,7 +177,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
protected LwM2MTestClient lwM2MTestClient;
private String[] resources;
protected String deviceId;
protected boolean isWriteAttribute = false;
protected boolean supportFormatOnly_SenMLJSON_SenMLCBOR = false;
@Before
@ -319,7 +318,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
try (ServerSocket socket = new ServerSocket(0)) {
int clientPort = socket.getLocalPort();
lwM2MTestClient.init(security, securityBs, clientPort, isRpc,
this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, isWriteAttribute,
this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest,
clientDtlsCidLength, queueMode, supportFormatOnly_SenMLJSON_SenMLCBOR);
}
}

4
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java

@ -141,7 +141,7 @@ public class LwM2MTestClient {
public void init(Security security, Security securityBs, int port, boolean isRpc,
LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler,
LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength, boolean queueMode,
LwM2mClientContext clientContext, Integer cIdLength, boolean queueMode,
boolean supportFormatOnly_SenMLJSON_SenMLCBOR) throws InvalidDDFFileException, IOException {
Assert.assertNull("client already initialized", leshanClient);
this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler;
@ -151,7 +151,7 @@ public class LwM2MTestClient {
models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName));
}
LwM2mModel model = new StaticModel(models);
ObjectsInitializer initializer = isWriteAttribute ? new TbObjectsInitializer(model) : new ObjectsInitializer(model);
ObjectsInitializer initializer = new ObjectsInitializer(model);
if (securityBs != null && security != null) {
// SECURITY
security.setId(serverId);

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

@ -20,7 +20,7 @@ import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.servers.LwM2mServer;
import org.eclipse.leshan.core.Destroyable;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.model.ResourceModel.Type;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.request.argument.Arguments;
import org.eclipse.leshan.core.response.ExecuteResponse;
@ -30,7 +30,6 @@ import org.eclipse.leshan.core.response.WriteResponse;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PrimitiveIterator;
@ -46,9 +45,46 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
private static final Random RANDOM = new Random();
private static final int min = 5;
private static final int max = 50;
private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min,max + 1).iterator();
private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min, max + 1).iterator();
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21);
/**
* 0: DC power
* 1: Internal Battery
* 2: External Battery
* 3: Fuel Cell
* 4: Power over Ethernet
* 5: USB
* 6: AC (Mains) power
* 7: Solar
*/
private static final Map<Integer, Long> availablePowerSources =
Map.of(0, 0L, 1, 1L, 2, 7L);
private static Map<Integer, Long> powerSourceVoltage =
Map.of(0, 12000L, 1, 12400L, 7, 14600L); //mV
private static Map<Integer, Long> powerSourceCurrent =
Map.of(0, 72000L, 1, 2000L, 7, 25000L); // mA
/**
* 0=No error
* 1=Low battery power
* 2=External power supply off
* 3=GPS module failure
* 4=Low received signal strength
* 5=Out of memory
* 6=SMS failure
* 7=IP connectivity failure
* 8=Peripheral malfunction
* 9..15=Reserved for future use
* 16..32=Device specific error codes
*
* When the single Device Object Instance is initiated, there is only one error code Resource Instance whose value is equal to 0 that means no error.
* When the first error happens, the LwM2M Client changes error code Resource Instance to any non-zero value to indicate the error type.
* When any other error happens, a new error code Resource Instance is created.
* When an error associated with a Resource Instance is no longer present, that Resource Instance is deleted.
* When the single existing error is no longer present, the LwM2M Client returns to the original no error state where Instance 0 has value 0.
*/
private static Map<Integer, Long> errorCode =
Map.of(0, 0L); // 0-32
public SimpleLwM2MDevice() {
}
@ -81,15 +117,17 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
case 3:
return ReadResponse.success(resourceId, getFirmwareVersion());
case 6:
return ReadResponse.success(resourceId, getAvailablePowerSources(), ResourceModel.Type.INTEGER);
return ReadResponse.success(resourceId, getAvailablePowerSources(), Type.INTEGER);
case 7:
return ReadResponse.success(resourceId, getPowerSourceVoltage(), Type.INTEGER);
case 8:
return ReadResponse.success(resourceId, getPowerSourceCurrent(), Type.INTEGER);
case 9:
return ReadResponse.success(resourceId, getBatteryLevel());
case 10:
return ReadResponse.success(resourceId, getMemoryFree());
case 11:
Map<Integer, Long> errorCodes = new HashMap<>();
errorCodes.put(0, getErrorCode());
return ReadResponse.success(resourceId, errorCodes, ResourceModel.Type.INTEGER);
return ReadResponse.success(resourceId, getErrorCodes(), Type.INTEGER);
case 14:
return ReadResponse.success(resourceId, getUtcOffset());
case 15:
@ -156,16 +194,19 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
return "1.0.2";
}
private long getErrorCode() {
return 0;
private Map<Integer, ?> getAvailablePowerSources() {
return availablePowerSources;
}
private Map<Integer, Long> getAvailablePowerSources() {
Map<Integer, Long> availablePowerSources = new HashMap<>();
availablePowerSources.put(0, 1L);
availablePowerSources.put(1, 2L);
availablePowerSources.put(2, 5L);
return availablePowerSources;
private Map<Integer, ?> getPowerSourceVoltage() {
return powerSourceVoltage;
}
private Map<Integer, ?> getPowerSourceCurrent() {
return powerSourceCurrent;
}
private Map<Integer, ?> getErrorCodes() {
return errorCode;
}
private int getBatteryLevel() {

729
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java

@ -1,729 +0,0 @@
/**
* Copyright © 2016-2024 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.client;
import org.eclipse.leshan.client.LwM2mClient;
import org.eclipse.leshan.client.resource.BaseObjectEnabler;
import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
import org.eclipse.leshan.client.resource.LwM2mInstanceEnablerFactory;
import org.eclipse.leshan.client.resource.listener.ResourceListener;
import org.eclipse.leshan.client.servers.LwM2mServer;
import org.eclipse.leshan.client.servers.ServersInfoExtractor;
import org.eclipse.leshan.client.util.LinkFormatHelper;
import org.eclipse.leshan.core.Destroyable;
import org.eclipse.leshan.core.LwM2mId;
import org.eclipse.leshan.core.Startable;
import org.eclipse.leshan.core.Stoppable;
import org.eclipse.leshan.core.link.lwm2m.LwM2mLink;
import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute;
import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet;
import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mObject;
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.LwM2mResourceInstance;
import org.eclipse.leshan.core.request.BootstrapDeleteRequest;
import org.eclipse.leshan.core.request.BootstrapReadRequest;
import org.eclipse.leshan.core.request.BootstrapWriteRequest;
import org.eclipse.leshan.core.request.ContentFormat;
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.DownlinkRequest;
import org.eclipse.leshan.core.request.ExecuteRequest;
import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.request.ReadRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.request.WriteRequest.Mode;
import org.eclipse.leshan.core.response.BootstrapDeleteResponse;
import org.eclipse.leshan.core.response.BootstrapReadResponse;
import org.eclipse.leshan.core.response.BootstrapWriteResponse;
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.ObserveResponse;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.WriteAttributesResponse;
import org.eclipse.leshan.core.response.WriteResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyable, Startable, Stoppable {
private static Logger LOG = LoggerFactory.getLogger(DummyInstanceEnabler.class);
protected Map<Integer, LwM2mInstanceEnabler> instances;
protected LwM2mInstanceEnablerFactory instanceFactory;
protected ContentFormat defaultContentFormat;
private LinkFormatHelper tbLinkFormatHelper;
protected Map<LwM2mPath, LwM2mAttributeSet> lwM2mAttributes;
public TbLwm2mObjectEnabler(int id, ObjectModel objectModel, Map<Integer, LwM2mInstanceEnabler> instances,
LwM2mInstanceEnablerFactory instanceFactory, ContentFormat defaultContentFormat) {
super(id, objectModel);
this.instances = new HashMap<>(instances);
;
this.instanceFactory = instanceFactory;
this.defaultContentFormat = defaultContentFormat;
for (Entry<Integer, LwM2mInstanceEnabler> entry : this.instances.entrySet()) {
instances.put(entry.getKey(), entry.getValue());
listenInstance(entry.getValue(), entry.getKey());
}
this.lwM2mAttributes = new HashMap<>();
}
public TbLwm2mObjectEnabler(int id, ObjectModel objectModel) {
super(id, objectModel);
}
@Override
public synchronized List<Integer> getAvailableInstanceIds() {
List<Integer> ids = new ArrayList<>(instances.keySet());
Collections.sort(ids);
return ids;
}
@Override
public synchronized List<Integer> getAvailableResourceIds(int instanceId) {
LwM2mInstanceEnabler instanceEnabler = instances.get(instanceId);
if (instanceEnabler != null) {
return instanceEnabler.getAvailableResourceIds(getObjectModel());
} else {
return Collections.emptyList();
}
}
public synchronized void addInstance(int instanceId, LwM2mInstanceEnabler newInstance) {
instances.put(instanceId, newInstance);
listenInstance(newInstance, instanceId);
fireInstancesAdded(instanceId);
}
public synchronized LwM2mInstanceEnabler getInstance(int instanceId) {
return instances.get(instanceId);
}
public synchronized LwM2mInstanceEnabler removeInstance(int instanceId) {
LwM2mInstanceEnabler removedInstance = instances.remove(instanceId);
if (removedInstance != null) {
fireInstancesRemoved(removedInstance.getId());
}
return removedInstance;
}
@Override
protected CreateResponse doCreate(LwM2mServer server, CreateRequest request) {
if (!getObjectModel().multiple && instances.size() > 0) {
return CreateResponse.badRequest("an instance already exist for this single instance object");
}
if (request.unknownObjectInstanceId()) {
// create instance
LwM2mInstanceEnabler newInstance = createInstance(server, getObjectModel().multiple ? null : 0,
request.getResources());
// add new instance to this object
instances.put(newInstance.getId(), newInstance);
listenInstance(newInstance, newInstance.getId());
fireInstancesAdded(newInstance.getId());
return CreateResponse
.success(new LwM2mPath(request.getPath().getObjectId(), newInstance.getId()).toString());
} else {
List<LwM2mObjectInstance> instanceNodes = request.getObjectInstances();
// checks single object instances
if (!getObjectModel().multiple) {
if (request.getObjectInstances().size() > 1) {
return CreateResponse.badRequest("can not create several instances on this single instance object");
}
if (request.getObjectInstances().get(0).getId() != 0) {
return CreateResponse.badRequest("single instance object must use 0 as ID");
}
}
// ensure instance does not already exists
for (LwM2mObjectInstance instance : instanceNodes) {
if (instances.containsKey(instance.getId())) {
return CreateResponse.badRequest(String.format("instance %d already exists", instance.getId()));
}
}
// create the new instances
int[] instanceIds = new int[request.getObjectInstances().size()];
int i = 0;
for (LwM2mObjectInstance instance : request.getObjectInstances()) {
// create instance
LwM2mInstanceEnabler newInstance = createInstance(server, instance.getId(),
instance.getResources().values());
// add new instance to this object
instances.put(newInstance.getId(), newInstance);
listenInstance(newInstance, newInstance.getId());
// store instance ids
instanceIds[i] = newInstance.getId();
i++;
}
fireInstancesAdded(instanceIds);
return CreateResponse.success();
}
}
protected LwM2mInstanceEnabler createInstance(LwM2mServer server, Integer instanceId,
Collection<LwM2mResource> resources) {
// create the new instance
LwM2mInstanceEnabler newInstance = instanceFactory.create(getObjectModel(), instanceId, instances.keySet());
newInstance.setLwM2mClient(getLwm2mClient());
// add/write resource
for (LwM2mResource resource : resources) {
newInstance.write(server, true, resource.getId(), resource);
}
return newInstance;
}
@Override
protected ReadResponse doRead(LwM2mServer server, ReadRequest request) {
LwM2mPath path = request.getPath();
// Manage Object case
if (path.isObject()) {
List<LwM2mObjectInstance> lwM2mObjectInstances = new ArrayList<>();
for (LwM2mInstanceEnabler instance : instances.values()) {
ReadResponse response = instance.read(server);
if (response.isSuccess()) {
lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent());
}
}
return ReadResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances));
}
// Manage Instance case
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId());
if (instance == null)
return ReadResponse.notFound();
if (path.getResourceId() == null) {
return instance.read(server);
}
// Manage Resource case
if (path.getResourceInstanceId() == null) {
return instance.read(server, path.getResourceId());
}
// Manage Resource Instance case
return instance.read(server, path.getResourceId(), path.getResourceInstanceId());
}
@Override
protected BootstrapReadResponse doRead(LwM2mServer server, BootstrapReadRequest request) {
// Basic implementation we delegate to classic Read Request
ReadResponse response = doRead(server,
new ReadRequest(request.getContentFormat(), request.getPath(), request.getCoapRequest()));
return new BootstrapReadResponse(response.getCode(), response.getContent(), response.getErrorMessage());
}
@Override
protected ObserveResponse doObserve(final LwM2mServer server, final ObserveRequest request) {
final LwM2mPath path = request.getPath();
// Manage Object case
if (path.isObject()) {
List<LwM2mObjectInstance> lwM2mObjectInstances = new ArrayList<>();
for (LwM2mInstanceEnabler instance : instances.values()) {
ReadResponse response = instance.observe(server);
if (response.isSuccess()) {
lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent());
}
}
return ObserveResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances));
}
// Manage Instance case
final LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId());
if (instance == null)
return ObserveResponse.notFound();
if (path.getResourceId() == null) {
return instance.observe(server);
}
// Manage Resource case
if (path.getResourceInstanceId() == null) {
return instance.observe(server, path.getResourceId());
}
// Manage Resource Instance case
return instance.observe(server, path.getResourceId(), path.getResourceInstanceId());
}
@Override
protected WriteResponse doWrite(LwM2mServer server, WriteRequest request) {
LwM2mPath path = request.getPath();
// Manage Instance case
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId());
if (instance == null)
return WriteResponse.notFound();
if (path.isObjectInstance()) {
return instance.write(server, request.isReplaceRequest(), (LwM2mObjectInstance) request.getNode());
}
// Manage Resource case
if (path.getResourceInstanceId() == null) {
return instance.write(server, request.isReplaceRequest(), path.getResourceId(),
(LwM2mResource) request.getNode());
}
// Manage Resource Instance case
return instance.write(server, false, path.getResourceId(), path.getResourceInstanceId(),
((LwM2mResourceInstance) request.getNode()));
}
@Override
protected BootstrapWriteResponse doWrite(LwM2mServer server, BootstrapWriteRequest request) {
LwM2mPath path = request.getPath();
// Manage Object case
if (path.isObject()) {
for (LwM2mObjectInstance instanceNode : ((LwM2mObject) request.getNode()).getInstances().values()) {
LwM2mInstanceEnabler instanceEnabler = instances.get(instanceNode.getId());
if (instanceEnabler == null) {
doCreate(server, new CreateRequest(path.getObjectId(), instanceNode));
} else {
doWrite(server, new WriteRequest(Mode.REPLACE, path.getObjectId(), instanceEnabler.getId(),
instanceNode.getResources().values()));
}
}
return BootstrapWriteResponse.success();
}
// Manage Instance case
if (path.isObjectInstance()) {
LwM2mObjectInstance instanceNode = (LwM2mObjectInstance) request.getNode();
LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId());
if (instanceEnabler == null) {
doCreate(server, new CreateRequest(path.getObjectId(), instanceNode));
} else {
doWrite(server, new WriteRequest(Mode.REPLACE, request.getContentFormat(), path.getObjectId(),
path.getObjectInstanceId(), instanceNode.getResources().values()));
}
return BootstrapWriteResponse.success();
}
// Manage resource case
LwM2mResource resource = (LwM2mResource) request.getNode();
LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId());
if (instanceEnabler == null) {
doCreate(server, new CreateRequest(path.getObjectId(),
new LwM2mObjectInstance(path.getObjectInstanceId(), resource)));
} else {
instanceEnabler.write(server, true, path.getResourceId(), resource);
}
return BootstrapWriteResponse.success();
}
@Override
protected ExecuteResponse doExecute(LwM2mServer server, ExecuteRequest request) {
LwM2mPath path = request.getPath();
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId());
if (instance == null) {
return ExecuteResponse.notFound();
}
return instance.execute(server, path.getResourceId(), request.getArguments());
}
@Override
protected DeleteResponse doDelete(LwM2mServer server, DeleteRequest request) {
LwM2mInstanceEnabler deletedInstance = instances.remove(request.getPath().getObjectInstanceId());
if (deletedInstance != null) {
deletedInstance.onDelete(server);
fireInstancesRemoved(deletedInstance.getId());
return DeleteResponse.success();
}
return DeleteResponse.notFound();
}
@Override
public BootstrapDeleteResponse doDelete(LwM2mServer server, BootstrapDeleteRequest request) {
if (request.getPath().isRoot() || request.getPath().isObject()) {
if (id == LwM2mId.SECURITY) {
// For security object, we clean everything except bootstrap Server account.
// Get bootstrap account and store removed instances ids
Entry<Integer, LwM2mInstanceEnabler> bootstrapServerAccount = null;
int[] instanceIds = new int[instances.size()];
int i = 0;
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) {
if (ServersInfoExtractor.isBootstrapServer(instance.getValue())) {
bootstrapServerAccount = instance;
} else {
// Store instance ids
instanceIds[i] = instance.getKey();
i++;
}
}
// Clear everything
instances.clear();
// Put bootstrap account again
if (bootstrapServerAccount != null) {
instances.put(bootstrapServerAccount.getKey(), bootstrapServerAccount.getValue());
}
fireInstancesRemoved(instanceIds);
return BootstrapDeleteResponse.success();
} else if (id == LwM2mId.OSCORE) {
// For OSCORE object, we clean everything except OSCORE object link to bootstrap Server account.
// Get bootstrap account
LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance(
getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY));
// Get OSCORE instance ID associated to it
Integer bootstrapOscoreInstanceId = bootstrapInstance != null
? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance)
: null;
// if bootstrap server use OSCORE,
// search the OSCORE instance for this ID and store removed instances ids
if (bootstrapOscoreInstanceId != null) {
Entry<Integer, LwM2mInstanceEnabler> bootstrapServerOscore = null;
int[] instanceIds = new int[instances.size()];
int i = 0;
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) {
if (bootstrapOscoreInstanceId.equals(instance.getKey())) {
bootstrapServerOscore = instance;
} else {
// Store instance ids
instanceIds[i] = instance.getKey();
i++;
}
}
// Clear everything
instances.clear();
// Put bootstrap OSCORE instance again
if (bootstrapServerOscore != null) {
instances.put(bootstrapServerOscore.getKey(), bootstrapServerOscore.getValue());
}
fireInstancesRemoved(instanceIds);
return BootstrapDeleteResponse.success();
}
// else delete everything.
}
// In all other cases, just delete everything
instances.clear();
// fired instances removed
int[] instanceIds = new int[instances.size()];
int i = 0;
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) {
instanceIds[i] = instance.getKey();
i++;
}
fireInstancesRemoved(instanceIds);
return BootstrapDeleteResponse.success();
} else if (request.getPath().isObjectInstance()) {
if (id == LwM2mId.SECURITY) {
// For security object, deleting bootstrap Server account is not allowed
LwM2mInstanceEnabler instance = instances.get(request.getPath().getObjectInstanceId());
if (instance == null) {
return BootstrapDeleteResponse
.badRequest(String.format("Instance %s not found", request.getPath()));
} else if (ServersInfoExtractor.isBootstrapServer(instance)) {
return BootstrapDeleteResponse.badRequest("bootstrap server can not be deleted");
}
} else if (id == LwM2mId.OSCORE) {
// For OSCORE object, deleting instance linked to Bootstrap account is not allowed
// Get bootstrap instance
LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance(
getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY));
// Get OSCORE instance ID associated to it
Integer bootstrapOscoreInstanceId = bootstrapInstance != null
? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance)
: null;
if (bootstrapOscoreInstanceId != null
&& bootstrapOscoreInstanceId.equals(request.getPath().getObjectInstanceId())) {
return BootstrapDeleteResponse
.badRequest("OSCORE instance linked to bootstrap server can not be deleted");
}
}
if (null != instances.remove(request.getPath().getObjectInstanceId())) {
fireInstancesRemoved(request.getPath().getObjectInstanceId());
return BootstrapDeleteResponse.success();
} else {
return BootstrapDeleteResponse.badRequest(String.format("Instance %s not found", request.getPath()));
}
}
return BootstrapDeleteResponse.badRequest(String.format("unexcepted path %s", request.getPath()));
}
protected void listenInstance(LwM2mInstanceEnabler instance, final int instanceId) {
instance.addResourceListener(new ResourceListener() {
@Override
public void resourceChanged(LwM2mPath... paths) {
for (LwM2mPath path : paths) {
if (!isValid(instanceId, path)) {
LOG.warn("InstanceEnabler ({}) of object ({}) try to raise a change of {} which seems invalid.",
instanceId, getId(), path);
}
}
fireResourcesChanged(paths);
}
});
}
protected boolean isValid(int instanceId, LwM2mPath pathToValidate) {
if (!(pathToValidate.isResource() || pathToValidate.isResourceInstance()))
return false;
if (pathToValidate.getObjectId() != getId()) {
return false;
}
if (pathToValidate.getObjectInstanceId() != instanceId) {
return false;
}
return true;
}
@Override
public ContentFormat getDefaultEncodingFormat(DownlinkRequest<?> request) {
return defaultContentFormat;
}
@Override
public void init(LwM2mClient client, LinkFormatHelper linkFormatHelper) {
super.init(client, linkFormatHelper);
this.tbLinkFormatHelper = linkFormatHelper;
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) {
instanceEnabler.setLwM2mClient(client);
}
}
@Override
public void destroy() {
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) {
if (instanceEnabler instanceof Destroyable) {
((Destroyable) instanceEnabler).destroy();
} else if (instanceEnabler instanceof Stoppable) {
((Stoppable) instanceEnabler).stop();
}
}
}
@Override
public void start() {
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) {
if (instanceEnabler instanceof Startable) {
((Startable) instanceEnabler).start();
}
}
}
@Override
public void stop() {
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) {
if (instanceEnabler instanceof Stoppable) {
((Stoppable) instanceEnabler).stop();
}
}
}
@Override
public synchronized WriteAttributesResponse writeAttributes(LwM2mServer server, WriteAttributesRequest request) {
// execute is not supported for bootstrap
if (server.isLwm2mBootstrapServer()) {
return WriteAttributesResponse.methodNotAllowed();
}
// return WriteAttributesResponse.internalServerError("not implemented");
return doWriteAttributes(server, request);
}
/**
* <NOTIFICATION> Class Attributes
* - pmin (def = 0(sec)) Integer Resource/Object Instance/Object Readable Resource
* - pmax (def = -- ) Integer Resource/Object Instance/Object Readable Resource
* - Greater Than gt (def = -- ) Float Resource Numerical&Readable Resource
* - Less Than lt (def = -- ) Float Resource Numerical&Readable Resource
* - Step st (def = -- ) Float Resource Numerical&Readable Resource
*/
public WriteAttributesResponse doWriteAttributes(LwM2mServer server, WriteAttributesRequest request) {
LwM2mPath lwM2mPath = request.getPath();
LwM2mAttributeSet attributeSet = lwM2mAttributes.get(lwM2mPath);
Map<String, LwM2mAttribute<?>> attributes = new HashMap<>();
for (LwM2mAttribute attr : request.getAttributes().getLwM2mAttributes()) {
if (attr.getName().equals("pmax") || attr.getName().equals("pmin")) {
if (lwM2mPath.isObject() || lwM2mPath.isObjectInstance() || lwM2mPath.isResource()) {
attributes.put(attr.getName(), attr);
} else {
return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource/Object Instance/Object.");
}
} else if (attr.getName().equals("gt") || attr.getName().equals("lt") || attr.getName().equals("st")) {
if (lwM2mPath.isResource()) {
attributes.put(attr.getName(), attr);
} else {
return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource.");
}
}
}
if (attributes.size() > 0) {
if (attributeSet == null) {
attributeSet = new LwM2mAttributeSet(attributes.values());
} else {
Iterable<LwM2mAttribute<?>> lwM2mAttributeIterable = attributeSet.getLwM2mAttributes();
Map<String, LwM2mAttribute<?>> attributesOld = new HashMap<>();
for (LwM2mAttribute<?> attr : lwM2mAttributeIterable) {
attributesOld.put(attr.getName(), attr);
}
attributesOld.putAll(attributes);
attributeSet = new LwM2mAttributeSet(attributesOld.values());
}
lwM2mAttributes.put(lwM2mPath, attributeSet);
return WriteAttributesResponse.success();
}
return WriteAttributesResponse.internalServerError("not implemented");
}
@Override
public synchronized DiscoverResponse discover(LwM2mServer server, DiscoverRequest request) {
if (server.isLwm2mBootstrapServer()) {
// discover is not supported for bootstrap
return DiscoverResponse.methodNotAllowed();
}
if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) {
return DiscoverResponse.notFound();
}
return doDiscover(server, request);
}
protected DiscoverResponse doDiscover(LwM2mServer server, DiscoverRequest request) {
LwM2mPath path = request.getPath();
if (path.isObject()) {
LwM2mLink[] ObjectLinks = linkAddUpdateAttributes(this.tbLinkFormatHelper.getObjectDescription(server, this, null), server);
return DiscoverResponse.success(ObjectLinks);
} else if (path.isObjectInstance()) {
// Manage discover on instance
if (!getAvailableInstanceIds().contains(path.getObjectInstanceId()))
return DiscoverResponse.notFound();
LwM2mLink[] instanceLink = linkAddUpdateAttributes(this.tbLinkFormatHelper.getInstanceDescription(server, this, path.getObjectInstanceId(), null), server);
return DiscoverResponse.success(instanceLink);
} else if (path.isResource()) {
// Manage discover on resource
if (!getAvailableInstanceIds().contains(path.getObjectInstanceId()))
return DiscoverResponse.notFound();
ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId());
if (resourceModel == null)
return DiscoverResponse.notFound();
if (!getAvailableResourceIds(path.getObjectInstanceId()).contains(path.getResourceId()))
return DiscoverResponse.notFound();
LwM2mLink[] resourceLink = linkAddUpdateAttributes(this.tbLinkFormatHelper.getResourceDescription(server,
this, path.getObjectInstanceId(), path.getResourceId(), null), server);
return DiscoverResponse.success(resourceLink);
}
return DiscoverResponse.badRequest(null);
}
private LwM2mLink[] linkAddUpdateAttributes(LwM2mLink[] links, LwM2mServer server) {
ArrayList<LwM2mLink> resourceLinkList = new ArrayList<>();
for (LwM2mLink link : links) {
LwM2mAttributeSet lwM2mAttributeSetDop = null;
if (this.lwM2mAttributes.get(link.getPath()) != null) {
lwM2mAttributeSetDop = this.lwM2mAttributes.get(link.getPath());
}
LwM2mAttribute resourceAttributeDim = getResourceAttributes(server, link.getPath());
Map<String, LwM2mAttribute<?>> attributes = new HashMap<>();
if (link.getAttributes() != null) {
for (LwM2mAttribute attr : link.getAttributes().getLwM2mAttributes()) {
attributes.put(attr.getName(), attr);
}
}
if (lwM2mAttributeSetDop != null) {
for (LwM2mAttribute attr : lwM2mAttributeSetDop.getLwM2mAttributes()) {
attributes.put(attr.getName(), attr);
}
}
if (resourceAttributeDim != null) {
attributes.put(resourceAttributeDim.getName(), resourceAttributeDim);
}
resourceLinkList.add(new LwM2mLink(link.getRootPath(), link.getPath(), attributes.values()));
}
return resourceLinkList.toArray(LwM2mLink[]::new);
}
protected LwM2mAttribute getResourceAttributes(LwM2mServer server, LwM2mPath path) {
ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId());
if (path.isResource() && resourceModel.multiple) {
return getResourceAttributeDim(path, server);
}
return null;
}
protected LwM2mAttribute getResourceAttributeDim(LwM2mPath path, LwM2mServer server) {
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId());
try {
ReadResponse readResponse = instance.read(server, path.getResourceId());
if (readResponse.getCode().getCode() == 205 && readResponse.getContent() instanceof LwM2mMultipleResource) {
long valueDim = ((LwM2mMultipleResource) readResponse.getContent()).getInstances().size();
return LwM2mAttributes.create(LwM2mAttributes.DIMENSION, valueDim);
} else {
return null;
}
} catch (Exception e) {
return null;
}
}
}

71
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java

@ -1,71 +0,0 @@
/**
* Copyright © 2016-2024 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.client;
import org.eclipse.leshan.client.resource.BaseInstanceEnablerFactory;
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
import org.eclipse.leshan.client.resource.ObjectsInitializer;
import org.eclipse.leshan.core.model.LwM2mModel;
import org.eclipse.leshan.core.model.ObjectModel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TbObjectsInitializer extends ObjectsInitializer {
public TbObjectsInitializer(LwM2mModel model) {
super(model);
}
public List<LwM2mObjectEnabler> create(int... objectId) {
List<LwM2mObjectEnabler> enablers = new ArrayList<>();
for (int anObjectId : objectId) {
LwM2mObjectEnabler objectEnabler = create(anObjectId);
if (objectEnabler != null)
enablers.add(objectEnabler);
}
return enablers;
}
public LwM2mObjectEnabler create(int objectId) {
ObjectModel objectModel = model.getObjectModel(objectId);
if (objectModel == null) {
throw new IllegalArgumentException(
"Cannot create object for id " + objectId + " because no model is defined for this id.");
}
return createNodeEnabler(objectModel);
}
protected LwM2mObjectEnabler createNodeEnabler(ObjectModel objectModel) {
Map<Integer, LwM2mInstanceEnabler> instances = new HashMap<>();
LwM2mInstanceEnabler[] newInstances = createInstances(objectModel);
for (LwM2mInstanceEnabler instance : newInstances) {
// set id if not already set
if (instance.getId() == null) {
int id = BaseInstanceEnablerFactory.generateNewInstanceId(instances.keySet());
instance.setId(id);
}
instance.setModel(objectModel);
instances.put(instance.getId(), instance);
}
return new TbLwm2mObjectEnabler(objectModel.id, objectModel, instances, getFactoryFor(objectModel),
getContentFormat(objectModel.id));
}
}

3
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java

@ -110,9 +110,6 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
@Before
public void startInitRPC() throws Exception {
if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationDiscoverWriteAttributesTest")){
isWriteAttribute = true;
}
if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationWriteCborTest")){
supportFormatOnly_SenMLJSON_SenMLCBOR = true;
}

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

@ -39,6 +39,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
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.Lwm2mTestHelper.RESOURCE_ID_6;
public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest {
@ -171,6 +172,17 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText());
}
@Test
public void testDiscoverRequestCannotTargetResourceInstance_Return_INTERNAL_SERVER_ERROR() throws Exception {
// ResourceInstanceId
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6 + "/1";
String actualResult = sendDiscover(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText());
String expected = "InvalidRequestException: Discover request cannot target resource instance path: /3/0/6/1";
assertTrue(rpcActualResult.get("error").asText().contains(expected));
}
private String sendDiscover(String path) throws Exception {
String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());

175
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java

@ -19,12 +19,12 @@ 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.common.transport.util.JsonUtils;
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_6;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_7;
@ -32,69 +32,22 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID
public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest {
/**
* WriteAttributes {"id":"/3_1.2/0/6","attributes":{"pmax":100, "pmin":10}}
* if not implemented:
* {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"}
* if implemented:
* {"result":"BAD_REQUEST","error":"Attribute pmax can be used for only Resource/Object Instance/Object."}
* <PROPERTIES> Class Attributes
* - dim (0-65535) Integer: Multiple-Instance Resource; R, Number of instances existing for a Multiple-Instance Resource
* <NOTIFICATION> Class Attributes
* - pmin (def = 0(sec)) Integer: Object; Object Instance; Resource; Resource Instance; RW, Readable Resource
* - pmax (def = -- ) Integer: Object; Object Instance; Resource; Resource Instance; RW, Readable Resource
* - Greater Than gt (def = -- ) Float: Resource; Resource Instance; RW, Numerical&Readable Resource
* - Less Than lt (def = -- ) Float: Resource; Resource Instance; RW, Numerical&Readable Resource
* - Step st (def = -- ) Float: Resource; Resource Instance; RW, Numerical&Readable Resource
*/
@Test
public void testWriteAttributesResourceWithParametersByResourceInstanceId_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6 + "/1";
String expectedValue = "{\"pmax\":100, \"pmin\":10}";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Attribute pmax can be used for only Resource/Object Instance/Object.";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
/**
* WriteAttributes {"id":"/3_1.2/0/6","attributes":{"pmax":100, "pmin":10}}
* if not implemented:
* {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"}
* if implemented:
* {"result":"BAD_REQUEST","error":"Attribute pmax can be used for only Resource/Object Instance/Object."}
*/
@Test
public void testWriteAttributeResourceDimWithParametersByResourceId_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6;
String expectedValue = "{\"dim\":3}";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Attribute dim is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request.";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
@Test
public void testWriteAttributesResourceVerWithParametersById_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectIdVer_3;
String expectedValue = "{\"ver\":1.3}";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Attribute ver is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request.";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
@Test
public void testWriteAttributesResourceServerUriWithParametersById_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectInstanceIdVer_1;
String actualResult = sendRPCReadById(expectedPath);
String expectedValue = "{\"uri\":\"coaps://localhost:5690\"}";
actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Attribute uri is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request.";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
/**
* <PROPERTIES> Class Attributes
* Object Version ver Object
* Provide the version of the associated Object.
* "ver" only for objectId
* <PROPERTIES> Class Attributes
* Dimension dim Integer [0:255]
* Number of instances existing for a Multiple-Instance Resource
@ -106,46 +59,34 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL
* <Type>Integer</Type>
* <RangeEnumeration>0..7</RangeEnumeration>
* WriteAttributes implemented: Discover {"id":"3/0/6"} -> 'dim' = 3
* "ver" only for objectId
*/
@Test
public void testReadDIM_3_0_6_Only_R() throws Exception {
String path = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6;
String actualResult = sendDiscover(path);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected = "</3/0/6>;dim=3";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
}
/**
* <PROPERTIES> Class Attributes
* Object Version ver Object
* Provide the version of the associated Object.
* "ver" only for objectId
*/
@Test
public void testReadVer() throws Exception {
String path = objectIdVer_3;
String actualResult = sendDiscover(path);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected = "</3>;ver=1.2";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
expected = "</3/0/6>;dim=3";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
expected = "</3/0/7>;dim=3";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
expected = "</3/0/8>;dim=3";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
expected = "</3/0/11>;dim=1";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
}
/**
* WriteAttributes {"id":"/3/0/14","attributes":{"pmax":100, "pmin":10}}
* if not implemented:
* {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"}
* if implemented:
* {"result":"CHANGED"}
* result changed:
*
*/
@Test
public void testWriteAttributesResourceWithParametersById_Result_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6;
String expectedValue = "{\"pmax\":100, \"pmin\":10}";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
@ -154,41 +95,33 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL
actualResult = sendDiscover(expectedPath);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected = "</3/0/14>;pmax=100;pmin=10";
String expected = "</3/0/6>;pmax=100;pmin=10;dim=3";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
}
/**
* <NOTIFICATION> Class Attributes
* Minimum/Maximum Period pmin/pmax
* Notes: The Minimum Period Attribute:
* -- indicates the minimum time in seconds the LwM2M Client MUST wait between two notifications. If a notification of an observed Resource is supposed to be generated but it is before pmin expiry, notification MUST be sent as soon as pmin expires. In the absence of this parameter, the Minimum Period is defined by the Default Minimum Period set in the LwM2M Server Account.
* Notes: The Maximum Period Attribute:
* -- indicates the maximum time in seconds the LwM2M Client MAY wait between two notifications. When this "Maximum Period" expires after the last notification, a new notification MUST be sent. In the absence of this parameter, the "Maximum Period" is defined by the Default Maximum Period when set in the LwM2M Server Account or considered as 0 otherwise. The value of 0, means pmax MUST be ignored. The maximum period parameter MUST be greater than the minimum period parameter otherwise pmax will be ignored for the Resource to which such inconsistent timing conditions are applied.
* Greater Than gt Resource
* Less Than lt Resource
* Step st Resource
*
* Object Id = 1
* Default Minimum Period Id = 2 300 or 0
* Default Maximum Period Id = 3 6000 or "-"
* </3/0>;pmax=65, </3/0/1>, <3/0/2>, </3/0/3>, </3/0/4>,
* <3/0/6>;dim=8,<3/0/7>;gt=50;lt=42.2;st=0.5,<3/0/8>;...
*/
@Test
public void testWriteAttributesPeriodLtGt() throws Exception {
public void testWriteAttributesResourceVerWithParametersById_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectIdVer_3;
String expectedValue = "{\"ver\":1.3}";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Attribute ver is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request.";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
@Test
public void testWriteAttributesObjectInstanceResourcePeriodLtGt_Return_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_3;
String expectedValue = "{\"pmax\":60}";
String expectedValue = "{\"pmax\":65, \"pmin\":5}";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
expectedPath = objectInstanceIdVer_3;
expectedValue = "{\"pmax\":65}";
actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_7;
expectedValue = "{\"gt\":50.0, \"lt\":42.2, \"st\":0.5}";
String expectedValueStr = "gt=50;lt=42.2;st=0.5";
JsonUtils.parse("{" + expectedValueStr + "}").toString();
expectedValue = JsonUtils.parse("{" + expectedValueStr + "}").toString();
actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
@ -198,11 +131,11 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValue = rpcActualResult.get("value").asText();
String expected = "</3>;ver=1.2,</3/0>;pmax=65";
String expected = "</3>;ver=1.2,</3/0>;pmax=65;pmin=5";
assertTrue(actualValue.contains(expected));
expected = "</3/0/6>;dim=3";
assertTrue(actualValue.contains(expected));
expected = "</3/0/7>;st=0.5;lt=42.2;gt=50";
expected = "</3/0/7>;" + expectedValueStr + ";dim=3";
assertTrue(actualValue.contains(expected));
// ObjectInstanceId
expectedPath = objectInstanceIdVer_3;
@ -210,30 +143,23 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
actualValue = rpcActualResult.get("value").asText();
expected = "</3/0>;pmax=65";
expected = "</3/0>;pmax=65;pmin=5";
assertTrue(actualValue.contains(expected));
expected = "</3/0/6>;dim=3,</3/0/7>;st=0.5;lt=42.2;gt=50";
expected = "</3/0/7>;" + expectedValueStr + ";dim=3";
assertTrue(actualValue.contains(expected));
// ResourceId
expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6;
actualResult = sendDiscover(expectedPath);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
expected = "</3/0/6>;dim=3";
expected = "</3/0/6>;dim=3,</3/0/6/0>,</3/0/6/1>,</3/0/6/2>";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_7;
actualResult = sendDiscover(expectedPath);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
expected = "</3/0/7>;st=0.5;lt=42.2;gt=50";
expected = "</3/0/7>;" + expectedValueStr;
assertTrue(rpcActualResult.get("value").asText().contains(expected));
// ResourceInstanceId
expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6 + "/1";
actualResult = sendDiscover(expectedPath);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText());
expected = "InvalidRequestException: Discover request cannot target resource instance path: /3/0/6/1";
assertTrue(rpcActualResult.get("error").asText().contains(expected));
}
private String sendRPCExecuteWithValueById(String path, String value) throws Exception {
@ -241,11 +167,6 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendRPCReadById(String path) throws Exception {
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendDiscover(String path) throws Exception {
String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());

Loading…
Cancel
Save