Browse Source

lwm2m_fix_bug_Negative_Integer_values

pull/15962/head
nickAS21 1 month ago
parent
commit
cb99a36adb
  1. 5
      application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java
  2. 3
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  3. 404
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTestObject.java
  4. 5
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java
  5. 74
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java
  6. 332
      application/src/test/resources/lwm2m/3442.xml
  7. 14
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java

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

@ -17,11 +17,12 @@ package org.thingsboard.server.transport.lwm2m;
public class Lwm2mTestHelper {
public static final String[] lwm2mClientResources = new String[]{"3-1_2.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"};
public static final String[] lwm2mClientResources = new String[]{"3-1_2.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml", "3442.xml"};
// Models
public static final int BINARY_APP_DATA_CONTAINER = 19;
public static final int TEMPERATURE_SENSOR = 3303;
public static final int OBJECT_ID_LWM2M_TEST_OBJECT = 3442;
// Ids in Client
public static final int OBJECT_INSTANCE_ID_0 = 0;
@ -41,6 +42,8 @@ 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_ID_120 = 120; // INTEGER_VALUE
public static final int RESOURCE_ID_125 = 125; // UNSIGNED_INTEGER_VALUE
public static final int RESOURCE_ID_5700 = 5700;
public static final int RESOURCE_INSTANCE_ID_0 = 0;
public static final int RESOURCE_INSTANCE_ID_2 = 2;

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

@ -118,6 +118,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INST
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_LWM2M_TEST_OBJECT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.lwm2mClientResources;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.setDtlsConnectorConfigCidLength;
import static org.thingsboard.server.utils.PortFinder.isUDPPortAvailable;
@ -192,7 +193,7 @@ public class LwM2MTestClient {
LwM2mTemperatureSensor lwM2mTemperatureSensor0 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_0);
lwM2MTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12);
initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2mTemperatureSensor0, lwM2MTemperatureSensor12);
initializer.setInstancesForObject(OBJECT_ID_LWM2M_TEST_OBJECT, new LwM2mTestObject());
List<LwM2mObjectEnabler> enablers = initializer.createAll();
// Create Californium Endpoints Provider:

404
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTestObject.java

@ -0,0 +1,404 @@
/*******************************************************************************
* Copyright (c) 2021 Sierra Wireless and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Sierra Wireless - initial API and implementation
*******************************************************************************/
package org.thingsboard.server.transport.lwm2m.client;
import org.eclipse.leshan.client.resource.SimpleInstanceEnabler;
import org.eclipse.leshan.client.servers.LwM2mServer;
import org.eclipse.leshan.core.link.Link;
import org.eclipse.leshan.core.link.attributes.Attribute;
import org.eclipse.leshan.core.link.attributes.QuotedStringAttribute;
import org.eclipse.leshan.core.link.attributes.ResourceTypeAttribute;
import org.eclipse.leshan.core.link.attributes.UnquotedStringAttribute;
import org.eclipse.leshan.core.link.lwm2m.LwM2mLink;
import org.eclipse.leshan.core.link.lwm2m.MixedLwM2mLink;
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.node.ObjectLink;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.request.argument.Argument;
import org.eclipse.leshan.core.request.argument.Arguments;
import org.eclipse.leshan.core.response.ErrorCallback;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.ResponseCallback;
import org.eclipse.leshan.core.response.SendResponse;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.core.util.RandomStringUtils;
import org.eclipse.leshan.core.util.datatype.ULong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* This aims to implements LWM2M Test Object(Id:3442) from LWM2M registry.
*/
public class LwM2mTestObject extends SimpleInstanceEnabler {
private static final Logger LOG = LoggerFactory.getLogger(LwM2mTestObject.class);
public static final String INITIAL_STRING_VALUE = "initial value";
public static final Long INITIAL_INTEGER_VALUE = 1024l;
public static final ULong INITIAL_UNSIGNED_INTEGER_VALUE = ULong.valueOf("9223372036854775808");
public static final Double INITIAL_FLOAT_VALUE = 3.14159d;
public static final Boolean INITIAL_BOOLEAN_VALUE = true;
public static final byte[] INITIAL_OPAQUE_VALUE = Hex.decodeHex("0123456789ABCDEF".toCharArray());
public static final Date INITIAL_TIME_VALUE = new Date(946684800000l);
public static final ObjectLink INITIAL_OBJLINK_VALUE = new ObjectLink(3, 0);
public static final Link[] INITIAL_CORELINK_VALUE = new Link[] { new LwM2mLink(null, new LwM2mPath(3442)) };
private final Random random = new Random(System.currentTimeMillis());
public LwM2mTestObject() {
super();
initialValues = new HashMap<>();
initialValues.put(4, Collections.EMPTY_MAP);
initialValues.put(6, Collections.EMPTY_MAP);
// single
initialValues.put(110, INITIAL_STRING_VALUE);
initialValues.put(120, INITIAL_INTEGER_VALUE);
initialValues.put(125, INITIAL_UNSIGNED_INTEGER_VALUE);
initialValues.put(130, INITIAL_FLOAT_VALUE);
initialValues.put(140, INITIAL_BOOLEAN_VALUE);
initialValues.put(150, INITIAL_OPAQUE_VALUE);
initialValues.put(160, INITIAL_TIME_VALUE);
initialValues.put(170, INITIAL_OBJLINK_VALUE);
initialValues.put(180, INITIAL_CORELINK_VALUE);
// multi
initialValues.put(1110, LwM2mResourceInstance.newStringInstance(0, INITIAL_STRING_VALUE));
initialValues.put(1120, LwM2mResourceInstance.newIntegerInstance(0, INITIAL_INTEGER_VALUE));
initialValues.put(1125, LwM2mResourceInstance.newUnsignedIntegerInstance(0, INITIAL_UNSIGNED_INTEGER_VALUE));
initialValues.put(1130, LwM2mResourceInstance.newFloatInstance(0, INITIAL_FLOAT_VALUE));
initialValues.put(1140, LwM2mResourceInstance.newBooleanInstance(0, INITIAL_BOOLEAN_VALUE));
initialValues.put(1150, LwM2mResourceInstance.newBinaryInstance(0, INITIAL_OPAQUE_VALUE));
initialValues.put(1160, LwM2mResourceInstance.newDateInstance(0, INITIAL_TIME_VALUE));
initialValues.put(1170, LwM2mResourceInstance.newObjectLinkInstance(0, INITIAL_OBJLINK_VALUE));
initialValues.put(1180, LwM2mResourceInstance.newCoreLinkInstance(0, INITIAL_CORELINK_VALUE));
}
private void clearValues() {
Map<Integer, Object> clearedValues = new HashMap<>();
clearedValues.put(4, Collections.EMPTY_MAP);
clearedValues.put(6, Collections.EMPTY_MAP);
// single
clearedValues.put(110, "");
clearedValues.put(120, 0l);
clearedValues.put(125, ULong.valueOf(0l));
clearedValues.put(130, 0.0d);
clearedValues.put(140, false);
clearedValues.put(150, new byte[0]);
clearedValues.put(160, new Date(0l));
clearedValues.put(170, new ObjectLink());
clearedValues.put(180, new Link[0]);
// multi
clearedValues.put(1110, Collections.EMPTY_MAP);
clearedValues.put(1120, Collections.EMPTY_MAP);
clearedValues.put(1125, Collections.EMPTY_MAP);
clearedValues.put(1130, Collections.EMPTY_MAP);
clearedValues.put(1140, Collections.EMPTY_MAP);
clearedValues.put(1150, Collections.EMPTY_MAP);
clearedValues.put(1160, Collections.EMPTY_MAP);
clearedValues.put(1170, Collections.EMPTY_MAP);
clearedValues.put(1180, Collections.EMPTY_MAP);
fireResourcesChange(applyValues(clearedValues));
}
private void resetValues() {
fireResourcesChange(applyValues(initialValues));
}
private void randomValues() {
Map<Integer, Object> randomValues = new HashMap<>();
randomValues.put(4, generateResourceInstances(new StringGenerator()));
// single
randomValues.put(110, new StringGenerator().generate());
randomValues.put(120, new LongGenerator().generate());
randomValues.put(125, new ULongGenerator().generate());
randomValues.put(130, new DoubleGenerator().generate());
randomValues.put(140, new BooleanGenerator().generate());
randomValues.put(150, new BytesGenerator().generate());
randomValues.put(160, new DateGenerator().generate());
randomValues.put(170, new ObjectLinkGenerator().generate());
randomValues.put(180, new CoreLinkGenerator().generate());
// multi
randomValues.put(1110, generateResourceInstances(new StringGenerator()));
randomValues.put(1120, generateResourceInstances(new LongGenerator()));
randomValues.put(1125, generateResourceInstances(new ULongGenerator()));
randomValues.put(1130, generateResourceInstances(new DoubleGenerator()));
randomValues.put(1140, generateResourceInstances(new BooleanGenerator()));
randomValues.put(1150, generateResourceInstances(new BytesGenerator()));
randomValues.put(1160, generateResourceInstances(new DateGenerator()));
randomValues.put(1170, generateResourceInstances(new ObjectLinkGenerator()));
randomValues.put(1180, generateResourceInstances(new CoreLinkGenerator()));
fireResourcesChange(applyValues(randomValues));
}
private void storeArguments(Arguments arguments) {
// convert Arguments to Map
Map<Integer, String> argValues = new LinkedHashMap<Integer, String>();
for (Argument argument : arguments) {
String value = argument.getValue() == null ? "" : argument.getValue();
argValues.put(argument.getDigit(), value);
}
// put value in resource (4)
Map<Integer, Object> newParams = new HashMap<>();
newParams.put(4, argValues);
fireResourcesChange(applyValues(newParams));
}
private ExecuteResponse sendData(LwM2mServer server, Arguments arguments) {
// get path of resources to send
List<String> paths = new ArrayList<>();
LwM2mResource lwM2mResource = resources.get(6);
if (lwM2mResource != null && lwM2mResource.isMultiInstances()) {
for (LwM2mResourceInstance instance : lwM2mResource.getInstances().values()) {
try {
paths.add(new LwM2mPath(instance.getValue().toString()).toString());
} catch (IllegalArgumentException e) {
LOG.warn("Instance {} of resource 6 contains invalid path", instance.getId());
}
}
}
if (paths.isEmpty()) {
LOG.info("Unable to Send Data to {} as resources 6 {} doesn't contain any valid LWM2M path", server,
lwM2mResource);
return ExecuteResponse.internalServerError("resources 6 doesn't contain any valid LWM2M path");
}
ContentFormat format = ContentFormat.SENML_CBOR;
Argument contenFormatArg = arguments.get(0);
try {
if (contenFormatArg != null)
format = ContentFormat.fromCode(contenFormatArg.getValue());
} catch (NumberFormatException e) {
return ExecuteResponse.badRequest(
String.format("argument 1 %s is not a valid ContentFormat.", contenFormatArg.getValue()));
}
long timeout = 3000;// ms
Argument timeoutArg = arguments.get(1);
try {
if (timeoutArg != null)
timeout = Long.parseLong(timeoutArg.getValue());
} catch (NumberFormatException e) {
return ExecuteResponse
.badRequest(String.format("argument 0 %s is not a valid timeout.", timeoutArg.getValue()));
}
// send data;
LOG.info("Try to send data {} using {} to {} (timeout {}ms)...", paths, format, server, timeout);
getLwM2mClient().getSendService().sendData(server, format, paths, timeout,
new ResponseCallback<SendResponse>() {
@Override
public void onResponse(SendResponse response) {
if (response.isSuccess()) {
LOG.info("... Send Succeed {}", response.getCode());
} else {
LOG.warn("... Send Response : {} {}", response.getCode(), response.getErrorMessage());
}
}
}, new ErrorCallback() {
@Override
public void onError(Exception e) {
LOG.error("... Send failed.", e);
}
});
return ExecuteResponse.success();
}
private Map<Integer, ?> generateResourceInstances(ValueGenerator<?> generator) {
HashMap<Integer, Object> instances = new HashMap<>();
for (int i = 0; i < random.nextInt(10); i++) {
instances.put(i, generator.generate());
}
return instances;
}
@Override
public ExecuteResponse execute(LwM2mServer server, int resourceid, Arguments arguments) {
switch (resourceid) {
case 0:
resetValues();
return ExecuteResponse.success();
case 1:
randomValues();
return ExecuteResponse.success();
case 2:
clearValues();
return ExecuteResponse.success();
case 3:
storeArguments(arguments);
return ExecuteResponse.success();
case 5:
return sendData(server, arguments);
default:
return super.execute(server, resourceid, arguments);
}
}
/* ***************** Random Generator ****************** */
static interface ValueGenerator<T> {
T generate();
}
class StringGenerator implements ValueGenerator<String> {
@Override
public String generate() {
return RandomStringUtils.randomAlphanumeric(random.nextInt(20));
}
}
class LongGenerator implements ValueGenerator<Long> {
@Override
public Long generate() {
return random.nextLong();
}
}
class ULongGenerator implements ValueGenerator<ULong> {
@Override
public ULong generate() {
return ULong.valueOf(random.nextLong());
}
}
class DoubleGenerator implements ValueGenerator<Double> {
@Override
public Double generate() {
return random.nextDouble();
}
}
class BooleanGenerator implements ValueGenerator<Boolean> {
@Override
public Boolean generate() {
return random.nextInt(2) == 1;
}
}
class BytesGenerator implements ValueGenerator<byte[]> {
@Override
public byte[] generate() {
byte[] bytes = new byte[random.nextInt(20)];
random.nextBytes(bytes);
return bytes;
}
}
class DateGenerator implements ValueGenerator<Date> {
@Override
public Date generate() {
// try to generate random date which is not so out of date.
long rd = System.currentTimeMillis();
// remove year randomly
rd -= (random.nextInt(20) - 10) * 31557600000l;
// add some variance in the year
rd += random.nextInt(3155760) * 1000l;
return new Date(rd);
}
}
class ObjectLinkGenerator implements ValueGenerator<ObjectLink> {
@Override
public ObjectLink generate() {
return new ObjectLink(random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1));
}
}
class CoreLinkGenerator implements ValueGenerator<Link[]> {
private LwM2mPath generatePath() {
int dice4Value = random.nextInt(4);
switch (dice4Value) {
case 0:
return new LwM2mPath(random.nextInt(ObjectLink.MAXID - 1));
case 1:
return new LwM2mPath(random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1));
case 2:
return new LwM2mPath(random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1),
random.nextInt(ObjectLink.MAXID - 1));
case 3:
return new LwM2mPath(random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1),
random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1));
}
return null; // should not happened
}
private Attribute[] generateAttributes() {
int nbAttributes = random.nextInt(3);
Map<String, Attribute> attributes = new HashMap<>(nbAttributes);
for (int i = 0; i < nbAttributes; i++) {
int dice2value = random.nextInt(2);
Attribute attr = null;
switch (dice2value) {
case 0:
attr = new QuotedStringAttribute(RandomStringUtils.randomAlphabetic(random.nextInt(5) + 1),
RandomStringUtils.randomAlphanumeric(random.nextInt(5) + 1));
break;
case 1:
attr = new UnquotedStringAttribute(RandomStringUtils.randomAlphabetic(random.nextInt(5) + 1),
RandomStringUtils.randomAlphanumeric(random.nextInt(5) + 1));
break;
}
attributes.put(attr.getName(), attr);
}
return attributes.values().toArray(new Attribute[attributes.size()]);
}
@Override
public Link[] generate() {
// define if root path is used or not
String rootpath = random.nextInt(4) == 0 ? "/" + RandomStringUtils.randomAlphabetic(1)
+ RandomStringUtils.randomAlphanumeric(random.nextInt(4)) : null;
// define number of link
int nbLink = random.nextInt(10);
// create links
Link[] links = new Link[nbLink];
for (int i = 0; i < links.length; i++) {
// when there is a rootpath first link has oma attribute
if (rootpath != null && i == 0) {
links[i] = new MixedLwM2mLink(rootpath, LwM2mPath.ROOTPATH, new ResourceTypeAttribute("oma.lwm2m"));
} else {
// generate random link with random path and attributes
links[i] = new MixedLwM2mLink(rootpath, generatePath(), generateAttributes());
}
}
return links;
}
}
}

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

@ -53,6 +53,7 @@ import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_LWM2M_TEST_OBJECT;
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.OBJECT_INSTANCE_ID_12;
@ -94,6 +95,8 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
protected String objectIdVer_19;
protected final String OBJECT_ID_VER_50 = "/50";
protected String objectIdVer_3303;
protected String objectIdVer_3442;
protected String objectInstanceIdVer_3442;
protected static AtomicInteger endpointSequence = new AtomicInteger();
protected static String DEVICE_ENDPOINT_RPC_PREF = "deviceEndpointRpc";
@ -152,10 +155,12 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
objectIdVer_3 = (String) expectedObjectIdVers.stream().filter(PREDICATE_3).findFirst().get();
objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get();
objectIdVer_3303 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + TEMPERATURE_SENSOR)).findFirst().get();
objectIdVer_3442 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + OBJECT_ID_LWM2M_TEST_OBJECT)).findFirst().get();
objectInstanceIdVer_1 = (String) expectedObjectIdVerInstances.stream().filter(path -> (!((String) path).startsWith("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).startsWith("/" + SERVER))).findFirst().get();
objectInstanceIdVer_3 = (String) expectedObjectIdVerInstances.stream().filter(PREDICATE_3).findFirst().get();
objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + FIRMWARE)).findFirst().get();
objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + SOFTWARE_MANAGEMENT)).findFirst().get();
objectInstanceIdVer_3442 = objectIdVer_3442 + "/" + OBJECT_INSTANCE_ID_0;
idVer_3_0_0 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0;
idVer_3_0_9 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9;

74
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java

@ -20,12 +20,15 @@ import com.google.common.hash.Hashing;
import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.util.datatype.ULong;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.tbel.TbUtils;
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import java.util.Base64;
import static org.eclipse.leshan.core.model.ResourceModel.Type.UNSIGNED_INTEGER;
import static org.eclipse.leshan.core.model.ResourceModel.Type.INTEGER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@ -55,6 +58,65 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes
assertTrue(actualValues.contains(expected));
}
/**
* update SingleResource:
* WriteReplace {"id":"3442/0/120","value":-1}
* WriteReplace {"id":"3442/0/120","value":-100}
* WriteReplace {"id":"3442/0/120","value":-2147483648} -> Integer.MIN_VALUE
* WriteReplace {"id":"3442/0/120","value":2147483647} -> Integer.MAX_VALUE
* {"result":"CHANGED"}
*/
@Test
public void testWriteReplaceNegativeIntegerValuesSingleResourceById_Result_CHANGED() throws Exception {
Integer expectedValue = -1;
sendRPC_3442_ResourceId_120(expectedValue);
expectedValue = -100;
sendRPC_3442_ResourceId_120(expectedValue);
expectedValue = Integer.MIN_VALUE;
sendRPC_3442_ResourceId_120(expectedValue);
expectedValue = Integer.MAX_VALUE;
sendRPC_3442_ResourceId_120(expectedValue);
}
/**
* update SingleResource:
* WriteReplace {"id":"3442/0/120","value":"18446744073709551615} // ULong.MAX
* {"result":"CHANGED"}
*/
@Test
public void testWriteReplaceUnIntegerValuesSingleResourceById_Result_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_3442 + "/" + RESOURCE_ID_125;
ULong expectedValue = ULong.MAX;
String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
actualResult = sendRPCReadById(expectedPath);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String actualValues = rpcActualResult.get("value").asText();
String expected = "LwM2mSingleResource [id=" + RESOURCE_ID_125 + ", value=" + expectedValue + ", type=" + UNSIGNED_INTEGER.name() + "]";
assertTrue(actualValues.contains(expected));
}
/**
* update SingleResource:
* WriteReplace {"id":"3442/0/120","value":"-1"}
* {"result":"BAD_REQUEST"}
*/
@Test
public void testWriteReplaceUnIntegerValuesSingleResourceById_Result__BAD_REQUEST() throws Exception {
String expectedPath = objectInstanceIdVer_3442 + "/" + RESOURCE_ID_125;
Integer expectedValue = -1;
String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Format value is bad. Value for this Single Resource must be UNSIGNED_INTEGER!";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.contains(expected));
}
/**
* key
* WriteReplace {"key":"timezone","value":"+10"}
@ -572,4 +634,16 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes
String setRpcRequest = "{\"method\": \"Create\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}";
return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk());
}
private void sendRPC_3442_ResourceId_120 (Integer expectedValue) throws Exception {
String expectedPath = objectInstanceIdVer_3442 + "/" + RESOURCE_ID_120;
String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
actualResult = sendRPCReadById(expectedPath);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String actualValues = rpcActualResult.get("value").asText();
String expected = "LwM2mSingleResource [id=" + RESOURCE_ID_120 + ", value=" + expectedValue + ", type=" + INTEGER.name() + "]";
assertTrue(actualValues.contains(expected));
}
}

332
application/src/test/resources/lwm2m/3442.xml

@ -0,0 +1,332 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- BSD-3 Clause License
Copyright 2021 Eclipse Foundation.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd">
<Object ObjectType="MODefinition">
<Name>LwM2M v1.1 Test Object</Name>
<Description1><![CDATA[This object aims to make easier to do interoperability tests about LWM2M v1.1. It contains resources for each available datatype.]]></Description1>
<ObjectID>3442</ObjectID>
<ObjectURN>urn:oma:lwm2m:ext:3442</ObjectURN>
<LWM2MVersion>1.1</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="0">
<Name>Reset values</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration />
<Units />
<Description>Reset all resources of this object with their initial value.</Description>
</Item>
<Item ID="1">
<Name>Randomize values</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration />
<Units />
<Description>
<![CDATA[Set random value to all resources. For multi-instance resources, the number of resource instances is also randomized.
Randomization should avoid to generate too big payload. We advice to limit value to something like :
- 20 characters for String,
- 20 bytes for Opaque,
- 10 instances for multi-instance resources.
]]>
</Description>
</Item>
<Item ID="2">
<Name>Clear values</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration />
<Units />
<Description>
<![CDATA[Clear all values :
- all multiple resource as empty resource
- all number to 0
- String to empty string
- boolean to false,
- opaque to empty byte array,
- time to an 1st, 1970 in the UTC time zone
- objlink to null link]]>
</Description>
</Item>
<Item ID="3">
<Name>Exec With Arguments</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration />
<Units />
<Description>
<![CDATA[This resources can be used to test "Execute Operation" with Arguments.
Sent Arguments can be read via "Arguments List"(4) resource.
E.g. If you send an Exec /3441/0/3 with "3='stringValue',4" as arguments value,
then /3441/0/4/3 will be 'stringValue' and /3441/0/4/4 will be an empty string.
]]>
</Description>
</Item>
<Item ID="4">
<Name>Arguments List</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration />
<Units />
<Description>List of Arguments from last execute on "Exec With Arguments"(3) resource. This resource is not affected by "Randomize values"(1) executable resource.
</Description>
</Item>
<Item ID="5">
<Name>Send Data</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration />
<Units />
<Description>
<![CDATA[This resources can be used to test "Send Operation".
When LWM2M client received the Execute Request, it must send data for each resources listed in "Resources to Send"(6) resource.
By default LWM2M client chooses the Content format and Timeout to use for this Send Request, but Arguments can optionally be used to change it.
Argument 0 must contain the Content Format code. e.g. 0='110' for SENML_JSON.
Argument 1 must contain the request timeout in ms. e.g. 1='2000' for 2s timeout.
]]>
</Description>
</Item>
<Item ID="6">
<Name>Resources to Send</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration />
<Units />
<Description>List of path to LWM2M node (Object / Object Instance / Resource / Resource Instance) used by "Send Data "(5) resource. This resource is not affected by "Randomize values"(1) executable resource.
</Description>
</Item>
<Item ID="110">
<Name>String Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be "initial value".
</Description>
</Item>
<Item ID="120">
<Name>Integer Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be "1024".
</Description>
</Item>
<Item ID="125">
<Name>Unsigned Integer Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Unsigned Integer</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be 2^63 (9223372036854775808).
</Description>
</Item>
<Item ID="130">
<Name>Float Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be "3.14159".
</Description>
</Item>
<Item ID="140">
<Name>Boolean Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Boolean</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be "true".</Description>
</Item>
<Item ID="150">
<Name>Opaque Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be the bytes sequence "0123456789ABCDEF" (Hexadecimal notation).
</Description>
</Item>
<Item ID="160">
<Name>Time Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Time</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be the time to an 1st, 2000 in the UTC time zone. (Timestamp value : 946684800)</Description>
</Item>
<Item ID="170">
<Name>ObjLink Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Objlnk</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be a link to instance 0 of Device Object 3 (3:0).</Description>
</Item>
<Item ID="180">
<Name>CoreLnk Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Corelnk</Type>
<RangeEnumeration />
<Units />
<Description><![CDATA[Initial value must be a the "</3442>".]]></Description>
</Item>
<Item ID="1110">
<Name>Multiple String Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be 1 instance with ID 0 and value "initial value".</Description>
</Item>
<Item ID="1120">
<Name>Multiple Integer Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be 1 instance with ID 0 and value "1024".</Description>
</Item>
<Item ID="1125">
<Name>Multiple Unsigned Integer Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Unsigned Integer</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be 1 instance with ID 0 and value 2^63 (9223372036854775808).
</Description>
</Item>
<Item ID="1130">
<Name>Multiple Float Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be 1 instance with ID 0 and value "3.14159".</Description>
</Item>
<Item ID="1140">
<Name>Multiple Boolean Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Boolean</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be 1 instance with ID 0 and value "true".</Description>
</Item>
<Item ID="1150">
<Name>Multiple Opaque Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be 1 instance with ID 0 and value "0123456789ABCDEF"(Hexadecimal notation of the bytes sequence).</Description>
</Item>
<Item ID="1160">
<Name>Multiple Time Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Time</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be 1 instance with ID 0 and value 1st, 2000 in the UTC time zone (Timestamp value : 946684800).</Description>
</Item>
<Item ID="1170">
<Name>Multiple ObjLink Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Objlnk</Type>
<RangeEnumeration />
<Units />
<Description>Initial value must be 1 instance with ID 0 and value "3:0".</Description>
</Item>
<Item ID="1180">
<Name>Multiple CoreLnk Value</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Corelnk</Type>
<RangeEnumeration />
<Units />
<Description><![CDATA[Initial value must be 1 instance with ID 0 and value "</3442>".]]></Description>
</Item>
</Resources>
<Description2></Description2>
</Object>
</LWM2M>

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

@ -68,6 +68,7 @@ import org.eclipse.leshan.core.response.WriteAttributesResponse;
import org.eclipse.leshan.core.response.WriteCompositeResponse;
import org.eclipse.leshan.core.response.WriteResponse;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.core.util.datatype.ULong;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.registration.Registration;
import org.springframework.stereotype.Service;
@ -87,6 +88,7 @@ import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogServic
import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcWriteCompositeRequest;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
@ -653,9 +655,17 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
switch (type) {
case STRING: // String
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, value.toString());
case INTEGER: // Long
final long valueInt = Integer.toUnsignedLong(Integer.parseInt(value.toString()));
case INTEGER: // Integer:
final int valueInt = Integer.parseInt(value.toString());
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueInt);
case UNSIGNED_INTEGER: // ULong
BigInteger validateUnsignedInt = new BigInteger(value.toString());
if (validateUnsignedInt.signum() < 0 ||
validateUnsignedInt.bitLength() > 64) {
throw new IllegalArgumentException("Invalid UNSIGNED_INTEGER");
}
ULong valueUnsignedInt = ULong.valueOf(validateUnsignedInt);
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueUnsignedInt);
case OBJLNK: // ObjectLink
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString()));
case BOOLEAN: // Boolean

Loading…
Cancel
Save