From 74daec474f96e6dd0b804d9e0ef1e1d1fb14a9df Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 25 Dec 2023 22:09:39 +0200 Subject: [PATCH] lwm2ml: ver 3.9.1 - start --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 51 +-- .../DefaultLwM2mAttributeParserTest.java | 147 +++++++ .../DefaultLwM2mLinkParserTest.java | 76 ++++ .../lwm2m/attributes/LwM2mAttributesTest.java | 87 ++++ .../transport/lwm2m/client/FwLwM2MDevice.java | 20 +- .../lwm2m/client/LwM2MTestClient.java | 243 +++++++---- .../client/LwM2mBinaryAppDataContainer.java | 5 +- .../transport/lwm2m/client/LwM2mLocation.java | 3 +- .../lwm2m/client/LwM2mTemperatureSensor.java | 8 +- .../transport/lwm2m/client/Lwm2mServer.java | 11 +- .../lwm2m/client/SimpleLwM2MDevice.java | 17 +- .../transport/lwm2m/client/SwLwM2MDevice.java | 19 +- .../ota/sql/OtaLwM2MIntegrationTest.java | 12 +- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 7 +- .../sql/RpcLwm2mIntegrationCreateTest.java | 4 +- .../sql/RpcLwm2mIntegrationDiscoverTest.java | 17 +- .../sql/RpcLwm2mIntegrationObserveTest.java | 4 +- .../rpc/sql/RpcLwm2mIntegrationReadTest.java | 36 +- ...pcLwm2mIntegrationWriteAttributesTest.java | 8 +- .../rpc/sql/RpcLwm2mIntegrationWriteTest.java | 67 +-- .../AbstractSecurityLwM2MIntegrationTest.java | 35 +- .../sql/NoSecLwM2MIntegrationTest.java | 18 +- .../security/sql/PskLwm2mIntegrationTest.java | 9 +- .../security/sql/RpkLwM2MIntegrationTest.java | 10 +- .../sql/X509_NoTrustLwM2MIntegrationTest.java | 6 +- .../sql/X509_TrustLwM2MIntegrationTest.java | 6 +- .../bootstrap/LwM2MServerSecurityConfig.java | 2 +- .../common/data/util/TbDDFFileParser.java | 6 +- .../data/util/TbDefaultDDFFileValidator.java | 129 ++++++ .../LwM2MTransportBootstrapService.java | 149 +++++-- .../LwM2mDefaultBootstrapSessionManager.java | 35 +- ...LwM2MBootstrapConfigStoreTaskProvider.java | 13 +- .../store/LwM2MBootstrapSecurityStore.java | 8 +- .../store/LwM2MConfigurationChecker.java | 22 +- .../LwM2MInMemoryBootstrapConfigStore.java | 6 +- ...LwM2mCredentialsSecurityInfoValidator.java | 4 +- .../lwm2m/secure/TbLwM2MAuthorizer.java | 38 +- .../AbstractLwM2mTransportResource.java | 3 +- .../server/DefaultLwM2mTransportService.java | 141 +++++-- .../lwm2m/server/LwM2MNetworkConfig.java | 6 +- .../lwm2m/server/LwM2mServerListener.java | 12 +- .../lwm2m/server/LwM2mTransportContext.java | 2 +- .../server/LwM2mVersionedModelProvider.java | 10 +- .../lwm2m/server/client/LwM2mClient.java | 21 +- .../DefaultLwM2mDownlinkMsgHandler.java | 88 ++-- .../downlink/LwM2mDownlinkMsgHandler.java | 8 +- .../rpc/DefaultLwM2MRpcRequestHandler.java | 115 +++--- .../lwm2m/server/rpc/RpcDiscoverCallback.java | 2 +- .../server/store/TbInMemorySecurityStore.java | 16 +- .../store/TbLwM2mRedisRegistrationStore.java | 99 +++-- .../store/TbLwM2mRedisSecurityStore.java | 22 +- .../server/store/TbLwM2mSecurityStore.java | 8 +- .../server/store/TbLwM2mStoreFactory.java | 6 +- .../server/store/util/LwM2MClientSerDes.java | 383 +++++++++--------- .../uplink/DefaultLwM2mUplinkMsgHandler.java | 4 +- .../lwm2m/utils/LwM2MTransportUtil.java | 133 +++--- .../validator/DeviceProfileDataValidator.java | 9 +- pom.xml | 10 +- .../lwm2m/lwm2m-profile-config.models.ts | 2 +- 59 files changed, 1629 insertions(+), 809 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mAttributeParserTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/util/TbDefaultDDFFileValidator.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 7d2bc51ce6..a160ba81f8 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.eclipse.californium.elements.config.Configuration; -import org.eclipse.leshan.client.californium.LeshanClient; +import org.eclipse.leshan.client.LeshanClient; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.ResponseCode; import org.junit.After; @@ -88,7 +88,6 @@ import static org.awaitility.Awaitility.await; import static org.eclipse.californium.core.config.CoapConfig.COAP_PORT; import static org.eclipse.californium.core.config.CoapConfig.COAP_SECURE_PORT; import static org.eclipse.leshan.client.object.Security.noSec; -import static org.eclipse.leshan.client.object.Security.noSecBootstap; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED; @@ -121,8 +120,11 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public static final String host = "localhost"; public static final String hostBs = "localhost"; - public static final int shortServerId = 123; - public static final int shortServerIdBs = 111; + public static final Integer shortServerId = 123; + public static final Integer shortServerIdBs0 = 0; + public static final int serverId = 1; + public static final int serverIdBs = 0; + public static final String COAP = "coap://"; public static final String COAPS = "coaps://"; public static final String URI = COAP + host + ":" + port; @@ -132,7 +134,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public static final Configuration COAP_CONFIG = new Configuration().set(COAP_PORT, port).set(COAP_SECURE_PORT, securityPort); public static Configuration COAP_CONFIG_BS = new Configuration().set(COAP_PORT, portBs).set(COAP_SECURE_PORT, securityPortBs); public static final Security SECURITY_NO_SEC = noSec(URI, shortServerId); - public static final Security SECURITY_NO_SEC_BS = noSecBootstap(URI_BS); protected final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS = " {\n" + @@ -235,8 +236,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte getWsClient().waitForReply(); getWsClient().registerWaitForUpdate(); - createNewClient(security, coapConfig, false, endpoint, false, null); - awaitObserveReadAll(0, false, device.getId().getId().toString()); + createNewClient(security, null, coapConfig, false, endpoint); + awaitObserveReadAll(0, device.getId().getId().toString()); String msg = getWsClient().waitForUpdate(); EntityDataUpdate update = JacksonUtil.fromString(msg, EntityDataUpdate.class); @@ -252,6 +253,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte int expectedMin = 5; Assert.assertTrue(expectedMax >= Long.parseLong(tsValue.getValue())); Assert.assertTrue(expectedMin <= Long.parseLong(tsValue.getValue())); + + } protected void createDeviceProfile(Lwm2mDeviceProfileTransportConfiguration transportConfiguration) throws Exception { @@ -300,14 +303,14 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte this.resources = resources; } - public void createNewClient(Security security, Configuration coapConfig, boolean isRpc, String endpoint, boolean isBootstrap, Security securityBs) throws Exception { + public void createNewClient(Security security, Security securityBs, Configuration coapConfig, boolean isRpc, String endpoint) throws Exception { this.clientDestroy(); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint); try (ServerSocket socket = new ServerSocket(0)) { int clientPort = socket.getLocalPort(); - lwM2MTestClient.init(security, coapConfig, clientPort, isRpc, isBootstrap, this.shortServerId, this.shortServerIdBs, - securityBs, this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest); + lwM2MTestClient.init(security, securityBs, coapConfig, clientPort, isRpc, + this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest); } } @@ -354,7 +357,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte private AbstractLwM2MBootstrapServerCredential getBootstrapServerCredentialNoSec(boolean isBootstrap) { AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential(); bootstrapServerCredential.setServerPublicKey(""); - bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs : shortServerId); + bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs0 : shortServerId); bootstrapServerCredential.setBootstrapServerIs(isBootstrap); bootstrapServerCredential.setHost(isBootstrap ? hostBs : host); bootstrapServerCredential.setPort(isBootstrap ? portBs : port); @@ -397,20 +400,18 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte .until(() -> leshanClient.getRegisteredServers().size() == 0); } - protected void awaitObserveReadAll(int cntObserve, boolean isBootstrap, String deviceIdStr) throws Exception { - if (!isBootstrap) { - await("ObserveReadAll after start client: countObserve " + cntObserve) - .atMost(40, TimeUnit.SECONDS) - .until(() -> { - String actualResultReadAll = sendObserve("ObserveReadAll", null, deviceIdStr); - ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - Assert.assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValuesReadAll = rpcActualResultReadAll.get("value").asText(); - log.warn("ObserveReadAll: [{}]", actualValuesReadAll); - int actualCntObserve = "[]".equals(actualValuesReadAll) ? 0 : actualValuesReadAll.split(",").length; - return cntObserve == actualCntObserve; - }); - } + protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception { + await("ObserveReadAll after start client: countObserve " + cntObserve) + .atMost(40, TimeUnit.SECONDS) + .until(() -> { + String actualResultReadAll = sendObserve("ObserveReadAll", null, deviceIdStr); + ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); + Assert.assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); + String actualValuesReadAll = rpcActualResultReadAll.get("value").asText(); + log.warn("ObserveReadAll: [{}]", actualValuesReadAll); + int actualCntObserve = "[]".equals(actualValuesReadAll) ? 0 : actualValuesReadAll.split(",").length; + return cntObserve == actualCntObserve; + }); } protected String sendObserve(String method, String params, String deviceIdStr) throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mAttributeParserTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mAttributeParserTest.java new file mode 100644 index 0000000000..0172710d0d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mAttributeParserTest.java @@ -0,0 +1,147 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.attributes; + +import org.eclipse.leshan.core.link.LinkParseException; +import org.eclipse.leshan.core.link.attributes.InvalidAttributeException; +import org.eclipse.leshan.core.link.lwm2m.attributes.DefaultLwM2mAttributeParser; +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.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class DefaultLwM2mAttributeParserTest { + + private final DefaultLwM2mAttributeParser parser = new DefaultLwM2mAttributeParser(); + + private static Stream validAttributes() throws InvalidAttributeException { + return Stream.of(// + Arguments.of("pmin", LwM2mAttributes.create(LwM2mAttributes.MINIMUM_PERIOD)), // + Arguments.of("pmin=30", LwM2mAttributes.create(LwM2mAttributes.MINIMUM_PERIOD, 30l)), // + Arguments.of("pmax", LwM2mAttributes.create(LwM2mAttributes.MAXIMUM_PERIOD)), // + Arguments.of("pmax=60", LwM2mAttributes.create(LwM2mAttributes.MAXIMUM_PERIOD, 60l)), // + Arguments.of("dim=2", LwM2mAttributes.create(LwM2mAttributes.DIMENSION, 2l)), // + Arguments.of("epmin", LwM2mAttributes.create(LwM2mAttributes.EVALUATE_MINIMUM_PERIOD)), // + Arguments.of("epmin=30", LwM2mAttributes.create(LwM2mAttributes.EVALUATE_MINIMUM_PERIOD, 30l)), // + Arguments.of("epmax", LwM2mAttributes.create(LwM2mAttributes.EVALUATE_MAXIMUM_PERIOD)), // + Arguments.of("epmax=60", LwM2mAttributes.create(LwM2mAttributes.EVALUATE_MAXIMUM_PERIOD, 60l)), // + Arguments.of("lt", LwM2mAttributes.create(LwM2mAttributes.LESSER_THAN)), // + Arguments.of("lt=30", LwM2mAttributes.create(LwM2mAttributes.LESSER_THAN, 30d)), // + Arguments.of("lt=-30", LwM2mAttributes.create(LwM2mAttributes.LESSER_THAN, -30d)), // + Arguments.of("lt=30.55", LwM2mAttributes.create(LwM2mAttributes.LESSER_THAN, 30.55)), // + Arguments.of("lt=-30.55", LwM2mAttributes.create(LwM2mAttributes.LESSER_THAN, -30.55)), // + Arguments.of("gt", LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN)), // + Arguments.of("gt=60", LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN, 60d)), // + Arguments.of("gt=-60", LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN, -60d)), // + Arguments.of("gt=60.55", LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN, 60.55)), // + Arguments.of("gt=-60.55", LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN, -60.55)), // + Arguments.of("st", LwM2mAttributes.create(LwM2mAttributes.STEP)), // + Arguments.of("st=60", LwM2mAttributes.create(LwM2mAttributes.STEP, 60d)), // + Arguments.of("st=60.55", LwM2mAttributes.create(LwM2mAttributes.STEP, 60.55)) // + ); + } + + @ParameterizedTest(name = "Tests {index} : {0}") + @MethodSource("validAttributes") + public void check_one_valid_attribute(String attributeAsString, LwM2mAttribute expectedValue) + throws LinkParseException, InvalidAttributeException { + LwM2mAttributeSet parsed = new LwM2mAttributeSet(parser.parseUriQuery(attributeAsString)); + LwM2mAttributeSet attResult = new LwM2mAttributeSet(expectedValue); + assertEquals(parsed, attResult); + } + + private static String[] invalidAttributes() { + return new String[] { // + // MINIMUM_PERIOD + "pmin=", // + "pmin=1.9", // + "pmin=-1.8", // + "pmin=a", // + // MAXIMUM_PERIOD + "pmax=", // + "pmax=-2", // + "pmax=2.7", // + "pmax=-2.6", // + "pmax=bc", // + // DIMENSION + "dim", // + "dim=", // + "dim=-3", // + "dim=3.5", // + "dim=-3.4", // + "dim=def", // + // EVALUATE_MINIMUM_PERIOD + "epmin=", // + "epmin=-4", // + "epmin=4.3", // + "epmin=-4.2", // + "epmin=ghij", // + // EVALUATE_MAXIMUM_PERIOD + "epmax=", // + "epmax=-5", // + "epmax=5.1", // + "epmax=-5.9", // + "epmax=klmno", // + // LESSER_THAN + "lt=", // + "lt=pqrts", // + "lt=0abc", // + // GREATER_THAN + "gt=", // + "gt=uvwxyz", // + "gt=0.xyz", // + // STEP + "st=", // + "st=-6", // + "st=-6.8", // + // Multi-attributes + "&pmin&pmax=60>=2&", // + "&pmin&pmax=60>=2", // + "pmin&pmax=60>=2&", // + "pmin&pmax=60&>=2", // + "pmin&pmax=60&>=2&&&", // + "&&&pmin&pmax=60&>=2", // + }; + } + + @ParameterizedTest(name = "Test {index} : {0}") + @MethodSource("invalidAttributes") + public void check_invalid_attributes(String invalidAttributesAsString) { + assertThrows(InvalidAttributeException.class, + () -> new LwM2mAttributeSet(parser.parseUriQuery(invalidAttributesAsString))); + + } + + @Test + public void check_multiple_valid_attributes() throws LinkParseException, InvalidAttributeException { + + LwM2mAttributeSet parsed = new LwM2mAttributeSet(parser.parseUriQuery("pmin&pmax=60>=2")); + LwM2mAttributeSet attResult = new LwM2mAttributeSet( // + LwM2mAttributes.create(LwM2mAttributes.MINIMUM_PERIOD), // + LwM2mAttributes.create(LwM2mAttributes.MAXIMUM_PERIOD, 60l), // + LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN, 2d)); + + assertEquals(attResult, parsed); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java new file mode 100644 index 0000000000..b5d8fecf10 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.attributes; + +import org.eclipse.leshan.core.link.LinkParseException; +import org.eclipse.leshan.core.link.attributes.AttributeSet; +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; +import org.eclipse.leshan.core.link.lwm2m.LwM2mLink; +import org.eclipse.leshan.core.link.lwm2m.LwM2mLinkParser; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +public class DefaultLwM2mLinkParserTest { + + private final LwM2mLinkParser parser = new DefaultLwM2mLinkParser(); + + @Test + public void check_invalid_values() throws LinkParseException { + // first check it's OK with valid value + LwM2mLink[] parsed = parser.parseLwM2mLinkFromCoreLinkFormat(";dim=255".getBytes(), null); + assertEquals(new LwM2mPath(3, 0, 11), parsed[0].getPath()); + AttributeSet attResult = new LwM2mAttributeSet(LwM2mAttributes.create(LwM2mAttributes.DIMENSION, 255l)); + assertEquals(attResult, parsed[0].getAttributes()); + + // then check an invalid one + assertThrowsExactly(LinkParseException.class, () -> { + // dim should be between 0-255 + parser.parseLwM2mLinkFromCoreLinkFormat(";dim=256".getBytes(), null); + }); + + // first check it's OK with valid value + parsed = parser.parseLwM2mLinkFromCoreLinkFormat(";ssid=1".getBytes(), null); + assertEquals(new LwM2mPath(0, 1), parsed[0].getPath()); + attResult = new LwM2mAttributeSet(LwM2mAttributes.create(LwM2mAttributes.SHORT_SERVER_ID, 1l)); + assertEquals(attResult, parsed[0].getAttributes()); + + // then check an invalid one + assertThrowsExactly(LinkParseException.class, () -> { + // ssid should be between 1-65534 + parser.parseLwM2mLinkFromCoreLinkFormat(";ssid=0".getBytes(), null); + }); + } + + @Test + public void check_attribute_with_no_value_failed() throws LinkParseException { + // first check it's OK with value + LwM2mLink[] parsed = parser.parseLwM2mLinkFromCoreLinkFormat(";pmin=200".getBytes(), null); + assertEquals(new LwM2mPath(3, 0, 11), parsed[0].getPath()); + AttributeSet attResult = new LwM2mAttributeSet(LwM2mAttributes.create(LwM2mAttributes.MINIMUM_PERIOD, 200l)); + assertEquals(attResult, parsed[0].getAttributes()); + + // then check an invalid one + assertThrowsExactly(LinkParseException.class, () -> { + // dim should be between 0-255 + parser.parseLwM2mLinkFromCoreLinkFormat(";pmin".getBytes(), null); + }); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java new file mode 100644 index 0000000000..d77a3252c0 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java @@ -0,0 +1,87 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.attributes; + +import org.eclipse.leshan.core.link.attributes.InvalidAttributeException; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeModel; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class LwM2mAttributesTest { + + @ParameterizedTest(name = "Tests {index} : {0}") + @MethodSource("supportNullAttributes") + public void check_attribute_can_be_created_with_null_value(LwM2mAttributeModel model) + throws InvalidAttributeException { + LwM2mAttribute attribute = LwM2mAttributes.create(model); + assertNotNull(attribute); + assertFalse(attribute.hasValue()); + assertNull(attribute.getValue()); + attribute = LwM2mAttributes.create(model, null); + assertNotNull(attribute); + assertFalse(attribute.hasValue()); + assertNull(attribute.getValue()); + } + + @ParameterizedTest(name = "Tests {index} : {0}") + @MethodSource("doesntSupportAttributesWithoutValue") + public void check_attribute_can_not_be_created_without_value(LwM2mAttributeModel model) { + assertThrows(UnsupportedOperationException.class, () -> LwM2mAttributes.create(model)); + } + + @ParameterizedTest(name = "Tests {index} : {0}") + @MethodSource("doesntSupportAttributesWithValueNull") + public void check_attribute_can_not_be_created_with_null(LwM2mAttributeModel model) { + assertThrows(NullPointerException.class, () -> LwM2mAttributes.create(model, null)); + } + + private static Stream supportNullAttributes() throws InvalidAttributeException { + return Stream.of(// + Arguments.of(LwM2mAttributes.MINIMUM_PERIOD), // + Arguments.of(LwM2mAttributes.MAXIMUM_PERIOD), // + Arguments.of(LwM2mAttributes.EVALUATE_MINIMUM_PERIOD), // + Arguments.of(LwM2mAttributes.EVALUATE_MAXIMUM_PERIOD), // + Arguments.of(LwM2mAttributes.LESSER_THAN), // + Arguments.of(LwM2mAttributes.GREATER_THAN), // + Arguments.of(LwM2mAttributes.STEP) // + ); + } + + private static Stream doesntSupportAttributesWithoutValue() throws InvalidAttributeException { + return Stream.of(// + Arguments.of(LwM2mAttributes.ENABLER_VERSION), // + Arguments.of(LwM2mAttributes.OBJECT_VERSION)// + ); + } + + private static Stream doesntSupportAttributesWithValueNull() throws InvalidAttributeException { + return Stream.of(// + Arguments.of(LwM2mAttributes.DIMENSION), // + Arguments.of(LwM2mAttributes.SHORT_SERVER_ID) // + ); + } +} + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java index 5fb21b344a..86124ccb5f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java @@ -17,9 +17,9 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -45,7 +45,7 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { private final AtomicInteger updateResult = new AtomicInteger(0); @Override - public ReadResponse read(ServerIdentity identity, int resourceId) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { if (!identity.isSystem()) log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { @@ -65,24 +65,24 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { } @Override - public ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) { - String withParams = null; - if (params != null && params.length() != 0) { - withParams = " with params " + params; - } - log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withParams != null ? withParams : ""); + public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { + String withArguments = ""; + if (!arguments.isEmpty()) + withArguments = " with arguments " + arguments; + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withArguments); + switch (resourceId) { case 2: startUpdating(); return ExecuteResponse.success(); default: - return super.execute(identity, resourceId, params); + return super.execute(identity, resourceId, arguments); } } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 449ac4c2ae..6e6fa12cf7 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -18,17 +18,28 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.scandium.config.DtlsConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; -import org.eclipse.leshan.client.californium.LeshanClient; -import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.LeshanClient; +import org.eclipse.leshan.client.LeshanClientBuilder; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointFactory; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; +import org.eclipse.leshan.client.californium.endpoint.ClientProtocolProvider; +import org.eclipse.leshan.client.californium.endpoint.coap.CoapOscoreProtocolProvider; +import org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientEndpointFactory; +import org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientProtocolProvider; +import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.resource.DummyInstanceEnabler; 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.client.servers.ServerIdentity; +import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter; +import org.eclipse.leshan.client.send.ManualDataSender; +import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.model.InvalidDDFFileException; import org.eclipse.leshan.core.model.LwM2mModel; @@ -49,13 +60,13 @@ import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandle import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; -import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.eclipse.leshan.core.LwM2mId.DEVICE; import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; @@ -63,6 +74,10 @@ import static org.eclipse.leshan.core.LwM2mId.LOCATION; import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; +import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.serverId; +import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.serverIdBs; +import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.shortServerId; +import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.shortServerIdBs0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE; @@ -97,11 +112,6 @@ public class LwM2MTestClient { private final ScheduledExecutorService executor; private final String endpoint; private LeshanClient leshanClient; - - private Security lwm2mSecurity; - private Security lwm2mSecurityBs; - private Lwm2mServer lwm2mServer; - private Lwm2mServer lwm2mServerBs; private SimpleLwM2MDevice lwM2MDevice; private FwLwM2MDevice fwLwM2MDevice; private SwLwM2MDevice swLwM2MDevice; @@ -112,8 +122,7 @@ public class LwM2MTestClient { private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; private LwM2mClientContext clientContext; - public void init(Security security, Configuration coapConfig, int port, boolean isRpc, boolean isBootstrap, - int shortServerId, int shortServerIdBs, Security securityBs, + public void init(Security security, Security securityBs,Configuration coapConfig, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, LwM2mClientContext clientContext) throws InvalidDDFFileException, IOException { Assert.assertNull("client already initialized", leshanClient); @@ -125,30 +134,36 @@ public class LwM2MTestClient { } LwM2mModel model = new StaticModel(models); ObjectsInitializer initializer = new ObjectsInitializer(model); - if (securityBs == null) { - initializer.setInstancesForObject(SECURITY, this.lwm2mSecurity = security); - } else { - securityBs.setId(0); - security.setId(1); - LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{this.lwm2mSecurityBs = securityBs, this.lwm2mSecurity = security}; + + if (securityBs != null && security != null) { + // SECURITY + security.setId(serverId); + securityBs.setId(serverIdBs); + LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{securityBs, security}; initializer.setClassForObject(SECURITY, Security.class); initializer.setInstancesForObject(SECURITY, instances); - } - if (isBootstrap) { - initializer.setInstancesForObject(SERVER, lwm2mServerBs = new Lwm2mServer(shortServerIdBs, 300)); + // SERVER + Server lwm2mServer = new Server(shortServerId, 300); + lwm2mServer.setId(serverId); + Server serverBs = new Server(shortServerIdBs0, 300); + serverBs.setId(serverIdBs); + instances = new LwM2mInstanceEnabler[]{serverBs, lwm2mServer}; + initializer.setClassForObject(SERVER, Server.class); + initializer.setInstancesForObject(SERVER, instances); + } else if (securityBs != null) { + // SECURITY + initializer.setInstancesForObject(SECURITY, securityBs); + // SERVER + initializer.setClassForObject(SERVER, Server.class); } else { - if (securityBs == null) { - initializer.setInstancesForObject(SERVER, lwm2mServer = new Lwm2mServer(shortServerId, 300)); - } else { - lwm2mServerBs = new Lwm2mServer(shortServerIdBs, 300); - lwm2mServerBs.setId(0); - lwm2mServer = new Lwm2mServer(shortServerId, 300); - lwm2mServer.setId(1); - LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{lwm2mServerBs, lwm2mServer}; - initializer.setClassForObject(SERVER, Server.class); - initializer.setInstancesForObject(SERVER, instances); - } + // SECURITY + initializer.setInstancesForObject(SECURITY, security); + // SERVER + Server lwm2mServer = new Server(shortServerId, 300); + lwm2mServer.setId(serverId); + initializer.setInstancesForObject(SERVER, lwm2mServer ); } + initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice(executor)); initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice()); initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice()); @@ -160,106 +175,193 @@ public class LwM2MTestClient { initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, OBJECT_INSTANCE_ID_0)); initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2MTemperatureSensor = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_0), new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12)); + List enablers = initializer.createAll(); + + // Create Californium Endpoints Provider: + // -------------------------------------- + // Define Custom CoAPS protocol provider + CoapsClientProtocolProvider customCoapsProtocolProvider = new CoapsClientProtocolProvider() { + @Override + public CaliforniumClientEndpointFactory createDefaultEndpointFactory() { + return new CoapsClientEndpointFactory() { + + @Override + protected DtlsConnectorConfig.Builder createRootDtlsConnectorConfigBuilder( + Configuration configuration) { + DtlsConnectorConfig.Builder builder = super.createRootDtlsConnectorConfigBuilder(configuration); + return builder; + }; + }; + } + }; + + // Create client endpoints Provider + List protocolProvider = new ArrayList<>(); + + /** + * "Use java-coap for CoAP protocol instead of Californium." + */ + boolean useJavaCoap = false; + if (!useJavaCoap) protocolProvider.add(new CoapOscoreProtocolProvider()); + protocolProvider.add(customCoapsProtocolProvider); + CaliforniumClientEndpointsProvider.Builder endpointsBuilder = new CaliforniumClientEndpointsProvider.Builder( + protocolProvider.toArray(new ClientProtocolProvider[protocolProvider.size()])); + + + // Create Californium Configuration + Configuration clientCoapConfig = endpointsBuilder.createDefaultConfiguration(); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(coapConfig); - dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, true); + // Set some DTLS stuff + // These configuration values are always overwritten by CLI therefore set them to transient. + clientCoapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); + clientCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); + boolean supportDeprecatedCiphers = false; + clientCoapConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !supportDeprecatedCiphers); + /** + * "Control usage of DTLS connection ID.", // + * "- 'on' to activate Connection ID support (same as -cid 0)", // + * "- 'off' to deactivate it", // + * "- Positive value define the size in byte of CID generated.", // + * "- 0 value means we accept to use CID but will not generated one for foreign peer.", // + * "Default: off" + */ + Integer cid = null; + clientCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cid); + + // Set Californium Configuration + endpointsBuilder.setConfiguration(clientCoapConfig); + endpointsBuilder.setClientAddress(new InetSocketAddress(port).getAddress()); + + + // creates EndpointsProvider + List endpointsProvider = new ArrayList<>(); + endpointsProvider.add(endpointsBuilder.build()); + /** + * dependency -> org.eclipse.leshan.transport.javacoap.client.endpoint; + */ +// if (useJavaCoap) {endpointsProvider.add(new JavaCoapClientEndpointsProvider()); + + // Configure Registration Engine DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory(); - engineFactory.setReconnectOnUpdate(false); + // old + /** + * Force reconnection/rehandshake on registration update. + */ + int comPeriodInSec = 5; + if (comPeriodInSec > 0) engineFactory.setCommunicationPeriod(comPeriodInSec * 1000); +// engineFactory.setCommunicationPeriod(5000); // old + /** + * By default client will try to resume DTLS session by using abbreviated Handshake. This option force to always do a full handshake." + */ + boolean reconnectOnUpdate = false; + engineFactory.setReconnectOnUpdate(reconnectOnUpdate); +// engineFactory.setReconnectOnUpdate(false); // old engineFactory.setResumeOnConnect(true); - engineFactory.setCommunicationPeriod(5000); - + // new + /** + * Client use queue mode (not fully implemented). + */ + boolean queueMode = false; + engineFactory.setQueueMode(queueMode); + + // Create client LeshanClientBuilder builder = new LeshanClientBuilder(endpoint); - builder.setLocalAddress("0.0.0.0", port); - builder.setObjects(initializer.createAll()); - builder.setCoapConfig(coapConfig); - builder.setDtlsConfig(dtlsConfig); + builder.setObjects(enablers); + builder.setEndpointsProviders(endpointsProvider.toArray(new LwM2mClientEndpointsProvider[endpointsProvider.size()])); + builder.setDataSenders(new ManualDataSender()); + builder.setRegistrationEngineFactory(engineFactory); + boolean supportOldFormat = true; + if (supportOldFormat) { + builder.setDecoder(new DefaultLwM2mDecoder(supportOldFormat)); + builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), supportOldFormat)); + } + builder.setRegistrationEngineFactory(engineFactory); builder.setSharedExecutor(executor); - builder.setDecoder(new DefaultLwM2mDecoder(false)); - builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false)); clientStates = new HashSet<>(); clientStates.add(ON_INIT); leshanClient = builder.build(); LwM2mClientObserver observer = new LwM2mClientObserver() { @Override - public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) { + public void onBootstrapStarted(LwM2mServer bsserver, BootstrapRequest request) { clientStates.add(ON_BOOTSTRAP_STARTED); } @Override - public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) { + public void onBootstrapSuccess(LwM2mServer bsserver, BootstrapRequest request) { clientStates.add(ON_BOOTSTRAP_SUCCESS); } @Override - public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + public void onBootstrapFailure(LwM2mServer bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { clientStates.add(ON_BOOTSTRAP_FAILURE); } @Override - public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) { + public void onBootstrapTimeout(LwM2mServer bsserver, BootstrapRequest request) { clientStates.add(ON_BOOTSTRAP_TIMEOUT); } @Override - public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) { + public void onRegistrationStarted(LwM2mServer server, RegisterRequest request) { clientStates.add(ON_REGISTRATION_STARTED); } @Override - public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) { + public void onRegistrationSuccess(LwM2mServer server, RegisterRequest request, String registrationID) { clientStates.add(ON_REGISTRATION_SUCCESS); } @Override - public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + public void onRegistrationFailure(LwM2mServer server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { clientStates.add(ON_REGISTRATION_FAILURE); } @Override - public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) { + public void onRegistrationTimeout(LwM2mServer server, RegisterRequest request) { clientStates.add(ON_REGISTRATION_TIMEOUT); } @Override - public void onUpdateStarted(ServerIdentity server, UpdateRequest request) { + public void onUpdateStarted(LwM2mServer server, UpdateRequest request) { clientStates.add(ON_UPDATE_STARTED); } @Override - public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) { + public void onUpdateSuccess(LwM2mServer server, UpdateRequest request) { clientStates.add(ON_UPDATE_SUCCESS); } @Override - public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + public void onUpdateFailure(LwM2mServer server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { clientStates.add(ON_UPDATE_FAILURE); } @Override - public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) { + public void onUpdateTimeout(LwM2mServer server, UpdateRequest request) { clientStates.add(ON_UPDATE_TIMEOUT); } @Override - public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) { + public void onDeregistrationStarted(LwM2mServer server, DeregisterRequest request) { clientStates.add(ON_DEREGISTRATION_STARTED); } @Override - public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) { + public void onDeregistrationSuccess(LwM2mServer server, DeregisterRequest request) { clientStates.add(ON_DEREGISTRATION_SUCCESS); } @Override - public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + public void onDeregistrationFailure(LwM2mServer server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { clientStates.add(ON_DEREGISTRATION_FAILURE); } @Override - public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) { + public void onDeregistrationTimeout(LwM2mServer server, DeregisterRequest request) { clientStates.add(ON_DEREGISTRATION_TIMEOUT); } @@ -270,6 +372,21 @@ public class LwM2MTestClient { }; this.leshanClient.addObserver(observer); + // Add some log about object tree life cycle. + this.leshanClient.getObjectTree().addListener(new ObjectsListenerAdapter() { + + @Override + public void objectRemoved(LwM2mObjectEnabler object) { + log.info("Object {} v{} disabled.", object.getId(), object.getObjectModel().version); + } + + @Override + public void objectAdded(LwM2mObjectEnabler object) { + log.info("Object {} v{} enabled.", object.getId(), object.getObjectModel().version); + } + }); + + if (!isRpc) { this.start(true); } @@ -279,18 +396,6 @@ public class LwM2MTestClient { if (leshanClient != null) { leshanClient.destroy(true); } - if (lwm2mSecurityBs != null) { - lwm2mSecurityBs = null; - } - if (lwm2mSecurity != null) { - lwm2mSecurity = null; - } - if (lwm2mServerBs != null) { - lwm2mServerBs = null; - } - if (lwm2mServer != null) { - lwm2mServer = null; - } if (lwM2MDevice != null) { lwM2MDevice.destroy(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java index 12a29ab216..35866dea0f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mMultipleResource; @@ -93,7 +92,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements } @Override - public ReadResponse read(ServerIdentity identity, int resourceId) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { try { switch (resourceId) { case 0: @@ -118,7 +117,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 0: diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java index 48cba35e36..4599f7c0ee 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.response.ReadResponse; @@ -85,7 +84,7 @@ public class LwM2mLocation extends BaseInstanceEnabler implements Destroyable { } @Override - public ReadResponse read(ServerIdentity identity, int resourceId) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { log.info("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 0: diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java index 7836bbf881..ae0297e02b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java @@ -17,8 +17,8 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; @@ -56,7 +56,7 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr } @Override - public synchronized ReadResponse read(ServerIdentity identity, int resourceId) { + public synchronized ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 5601: @@ -73,14 +73,14 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr } @Override - public synchronized ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) { + public synchronized ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { log.info("Execute on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 5605: resetMinMaxMeasuredValues(); return ExecuteResponse.success(); default: - return super.execute(identity, resourceId, params); + return super.execute(identity, resourceId, arguments); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java index 86f6f63a62..1cddbb17f8 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java @@ -18,11 +18,11 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.request.BindingMode; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -70,7 +70,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { } @Override - public ReadResponse read(ServerIdentity identity, int resourceid) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceid) { if (!identity.isSystem()) LOG.debug("Read on Server resource /{}/{}/{}", getModel().id, getId(), resourceid); @@ -108,7 +108,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceid, LwM2mResource value) { + public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceid, LwM2mResource value) { if (!identity.isSystem()) log.debug("Write on Server resource /{}/{}/{}", getModel().id, getId(), resourceid); @@ -195,8 +195,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { } } - @Override - public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) { + public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceid, Arguments arguments) { log.info("Execute on Server resource /{}/{}/{}", getModel().id, getId(), resourceid); if (resourceid == 8) { getLwM2mClient().triggerRegistrationUpdate(identity); @@ -210,7 +209,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { return ExecuteResponse.badRequest("probably no bootstrap server configured"); } } else { - return super.execute(identity, resourceid, params); + return super.execute(identity, resourceid, arguments); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index dbb75c01cd..8a32092572 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -17,11 +17,11 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; 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.node.LwM2mResource; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -66,7 +66,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl @Override - public ReadResponse read(ServerIdentity identity, int resourceId) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { if (!identity.isSystem()) log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { @@ -108,17 +108,16 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl } @Override - public ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) { - String withParams = null; - if (params != null && params.length() != 0) { - withParams = " with params " + params; - } - log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withParams != null ? withParams : ""); + public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { + String withArguments = ""; + if (!arguments.isEmpty()) + withArguments = " with arguments " + arguments; + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withArguments); return ExecuteResponse.success(); } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java index 2be8c2d2ae..340f9ffcb2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java @@ -17,9 +17,9 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -45,7 +45,7 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { private final AtomicInteger updateResult = new AtomicInteger(0); @Override - public ReadResponse read(ServerIdentity identity, int resourceId) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { if (!identity.isSystem()) log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { @@ -63,12 +63,11 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { } @Override - public ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) { - String withParams = null; - if (params != null && params.length() != 0) { - withParams = " with params " + params; - } - log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withParams != null ? withParams : ""); + public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { + String withArguments = ""; + if (!arguments.isEmpty()) + withArguments = " with arguments " + arguments; + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withArguments); switch (resourceId) { case 4: @@ -77,12 +76,12 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { case 6: return ExecuteResponse.success(); default: - return super.execute(identity, resourceId, params); + return super.execute(identity, resourceId, arguments); } } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java index 5c580adc85..6e6b0f2c2a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java @@ -99,8 +99,8 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); - createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, false, null); - awaitObserveReadAll(0, false, device.getId().getId().toString()); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); + awaitObserveReadAll(0, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); final Device savedDevice = doPost("/api/device", device, Device.class); @@ -124,8 +124,8 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5); - createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5, false, null); - awaitObserveReadAll(9, false, device.getId().getId().toString()); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5); + awaitObserveReadAll(9, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); final Device savedDevice = doPost("/api/device", device, Device.class); @@ -154,8 +154,8 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9); - createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9, false, null); - awaitObserveReadAll(9, false, device.getId().getId().toString()); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9); + awaitObserveReadAll(9, device.getId().getId().toString()); device.setSoftwareId(createSoftware().getId()); final Device savedDevice = doPost("/api/device", device, Device.class); //sync call diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index 2e51c300ee..ddd1329254 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.rpc; +import org.eclipse.leshan.core.link.LinkParser; +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; import org.junit.Before; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; @@ -50,6 +52,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; @DaoSqlTest public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + protected final LinkParser linkParser = new DefaultLwM2mLinkParser(); protected String OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC; protected String deviceId; public Set expectedObjects; @@ -84,7 +87,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg private void initRpc () throws Exception { String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); - createNewClient(SECURITY_NO_SEC, COAP_CONFIG, true, endpoint, false, null); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, true, endpoint); expectedObjects = ConcurrentHashMap.newKeySet(); expectedObjectIdVers = ConcurrentHashMap.newKeySet(); expectedInstances = ConcurrentHashMap.newKeySet(); @@ -147,7 +150,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg deviceId = device.getId().getId().toString(); lwM2MTestClient.start(true); - awaitObserveReadAll(2, false, device.getId().getId().toString()); +// awaitObserveReadAll(2, true, device.getId().getId().toString()); } protected String pathIdVerToObjectId(String pathIdVer) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java index 6e005fcaf8..3eb0d25e85 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java @@ -99,7 +99,7 @@ public class RpcLwm2mIntegrationCreateTest extends AbstractRpcLwM2MIntegrationTe String actualResult = sendRPCreateById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expectedObjectId = pathIdVerToObjectId((String) expectedPath); + String expectedObjectId = pathIdVerToObjectId(expectedPath); LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; String actual = rpcActualResult.get("error").asText(); @@ -118,7 +118,7 @@ public class RpcLwm2mIntegrationCreateTest extends AbstractRpcLwM2MIntegrationTe String actualResult = sendRPCreateById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expectedObjectId = pathIdVerToObjectId((String) expectedPath); + String expectedObjectId = pathIdVerToObjectId(expectedPath); LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; String actual = rpcActualResult.get("error").asText(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java index 7d3cf6acd1..ea0a1ad4ca 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java @@ -18,6 +18,8 @@ package org.thingsboard.server.transport.lwm2m.rpc.sql; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.LinkParseException; import org.eclipse.leshan.core.node.LwM2mPath; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; @@ -52,12 +54,17 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration Set actualObjects = ConcurrentHashMap.newKeySet(); Set actualInstances = ConcurrentHashMap.newKeySet(); rpcActualValue.forEach(node -> { - if (!node.get("uriReference").asText().equals("/")) { - LwM2mPath path = new LwM2mPath(node.get("uriReference").asText()); - actualObjects.add("/" + path.getObjectId()); - if (path.isObjectInstance()) { - actualInstances.add("/" + path.getObjectId() + "/" + path.getObjectInstanceId()); + try { + Link[] parsedLink = linkParser.parseCoreLinkFormat(node.asText().getBytes()); + if (!parsedLink[0].getUriReference().equals("/")) { + LwM2mPath path = new LwM2mPath(parsedLink[0].getUriReference()); + actualObjects.add("/" + path.getObjectId()); + if (path.isObjectInstance()) { + actualInstances.add("/" + path.getObjectId() + "/" + path.getObjectInstanceId()); + } } + } catch (LinkParseException e) { + throw new RuntimeException(e); } }); assertEquals(expectedInstances, actualInstances); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java index a54fdce6b5..9a17978e83 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java @@ -168,12 +168,14 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT */ @Test public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveReadAll() throws Exception { + String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; + sendRpcObserve("Observe", expectedId); String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); String actualValuesReadAll = rpcActualResultReadAll.get("value").asText(); log.warn("ObserveReadAll: [{}]", actualValuesReadAll); - assertEquals(2, actualValuesReadAll.split(",").length); + assertEquals(3, actualValuesReadAll.split(",").length); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java index cb9702aa70..cb92aada30 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java @@ -50,22 +50,28 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest */ @Test public void testReadAllObjectsInClientById_Result_CONTENT_Value_IsLwM2mObject_IsInstances() throws Exception { - expectedObjectIdVers.forEach(expected -> { - try { - String actualResult = sendRPCById((String) expected); - String expectedObjectId = pathIdVerToObjectId((String) expected); - LwM2mPath expectedPath = new LwM2mPath(expectedObjectId); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String expectedObjectInstances = "LwM2mObject [id=" + expectedPath.getObjectId() + ", instances={0=LwM2mObjectInstance [id=0, resources="; - if (expectedPath.getObjectId() == 2) { - expectedObjectInstances = "LwM2mObject [id=2, instances={}]"; + try { + expectedObjectIdVers.forEach(expected -> { + try { + String actualResult = sendRPCById((String) expected); + String expectedObjectId = pathIdVerToObjectId((String) expected); + LwM2mPath expectedPath = new LwM2mPath(expectedObjectId); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expectedObjectInstances = "LwM2mObject [id=" + expectedPath.getObjectId() + ", instances={0=LwM2mObjectInstance [id=0, resources="; + if (expectedPath.getObjectId() == 1) { + expectedObjectInstances = "LwM2mObject [id=1, instances={1="; + } else if (expectedPath.getObjectId() == 2) { + expectedObjectInstances = "LwM2mObject [id=2, instances={}]"; + } + assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances)); + } catch (Exception e) { + e.printStackTrace(); } - assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances)); - } catch (Exception e) { - e.printStackTrace(); - } - }); + }); + } catch (Exception e2){ + e2.printStackTrace(); + } } /** diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java index f9e0725084..6275a657b5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.rpc.sql; import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.leshan.core.ResponseCode; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; @@ -29,10 +30,11 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest { + @Ignore /** * WriteAttributes {"id":"/3/0/14","attributes":{"pmax":100, "pmin":10}} * if not implemented: - * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} + * {"result":"BAD_REQUEST","error":"Content Format is mandatory"} * if implemented: * {"result":"CHANGED"} */ @@ -42,8 +44,8 @@ public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MInte String expectedValue = "{\"pmax\":100, \"pmin\":10}"; String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); - String expected = "not implemented"; + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Content Format is mandatory"; String actual = rpcActualResult.get("error").asText(); assertTrue(actual.equals(expected)); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java index 3aff2e4725..e1f1cf2f15 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java @@ -161,8 +161,8 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes int resourceInstanceId25 = 25; String expectedValue0 = "00ad45675600"; String expectedValue25 = "25ad45675600cdef"; - String expectedValue = "{\"" + RESOURCE_ID_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}"; - String actualResult = sendRPCWriteObjectById("WriteUpdate", expectedPath, expectedValue); + String expectedValueResourcesObject19 = "{\"" + RESOURCE_ID_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}"; + String actualResult = sendRPCWriteObjectById("WriteUpdate", expectedPath, expectedValueResourcesObject19); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); String expectedPath0 = expectedPath + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0; @@ -181,29 +181,37 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes /** * ResourceInstance + KeySingleResource + IdSingleResource - * WriteComposite {"nodes":{"/19/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}} + * WriteComposite {"/19_1.1/0":{"0":{"0":"00ad45675600", "25":"25ad45675600cdef"}}, "UtfOffset":"+04", "/3_1.0/0/15":"Kiyv/Europe"} * {"result":"CHANGED"} */ @Test - public void testWriteCompositeValueSingleResourceResourceInstanceByIdKey_Result_CHANGED() throws Exception { - int resourceInstanceId2 = 2; - String expectedPath19_1_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId2; - String expectedValue19_1_0_2 = "00001234"; + public void testWriteCompositeValueSingleResourceWithMultiResourceInstanceByIdKey_Result_CHANGED() throws Exception { + String expectedPath_19_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0; + int resourceInstanceId0 = 0; + int resourceInstanceId25 = 25; + String expectedValue0 = "00ad45675600"; + String expectedValue25 = "25ad45675600cdef"; + String expectedValue_19_Resources = "{\"" + RESOURCE_ID_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}"; + String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedValue3_0_14 = "+04"; String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_15; String expectedValue3_0_15 = "Kiyv/Europe"; - String nodes = "{\"" + expectedPath19_1_0_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 + + String nodes = "{\"" + expectedPath_19_0 + "\":" + expectedValue_19_Resources + ", \"" + expectedKey3_0_14 + "\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}"; String actualResult = sendCompositeRPC(nodes); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); - actualResult = sendRPCReadById(expectedPath19_1_0_2); + actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId2 + ", value=" + expectedValue19_1_0_2.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); - actualResult = sendRPCReadByKey(expectedKey3_0_14); + actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadByKey(expectedKey3_0_14); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue3_0_14 + ", type=STRING]"; @@ -217,29 +225,36 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes /** * multipleResource == error - * bad - cannot be used for value Json, only primitive: SingleResource, ResourceInstance (for Json: WriteUpdate, WriteReplace) - * WriteComposite {"nodes":{"/19/0/0":{"0":"abcd5678", "10":"abcd5678"}}} + * WriteComposite {"/19_1.1/0/0":{"0":"00ad45675600", "25":"25ad45675600cdef"}} + * {"result":"CHANGED"} */ @Test - public void testWriteCompositeValueSingleMultipleResourceByIdKey_Result_BAD_REQUEST_WriteComposite_operation_for_SingleResources_or_and_ResourceInstance() throws Exception { - String nodes = "{\"/19/0/0\":{\"0\":\"abcd5678\", \"10\":\"abcd5678\"}}"; + public void testWriteCompositeValueSingleMultipleResourceOpaqueValueInputHexStringByIdKey_Result_CHANGED() throws Exception { + String expectedPath_19_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0; + int resourceInstanceId0 = 0; + int resourceInstanceId25 = 25; + String expectedValue0 = "00ad45675600"; + String expectedValue25 = "25ad45675600cdef"; + String nodes = "{\"" + expectedPath_19_0 + "/" + RESOURCE_ID_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}"; + String actualResult = sendCompositeRPC(nodes); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String actualValues = rpcActualResult.get("error").asText(); - String expectedNodes = nodes.replaceAll("\"", "").replaceAll(":", "="); - String expected = String.format("nodes: %s is not validate value. " + - "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", expectedNodes); - assertEquals(expected, actualValues); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); } /** * update_resourceInstances&update_singleResource - * new ResourceInstance if Resource is Multiple & Resource Single - * - WriteReplace {"id":"/19_1.2/1/0","value":{"2":ddff12"}} - * - WriteReplace {"key":"UtfOffset","value":"+04"} - * - WriteReplace {"id":"/3/0/15","value":"Kiyv/Europe"} * WriteComposite {"nodes":{"/19_1.1/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}}} * {"result":"CHANGED"} */ @@ -331,4 +346,4 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes String setRpcRequest = "{\"method\": \"WriteComposite\", \"params\": {\"nodes\":" + nodes + "}}"; return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); } -} \ No newline at end of file +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index 69b257d910..b97adf035c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -63,7 +63,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; -import static org.eclipse.leshan.client.object.Security.noSecBootstap; +import static org.eclipse.leshan.client.object.Security.noSecBootstrap; import static org.junit.Assert.assertEquals; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED; @@ -89,6 +89,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M protected static final String SERVER_STORE_PWD = "server_ks_password"; protected static final String SERVER_CERT_ALIAS = "server"; protected static final String SERVER_CERT_ALIAS_BS = "bootstrap"; + protected static final Security SECURITY_NO_SEC_BS = noSecBootstrap(URI_BS);; protected final X509Certificate serverX509Cert; // server certificate signed by rootCA protected final X509Certificate serverX509CertBs; // serverBs certificate signed by rootCA protected final PublicKey serverPublicKeyFromCert; // server public key used for RPK @@ -171,7 +172,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M LwM2MClientState finishState) throws Exception { Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(type)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); - this.basicTestConnection(noSecBootstap(URI_BS), + this.basicTestConnection(null , SECURITY_NO_SEC_BS, deviceCredentials, COAP_CONFIG_BS, clientEndpoint, @@ -183,22 +184,24 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M false); } - protected void basicTestConnection(Security security, + protected void basicTestConnection(Security security, Security securityBs, LwM2MDeviceCredentials deviceCredentials, Configuration coapConfig, String endpoint, Lwm2mDeviceProfileTransportConfiguration transportConfiguration, String awaitAlias, Set expectedStatuses, - boolean isBootstrap, + boolean isAwaitObserveReadAll, LwM2MClientState finishState, boolean isStartLw) throws Exception { createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); device.getId().getId().toString(); - createNewClient(security, coapConfig, true, endpoint, isBootstrap, null); + createNewClient(security, securityBs, coapConfig, true, endpoint); lwM2MTestClient.start(isStartLw); - awaitObserveReadAll(0, isBootstrap, device.getId().getId().toString()); + if (isAwaitObserveReadAll) { + awaitObserveReadAll(0, device.getId().getId().toString()); + } await(awaitAlias) .atMost(40, TimeUnit.SECONDS) .until(() -> { @@ -220,34 +223,31 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); this.basicTestConnectionBootstrapRequestTrigger( SECURITY_NO_SEC, + SECURITY_NO_SEC_BS, deviceCredentials, COAP_CONFIG, clientEndpoint, transportConfiguration, awaitAlias, expectedStatusesRegistrationLwm2mSuccess, - expectedStatusesRegistrationBsSuccess, - false, - SECURITY_NO_SEC_BS); + expectedStatusesRegistrationBsSuccess); } - private void basicTestConnectionBootstrapRequestTrigger(Security security, + private void basicTestConnectionBootstrapRequestTrigger(Security security, Security securityBs, LwM2MDeviceCredentials deviceCredentials, Configuration coapConfig, String endpoint, Lwm2mDeviceProfileTransportConfiguration transportConfiguration, String awaitAlias, Set expectedStatusesLwm2m, - Set expectedStatusesBs, - boolean isBootstrap, - Security securityBs) throws Exception { + Set expectedStatusesBs) throws Exception { createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); String deviceIdStr = device.getId().getId().toString(); - createNewClient(security, coapConfig, true, endpoint, isBootstrap, securityBs); + createNewClient(security, securityBs, coapConfig, true, endpoint); lwM2MTestClient.start(true); - awaitObserveReadAll(0, isBootstrap, deviceIdStr); + awaitObserveReadAll(0, deviceIdStr); await(awaitAlias) .atMost(40, TimeUnit.SECONDS) .until(() -> { @@ -263,7 +263,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesLwm2m)); String executedPath = "/" + OBJECT_ID_1 + "_" + lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_1).version - + "/0/" + RESOURCE_ID_9; + + "/" +serverId + "/" + RESOURCE_ID_9; lwM2MTestClient.setClientStates(new HashSet<>()); String actualResult = sendRPCSecurityExecuteById(executedPath, deviceIdStr, endpoint); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); @@ -337,7 +337,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M default: throw new IllegalStateException("Unexpected value: " + mode); } - bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs : shortServerId); + bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs0 : shortServerId); bootstrapServerCredential.setBootstrapServerIs(isBootstrap); bootstrapServerCredential.setHost(isBootstrap ? hostBs : host); bootstrapServerCredential.setPort(isBootstrap ? securityPortBs : securityPort); @@ -443,3 +443,4 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); } } + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java index 49c28cc13c..33e2965d62 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java @@ -19,7 +19,6 @@ import org.junit.Test; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOOTSTRAP_ONLY; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; @@ -39,7 +38,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationT // Bootstrap + Lwm2m @Test public void testWithNoSecConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS; + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + BOTH.name(); String awaitAlias = "await on client state (NoSecBS two section)"; basicTestConnectionBefore(clientEndpoint, awaitAlias, BOTH, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS); } @@ -51,20 +50,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationT basicTestConnectionBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS); } - @Test - public void testWithNoSecConnectBsSuccess_UpdateBootstrapSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC + BOOTSTRAP_ONLY.name(); - String awaitAlias = "await on client state (NoSecBS Bootstrap section)"; - basicTestConnectionBefore(clientEndpoint, awaitAlias, BOOTSTRAP_ONLY, expectedStatusesBsSuccess, ON_BOOTSTRAP_SUCCESS); - } - - @Test - public void testWithNoSecConnectBsSuccess_UpdateNoneSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC + NONE.name(); - String awaitAlias = "await on client state (NoSecBS None section)"; - basicTestConnectionBefore(clientEndpoint, awaitAlias, NONE, expectedStatusesBsSuccess, ON_BOOTSTRAP_SUCCESS); - } - + // Bs trigger @Test public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateTwoSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOTH.name(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index f041f0be55..a6211b2594 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -55,13 +55,14 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); this.basicTestConnection(security, + null, deviceCredentials, COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (Psk_Lwm2m)", expectedStatusesRegistrationLwm2mSuccess, - false, + true, ON_REGISTRATION_SUCCESS, true); } @@ -100,15 +101,15 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes Hex.decodeHex(keyPsk.toCharArray())); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); - this.basicTestConnection(securityBs, + this.basicTestConnection(null, securityBs, deviceCredentials, COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (PskBS two section)", expectedStatusesRegistrationBsSuccess, - true, + false, ON_REGISTRATION_SUCCESS, - true); + false); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java index c75ecbd820..73176b1646 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java @@ -49,21 +49,21 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes RPKClientCredential clientCredentials = new RPKClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded())); - Security securityBs = rpk(SECURE_URI, + Security security = rpk(SECURE_URI, shortServerId, certificate.getPublicKey().getEncoded(), privateKey.getEncoded(), serverX509Cert.getPublicKey().getEncoded()); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, false); - this.basicTestConnection(securityBs, + this.basicTestConnection(security, null, deviceCredentials, COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (Rpk_Lwm2m)", expectedStatusesRegistrationLwm2mSuccess, - false, + true, ON_REGISTRATION_SUCCESS, true); } @@ -117,14 +117,14 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes serverX509CertBs.getPublicKey().getEncoded()); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, clientPrivateKeyFromCertTrust, certificate, RPK, false); - this.basicTestConnection(securityBs, + this.basicTestConnection(null, securityBs, deviceCredentials, COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (RpkBS two section)", expectedStatusesRegistrationBsSuccess, - true, + false, ON_REGISTRATION_SUCCESS, true); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java index 1a908bffb8..5970f16958 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java @@ -57,13 +57,14 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); this.basicTestConnection(security, + null, deviceCredentials, COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (X509_Trust_Lwm2m)", expectedStatusesRegistrationLwm2mSuccess, - false, + true, ON_REGISTRATION_SUCCESS, true); } @@ -118,13 +119,14 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); this.basicTestConnection(security, + null, deviceCredentials, COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (X509NoTrust two section)", expectedStatusesRegistrationBsSuccess, - true, + false, ON_REGISTRATION_SUCCESS, true); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java index 98a5d018bc..d07d0c1df6 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java @@ -51,13 +51,14 @@ public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegra Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); this.basicTestConnection(security, + null, deviceCredentials, COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (X509_Trust_Lwm2m)", expectedStatusesRegistrationLwm2mSuccess, - false, + true, ON_REGISTRATION_SUCCESS, true); } @@ -78,13 +79,14 @@ public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegra Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); this.basicTestConnection(security, + null, deviceCredentials, COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (X509Trust two section)", expectedStatusesRegistrationBsSuccess, - true, + false, ON_REGISTRATION_SUCCESS, true); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java index b59c5fadea..8644ff1b79 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java @@ -26,7 +26,7 @@ public class LwM2MServerSecurityConfig implements Serializable { @Schema(description = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " + "This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. " + - "The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", accessMode = Schema.AccessMode.READ_ONLY) + "The values ID:1 and ID:65534 values MUST NOT be used for identifying the LwM2M Server.", example = "123", accessMode = Schema.AccessMode.READ_ONLY) protected Integer shortServerId = 123; /** Security -> ObjectId = 0 'LWM2M Security' */ @Schema(description = "Is Bootstrap Server or Lwm2m Server. " + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java index 7c1b9307c5..1a84f45b7d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java @@ -17,8 +17,6 @@ package org.thingsboard.server.common.data.util; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.LwM2m; -import org.eclipse.leshan.core.model.DDFFileValidator; -import org.eclipse.leshan.core.model.DefaultDDFFileValidator; import org.eclipse.leshan.core.model.InvalidDDFFileException; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; @@ -41,7 +39,7 @@ import java.util.Map; @Slf4j public class TbDDFFileParser { - private static final DDFFileValidator ddfFileValidator = new DefaultDDFFileValidator(); + private static final TbDefaultDDFFileValidator ddfFileValidator = new TbDefaultDDFFileValidator(); public List parse(InputStream inputStream, String streamName) throws InvalidDDFFileException, IOException { @@ -269,4 +267,4 @@ public class TbDDFFileParser { } return new ResourceModel(id, name, operations, multiple, mandatory, type, rangeEnumeration, units, description); } -} \ No newline at end of file +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDefaultDDFFileValidator.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDefaultDDFFileValidator.java new file mode 100644 index 0000000000..36ed0f668d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDefaultDDFFileValidator.java @@ -0,0 +1,129 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.util; + + +import org.eclipse.leshan.core.LwM2m.LwM2mVersion; +import org.eclipse.leshan.core.model.DDFFileValidator; +import org.eclipse.leshan.core.model.InvalidDDFFileException; +import org.eclipse.leshan.core.util.Validate; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; + +import javax.xml.XMLConstants; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.IOException; +import java.io.InputStream; + +/** + * A DDF File Validator. + *

+ * Validate a DDF File against the embedded LWM2M schema. + *

+ * Support LWM2M version 1.0 and 1.1. + */ + +public class TbDefaultDDFFileValidator implements DDFFileValidator { + private static String LWM2M_V1_0_SCHEMA_PATH = "/schemas/LWM2M.xsd"; + private static String LWM2M_V1_1_SCHEMA_PATH = "/schemas/LWM2M-v1_1.xsd"; + + private final String schema; + + /** + * Create a {@link DDFFileValidator} using the LWM2M v1.1 schema. + */ + public TbDefaultDDFFileValidator() { + this(LwM2mVersion.V1_1); + } + + /** + * Create a {@link DDFFileValidator} using schema corresponding to LWM2M {@link LwM2mVersion}. + */ + public TbDefaultDDFFileValidator(LwM2mVersion version) { + Validate.notNull(version, "version must not be null"); + if (LwM2mVersion.V1_0.equals(version)) { + schema = LWM2M_V1_0_SCHEMA_PATH; + } else if (LwM2mVersion.V1_1.equals(version)) { + schema = LWM2M_V1_1_SCHEMA_PATH; + } else { + throw new IllegalStateException(String.format("Unsupported version %s", version)); + } + } + + @Override + public void validate(Node xmlToValidate) throws InvalidDDFFileException { + try { + validate(new DOMSource(xmlToValidate)); + } catch (SAXException | IOException e) { + throw new InvalidDDFFileException(e); + } + } + + /** + * Validate a XML {@link Source} against the embedded LWM2M Schema. + * + * @param xmlToValidate an XML source to validate + * @throws SAXException see {@link Validator#validate(Source)} + * @throws IOException see {@link Validator#validate(Source)} + */ + public void validate(Source xmlToValidate) throws SAXException, IOException { + Validator validator = getEmbeddedLwM2mSchema().newValidator(); + validator.validate(xmlToValidate); + } + + /** + * Get the Embedded the LWM2M.xsd Schema. + * + * @throws SAXException see {@link SchemaFactory#newSchema(Source)} + */ + protected Schema getEmbeddedLwM2mSchema() throws SAXException { + InputStream inputStream = DDFFileValidator.class.getResourceAsStream(schema); + Source source = new StreamSource(inputStream); + SchemaFactory schemaFactory = createSchemaFactory(); + return schemaFactory.newSchema(source); + } + + protected SchemaFactory createSchemaFactory() { + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); +// SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); + try { + // Create Safe SchemaFactory (not vulnerable to XXE Attacks) + // -------------------------------------------------------- + // There is several recommendation from different source we try to apply all, even if some are maybe + // redundant. + + // from : + // https://semgrep.dev/docs/cheat-sheets/java-xxe/ + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + + // from : + // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#schemafactory +// factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); +// factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + } catch (SAXNotRecognizedException | SAXNotSupportedException e) { + throw new IllegalStateException("Unable to create SchemaFactory", e); + } + return factory; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 75a2f936ca..96a02bdf97 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -15,13 +15,20 @@ */ package org.thingsboard.server.transport.lwm2m.bootstrap; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.scandium.config.DtlsConfig; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServerBuilder; +import org.eclipse.leshan.server.californium.bootstrap.LwM2mBootstrapPskStore; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointsProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coap.CoapBootstrapServerProtocolProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coaps.CoapsBootstrapServerProtocolProvider; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.config.ssl.SslCredentials; @@ -33,16 +40,12 @@ import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MInMemoryBoots import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; +import java.net.InetSocketAddress; import java.security.cert.X509Certificate; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT; -import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE; -import static org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole.SERVER_ONLY; import static org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService.PSK_CIPHER_SUITES; import static org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService.RPK_OR_X509_CIPHER_SUITES; import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig; @@ -82,24 +85,121 @@ public class LwM2MTransportBootstrapService { public LeshanBootstrapServer getLhBootstrapServer() { LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); - builder.setLocalAddress(bootstrapConfig.getHost(), bootstrapConfig.getPort()); - builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); - /* Create CoAP Config */ - builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); + // Create Californium Endpoints Provider: + // ------------------ + // Create Server Endpoints Provider + CaliforniumBootstrapServerEndpointsProvider.Builder endpointsBuilder = new CaliforniumBootstrapServerEndpointsProvider.Builder( + // Add coap Protocol support + new CoapBootstrapServerProtocolProvider(), + + // Add coaps/dtls protocol support + new CoapsBootstrapServerProtocolProvider(c -> { + if (this.bootstrapConfig.getSslCredentials() != null) { + c.setAdvancedCertificateVerifier(certificateVerifier); + c.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, RPK_OR_X509_CIPHER_SUITES); + } else { + log.info("Unable to load X509 files for LWM2MServer"); + LwM2mBootstrapPskStore lwM2mBsPskStore = new LwM2mBootstrapPskStore(lwM2MBootstrapSecurityStore); + c.setAdvancedPskStore(lwM2mBsPskStore); + c.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, PSK_CIPHER_SUITES); + } + })); + + + // Create Californium Configuration + Configuration serverCoapConfig = endpointsBuilder.createDefaultConfiguration(); + getCoapConfig(serverCoapConfig, bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(),serverConfig); + serverCoapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); + serverCoapConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers()); + serverCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); + int cid = 6; + if (cid > 0) { + serverCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cid); + } + + serverCoapConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups()); + + serverCoapConfig.setTransient(DTLS_RETRANSMISSION_TIMEOUT); + serverCoapConfig.set(DTLS_RETRANSMISSION_TIMEOUT, serverConfig.getDtlsRetransmissionTimeout(), MILLISECONDS); + + + + /* Create DTLS Config */ + this.setServerWithCredentials(builder); +// DtlsConnectorConfig dtlsConfig; +// try { +// dtlsConfig = dtlsConfigBuilder.build(); +// } catch (IllegalStateException e) { +// log.warn("Unable to create DTLS config for endpont {}.", endpointUri.toString(), e); +// return null; +// } +// +// Connector dTLSConnector = new DTLSConnector(dtlsConfig); - /* Create and Set DTLS Config */ - DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); +// endpointsBuilder.setConnector(dTLSConnector); +// endpointsBuilder.setConfiguration(serverCoapConfig); - dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups()); - dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers()); - dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, serverConfig.getDtlsRetransmissionTimeout(), MILLISECONDS); - dtlsConfig.set(DTLS_ROLE, SERVER_ONLY); - setServerWithCredentials(builder, dtlsConfig); +// endpointsBuilder.setLoggingTag(String.format("[%s]", "/" + "options.getUriPathString()")); +// endpointsBuilder.setEndpointContextMatcher(new Lwm2mEndpointContextMatcher()); - /* Set DTLS Config */ - builder.setDtlsConfig(dtlsConfig); + /* Create credentials */ + + + + + // Set Californium Configuration + endpointsBuilder.setConfiguration(serverCoapConfig); + + + // Create CoAP endpoint + InetSocketAddress coapAddr = new InetSocketAddress(bootstrapConfig.getHost(), bootstrapConfig.getPort()); + endpointsBuilder.addEndpoint(coapAddr, Protocol.COAP); + + // Create CoAP over DTLS endpoint + InetSocketAddress coapsAddr = new InetSocketAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); + endpointsBuilder.addEndpoint(coapsAddr, Protocol.COAPS); + + +// builder.setLocalAddress(config.getHost(), config.getPort()); +// builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort()); +// builder.setDecoder(new DefaultLwM2mDecoder()); + /* Use a magic converter to support bad type send by the UI. */ +// builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance())); + + /* Create CoAP Config */ +// builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); + +// +// /* Set securityStore with new registrationStore */ +// builder.setSecurityStore(securityStore); +// builder.setRegistrationStore(registrationStore); +// +// +// // Create LWM2M server +// builder.setEndpointsProviders(endpointsBuilder.build()); +// +// +// +// builder.setLocalAddress(bootstrapConfig.getHost(), bootstrapConfig.getPort()); +// builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); +// +// /* Create CoAP Config */ +// builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); +// +// +// /* Create and Set DTLS Config */ +// DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); +// +// dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups()); +// dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers()); +// dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, serverConfig.getDtlsRetransmissionTimeout(), MILLISECONDS); +// dtlsConfig.set(DTLS_ROLE, SERVER_ONLY); +// setServerWithCredentials(builder, dtlsConfig); +// +// /* Set DTLS Config */ +// builder.setDtlsConfig(dtlsConfig); /* Set securityStore with new ConfigStore */ builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); @@ -112,22 +212,19 @@ public class LwM2MTransportBootstrapService { builder.setSessionManager(sessionManager); /* Create BootstrapServer */ + builder.setEndpointsProviders(endpointsBuilder.build()); return builder.build(); } - private void setServerWithCredentials(LeshanBootstrapServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) { + private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) { if (this.bootstrapConfig.getSslCredentials() != null) { SslCredentials sslCredentials = this.bootstrapConfig.getSslCredentials(); builder.setPublicKey(sslCredentials.getPublicKey()); builder.setPrivateKey(sslCredentials.getPrivateKey()); builder.setCertificateChain(sslCredentials.getCertificateChain()); - dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier); - dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, RPK_OR_X509_CIPHER_SUITES); } else { /* by default trust all */ builder.setTrustedCertificates(new X509Certificate[0]); - log.info("Unable to load X509 files for BootStrap Server"); - dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, PSK_CIPHER_SUITES); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java index ea3b8e60c9..03b99f12c9 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java @@ -16,10 +16,13 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.peer.IpPeer; +import org.eclipse.leshan.core.peer.LwM2mPeer; +import org.eclipse.leshan.core.peer.PskIdentity; +import org.eclipse.leshan.core.peer.X509Identity; import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; import org.eclipse.leshan.core.request.BootstrapFinishRequest; import org.eclipse.leshan.core.request.BootstrapRequest; -import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.BootstrapFailureCause; @@ -39,6 +42,7 @@ import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecu import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapTaskProvider; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -63,7 +67,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession * @param bsSecurityStore the {@link BootstrapSecurityStore} used by default {@link SecurityChecker}. */ public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore, TransportService transportService) { - this(bsSecurityStore, new SecurityChecker(), new LwM2MBootstrapConfigStoreTaskProvider(configStore), + this(bsSecurityStore, configStore, new SecurityChecker(), new LwM2MBootstrapConfigStoreTaskProvider(configStore), new StandardBootstrapModelProvider()); this.transportService = transportService; } @@ -74,9 +78,9 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession * @param bsSecurityStore the {@link BootstrapSecurityStore} used by {@link SecurityChecker}. * @param securityChecker used to accept or refuse new {@link BootstrapSession}. */ - public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker, + public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore, SecurityChecker securityChecker, LwM2MBootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) { - super(bsSecurityStore, securityChecker, tasksProvider, modelProvider); + super(bsSecurityStore, configStore); this.bsSecurityStore = bsSecurityStore; this.securityChecker = securityChecker; this.tasksProvider = tasksProvider; @@ -84,23 +88,24 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession } @Override - public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { - boolean authorized = true; + public BootstrapSession begin(BootstrapRequest request, LwM2mPeer sender, URI endpointUsed) { + boolean authorized = sender.getIdentity().isSecure(); Iterator securityInfos = null; - try { + try { if (bsSecurityStore != null && securityChecker != null) { - if (clientIdentity.isPSK()) { - SecurityInfo securityInfo = bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity()); + if (((IpPeer) sender).isPSK()) { + SecurityInfo securityInfo = bsSecurityStore.getByIdentity(((PskIdentity) sender.getIdentity()).getPskIdentity()); securityInfos = Collections.singletonList(securityInfo).iterator(); - } else if (!clientIdentity.isX509()) { + } + else if (!((IpPeer) sender).isX509()) { securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName()); } - authorized = this.checkSecurityInfo(request.getEndpointName(), clientIdentity, securityInfos); + authorized = this.checkSecurityInfo(request.getEndpointName(), sender, securityInfos); } } catch (LwM2MAuthException e) { authorized = false; } - DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized); + DefaultBootstrapSession session = new DefaultBootstrapSession(request, sender, authorized, null, endpointUsed); if (authorized) { try { this.tasksProvider.put(session.getEndpoint()); @@ -239,9 +244,9 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession transportService.log(((LwM2MBootstrapSecurityStore) bsSecurityStore).getSessionByEndpoint(endpointName), logMsg); } - private boolean checkSecurityInfo(String endpoint, Identity clientIdentity, Iterator securityInfos) { - if (clientIdentity.isX509()) { - return clientIdentity.getX509CommonName().equals(endpoint) + private boolean checkSecurityInfo(String endpoint, LwM2mPeer clientIdentity, Iterator securityInfos) { + if (((IpPeer) clientIdentity).isX509()) { + return ((X509Identity)clientIdentity.getIdentity()).getX509CommonName().equals(endpoint) & ((LwM2MBootstrapSecurityStore) bsSecurityStore).getBootstrapConfigByEndpoint(endpoint) != null; } else { return securityChecker.checkSecurityInfos(endpoint, clientIdentity, securityInfos); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java index 53d72521b0..018648100d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java @@ -76,7 +76,8 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask @Override public Tasks getTasks(BootstrapSession session, List previousResponse) { - BootstrapConfig config = store.get(session.getEndpoint(), session.getIdentity(), session); +// BootstrapConfig config = store.get(session.getEndpoint(), session.getClientTransportData().getIdentity(), session); + BootstrapConfig config = store.get(session); if (config == null) { return null; } @@ -150,7 +151,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask * The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server. * "Short Server ID": * - Link Instance (lwm2m Server) hase linkParams with key = "ssid" value = "shortId" (ver lvm2m = 1.1). - * - Link Instance (bootstrap Server) hase not linkParams with key = "ssid" (ver lvm2m = 1.1). + * - Link Instance (bootstrap Server) hase not linkParams with key = "ssid" (ver lvm2m = 1.0). */ protected void findSecurityInstanceId(Link[] objectLinks, String endpoint) { log.info("Object after discover: [{}]", objectLinks); @@ -159,14 +160,14 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask try { LwM2mPath path = new LwM2mPath(link.getUriReference()); if (path.isObjectInstance()) { - if (link.getLinkParams().containsKey("ssid")) { - int serverId = Integer.parseInt(link.getLinkParams().get("ssid").getUnquoted()); + if (link.getAttributes().get("ssid") != null) { + int serverId = Integer.parseInt(link.getAttributes().get("ssid").getCoreLinkValue()); if (!lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(serverId)) { lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(serverId, path.getObjectInstanceId()); } else { log.error("Invalid lwm2mSecurityInstance by [{}]", path.getObjectInstanceId()); } - lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(Integer.valueOf(link.getLinkParams().get("ssid").getUnquoted()), path.getObjectInstanceId()); + lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(serverId, path.getObjectInstanceId()); } else { if (!this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(0)) { this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(BOOTSTRAP_DEFAULT_SHORT_ID, path.getObjectInstanceId()); @@ -221,7 +222,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask LwM2mPath path = new LwM2mPath(link.getUriReference()); if (!path.isRoot() && path.getObjectId() < 3) { if (path.isObject()) { - String ver = link.getLinkParams().get("ver") != null ? link.getLinkParams().get("ver").getUnquoted() : "1.0"; + String ver = link.getAttributes().get("ver") != null ? link.getAttributes().get("ver").getCoreLinkValue() : "1.0"; this.supportedObjects.put(path.getObjectId(), ver); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java index 5dd4e41c3e..be4d0b15ad 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.store; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.peer.OscoreIdentity; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; @@ -98,6 +99,11 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } } + @Override + public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { + return null; + } + public TbLwM2MSecurityInfo getX509ByEndpoint(String endPoint) { TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, BOOTSTRAP); this.addValueToStore(store, store.getEndpoint()); @@ -210,4 +216,4 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } return securityInfo; } -} \ No newline at end of file +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java index 3d3a96ac9e..1466012e2b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java @@ -58,17 +58,25 @@ public class LwM2MConfigurationChecker extends ConfigurationChecker { for (Map.Entry e : config.servers.entrySet()) { BootstrapConfig.ServerConfig srvCfg = e.getValue(); - // shortId checks - if (srvCfg.shortId == 0) { - throw new InvalidConfigurationException("short ID must not be 0"); - } - // look for security entry BootstrapConfig.ServerSecurity security = getSecurityEntry(config, srvCfg.shortId); - if (security == null) { throw new InvalidConfigurationException("no security entry for server instance: " + e.getKey()); } + // BS Server + if (security.bootstrapServer && srvCfg.shortId != 0) { + throw new InvalidConfigurationException("short ID must be 0"); + } + + // LwM2M Server + /** + * This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. + * This Resource MUST be set when the Bootstrap-Server Resource has false value. + * Specific ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server (Section 6.3 of the LwM2M version 1.0 specification). + */ + if (!security.bootstrapServer && (srvCfg.shortId < 1 && srvCfg.shortId > 65534 )) { + throw new InvalidConfigurationException("Specific ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server"); + } } } @@ -81,4 +89,4 @@ public class LwM2MConfigurationChecker extends ConfigurationChecker { return null; } -} \ No newline at end of file +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java index bf2cc003a0..62339211bd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java @@ -16,9 +16,7 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.store; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; -import org.eclipse.leshan.server.bootstrap.BootstrapSession; import org.eclipse.leshan.server.bootstrap.ConfigurationChecker; import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; @@ -39,8 +37,8 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt private final Lock writeLock = readWriteLock.writeLock(); protected final ConfigurationChecker configChecker = new LwM2MConfigurationChecker(); - @Override - public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) { + + public BootstrapConfig get(String endpoint) { return bootstrapByEndpoint.get(endpoint); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index 63b2a60e8e..4a3eccd664 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -67,14 +67,14 @@ public class LwM2mCredentialsSecurityInfoValidator { new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { - log.trace("Validated credentials: [{}] [{}]", credentialsId, msg); + log.info("Validated credentials: [{}] [{}]", credentialsId, msg); resultSecurityStore[0] = createSecurityInfo(credentialsId, msg, keyValue); latch.countDown(); } @Override public void onError(Throwable e) { - log.trace("[{}] [{}] Failed to process credentials ", credentialsId, e); + log.info("[{}] [{}] Failed to process credentials ", credentialsId, e); TbLwM2MSecurityInfo result = new TbLwM2MSecurityInfo(); result.setEndpoint(credentialsId); resultSecurityStore[0] = result; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java index 7cdbe609e1..573e2fa1e7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java @@ -18,9 +18,11 @@ package org.thingsboard.server.transport.lwm2m.secure; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; -import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.peer.LwM2mPeer; +import org.eclipse.leshan.core.peer.X509Identity; import org.eclipse.leshan.core.request.UplinkRequest; import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.security.Authorization; import org.eclipse.leshan.server.security.Authorizer; import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityInfo; @@ -45,38 +47,30 @@ public class TbLwM2MAuthorizer implements Authorizer { private final LwM2mClientContext clientContext; @Override - public Registration isAuthorized(UplinkRequest request, Registration registration, Identity senderIdentity) { - if (senderIdentity.isX509()) { - TbX509DtlsSessionInfo sessionInfo = sessionStorage.get(registration.getEndpoint()); - if (sessionInfo != null) { - if (sessionInfo.getX509CommonName().endsWith(senderIdentity.getX509CommonName())) { - clientContext.registerClient(registration, sessionInfo.getCredentials()); + public Authorization isAuthorized(UplinkRequest request, Registration registration, LwM2mPeer sender) { + SecurityInfo expectedSecurityInfo = null; + if (securityStore != null) expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); + if (securityChecker.checkSecurityInfo(registration.getEndpoint(), sender, expectedSecurityInfo)) { + if (sender.getIdentity() instanceof X509Identity) { + TbX509DtlsSessionInfo sessionInfo = sessionStorage.get(registration.getEndpoint()); + if (sessionInfo != null) { // X509 certificate is valid and matches endpoint. - return registration; - } else { - // X509 certificate is not valid. - return null; + clientContext.registerClient(registration, sessionInfo.getCredentials()); } } - // If session info is not found, this may be the trusted certificate, so we still need to check all other options below. - } - SecurityInfo expectedSecurityInfo; try { - expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); if (expectedSecurityInfo != null && expectedSecurityInfo.usePSK() && expectedSecurityInfo.getEndpoint().equals(SecurityMode.NO_SEC.toString()) - && expectedSecurityInfo.getIdentity().equals(SecurityMode.NO_SEC.toString()) + && expectedSecurityInfo.getPskIdentity().equals(SecurityMode.NO_SEC.toString()) && Arrays.equals(SecurityMode.NO_SEC.toString().getBytes(), expectedSecurityInfo.getPreSharedKey())) { - expectedSecurityInfo = null; + return Authorization.declined(); } } catch (LwM2MAuthException e) { log.info("Registration failed: FORBIDDEN, endpointId: [{}]", registration.getEndpoint()); - return null; + return Authorization.declined(); } - if (securityChecker.checkSecurityInfo(registration.getEndpoint(), senderIdentity, expectedSecurityInfo)) { - return registration; + return Authorization.approved(); } else { - securityStore.remove(registration.getEndpoint(), registration.getId()); - return null; + return Authorization.declined(); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java index d757c91648..dd387f5264 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java @@ -18,12 +18,13 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.leshan.core.californium.LwM2mCoapResource; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; @Slf4j public abstract class AbstractLwM2mTransportResource extends LwM2mCoapResource { public AbstractLwM2mTransportResource(String name) { - super(name); + super(name, new IdentityHandlerProvider()); } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 5fb36f6f55..5c2045f61a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -15,16 +15,22 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.scandium.config.DtlsConfig; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; -import org.eclipse.leshan.server.californium.LeshanServer; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; +import org.eclipse.leshan.server.LeshanServer; +import org.eclipse.leshan.server.LeshanServerBuilder; +import org.eclipse.leshan.server.californium.LwM2mPskStore; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider; +import org.eclipse.leshan.server.californium.endpoint.coap.CoapServerProtocolProvider; +import org.eclipse.leshan.server.californium.endpoint.coaps.CoapsServerProtocolProvider; +import org.eclipse.leshan.server.registration.RegistrationStore; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; import org.thingsboard.server.cache.ota.OtaPackageDataCache; @@ -37,13 +43,11 @@ import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier; import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import jakarta.annotation.PreDestroy; +import java.net.InetSocketAddress; import java.security.cert.X509Certificate; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE; @@ -64,12 +68,13 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { public static final CipherSuite[] RPK_OR_X509_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}; public static final CipherSuite[] PSK_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256}; +// public static final CipherSuite[] PSK_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8}; private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; private final OtaPackageDataCache otaPackageDataCache; private final LwM2mUplinkMsgHandler handler; - private final CaliforniumRegistrationStore registrationStore; + private final RegistrationStore registrationStore; private final TbSecurityStore securityStore; private final TbLwM2MDtlsCertificateVerifier certificateVerifier; private final TbLwM2MAuthorizer authorizer; @@ -88,7 +93,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { * "coap://host:port/{path}/{token}/{nameFile}" */ LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(otaPackageDataCache, FIRMWARE_UPDATE_COAP_RESOURCE); - this.server.coap().getServer().add(otaCoapResource); +// this.server.coap().getServer().add(otaCoapResource); this.context.setServer(server); this.startLhServer(); } @@ -117,54 +122,128 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private LeshanServer getLhServer() { LeshanServerBuilder builder = new LeshanServerBuilder(); - builder.setLocalAddress(config.getHost(), config.getPort()); - builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort()); - builder.setDecoder(new DefaultLwM2mDecoder()); - /* Use a magic converter to support bad type send by the UI. */ - builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance())); - - /* Create CoAP Config */ - builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); /* Define model provider (Create Models )*/ builder.setObjectModelProvider(modelProvider); + /* Set securityStore with new registrationStore */ builder.setSecurityStore(securityStore); builder.setRegistrationStore(registrationStore); + + // Create Californium Endpoints Provider: + // ------------------ + // Create Server Endpoints Provider + CaliforniumServerEndpointsProvider.Builder endpointsBuilder = new CaliforniumServerEndpointsProvider.Builder( + // Add coap Protocol support + new CoapServerProtocolProvider(), + + // Add coaps/dtls protocol support + new CoapsServerProtocolProvider(c -> { + if (this.config.getSslCredentials() != null) { + c.setAdvancedCertificateVerifier(certificateVerifier); + c.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, RPK_OR_X509_CIPHER_SUITES); + } else { + log.info("Unable to load X509 files for LWM2MServer"); + LwM2mPskStore lwM2mPskStore = new LwM2mPskStore(securityStore, registrationStore); + c.setAdvancedPskStore(lwM2mPskStore); + c.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, PSK_CIPHER_SUITES); + } + })); + + // Create Californium Configuration + Configuration serverCoapConfig = endpointsBuilder.createDefaultConfiguration(); + getCoapConfig(serverCoapConfig, config.getPort(), config.getSecurePort(), config); + + // Set some DTLS stuff + + serverCoapConfig.setTransient(DTLS_RECOMMENDED_CURVES_ONLY); + serverCoapConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, config.isRecommendedSupportedGroups()); + + serverCoapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); + serverCoapConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, config.isRecommendedCiphers()); + + serverCoapConfig.set(DTLS_RETRANSMISSION_TIMEOUT, config.getDtlsRetransmissionTimeout(), MILLISECONDS); + serverCoapConfig.set(DTLS_ROLE, SERVER_ONLY); + serverCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); + /** + * "Control usage of DTLS connection ID.", // + * "- 'on' to activate Connection ID support ", // + * " (same as -cid 6)", // + * "- 'off' to deactivate it", // + * "- Positive value define the size in byte of CID generated.", // + * "- 0 value means we accept to use CID but will not generated one for foreign peer.", // + * "Default: on" + */ + int cid = 6; + serverCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cid); + + /* Create DTLS Config */ - DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(config.getPort(), config.getSecurePort(), config)); + this.setServerWithCredentials(builder); + + +// DtlsConnectorConfig dtlsConfig; +// try { +// dtlsConfig = dtlsConfigBuilder.build(); +// } catch (IllegalStateException e) { +// log.warn("Unable to create DTLS config for endpont {}.", endpointUri.toString(), e); +// return null; +// } +// +// Connector dTLSConnector = new DTLSConnector(dtlsConfig); + +// endpointsBuilder.setConnector(dTLSConnector); +// endpointsBuilder.setConfiguration(serverCoapConfig); - dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, config.isRecommendedSupportedGroups()); - dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, config.isRecommendedCiphers()); - dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, config.getDtlsRetransmissionTimeout(), MILLISECONDS); - dtlsConfig.set(DTLS_ROLE, SERVER_ONLY); +// endpointsBuilder.setLoggingTag(String.format("[%s]", "/" + "options.getUriPathString()")); +// endpointsBuilder.setEndpointContextMatcher(new Lwm2mEndpointContextMatcher()); /* Create credentials */ - this.setServerWithCredentials(builder, dtlsConfig); - /* Set DTLS Config */ - builder.setDtlsConfig(dtlsConfig); - /* Create LWM2M server */ + + + // Set Californium Configuration + endpointsBuilder.setConfiguration(serverCoapConfig); + + + // Create CoAP endpoint + InetSocketAddress coapAddr = new InetSocketAddress(config.getHost(), config.getPort()); + endpointsBuilder.addEndpoint(coapAddr, Protocol.COAP); + + // Create CoAP over DTLS endpoint + InetSocketAddress coapsAddr = new InetSocketAddress(config.getSecureHost(), config.getSecurePort()); + endpointsBuilder.addEndpoint(coapsAddr, Protocol.COAPS); + + + builder.setDecoder(new DefaultLwM2mDecoder(true)); + builder.setEncoder(new DefaultLwM2mEncoder(true)); +// builder.setDecoder(new DefaultLwM2mDecoder()); +// /* Use a magic converter to support bad type send by the UI. */ +// builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance())); + + /* Create CoAP Config */ +// builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); + + + // Create LWM2M server + builder.setEndpointsProviders(endpointsBuilder.build()); return builder.build(); } - private void setServerWithCredentials(LeshanServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) { + private void setServerWithCredentials(LeshanServerBuilder builder) { +// private void setServerWithCredentials(LeshanServerBuilder builder) { if (this.config.getSslCredentials() != null) { SslCredentials sslCredentials = this.config.getSslCredentials(); builder.setPublicKey(sslCredentials.getPublicKey()); builder.setPrivateKey(sslCredentials.getPrivateKey()); builder.setCertificateChain(sslCredentials.getCertificateChain()); - dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier); builder.setAuthorizer(authorizer); - dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, RPK_OR_X509_CIPHER_SUITES); } else { /* by default trust all */ builder.setTrustedCertificates(new X509Certificate[0]); - log.info("Unable to load X509 files for LWM2MServer"); - dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, PSK_CIPHER_SUITES); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java index 569198f50f..eafe11d9dc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java @@ -27,8 +27,8 @@ import static org.eclipse.californium.core.config.CoapConfig.DEFAULT_BLOCKWISE_S public class LwM2MNetworkConfig { - public static Configuration getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort, LwM2MTransportServerConfig config) { - Configuration coapConfig = new Configuration(); + public static Configuration getCoapConfig(Configuration coapConfig, Integer serverPortNoSec, Integer serverSecurePort, LwM2MTransportServerConfig config) { +// Configuration coapConfig = new Configuration(); coapConfig.set(CoapConfig.COAP_PORT, serverPortNoSec); coapConfig.set(CoapConfig.COAP_SECURE_PORT, serverSecurePort); /** @@ -107,4 +107,4 @@ public class LwM2MNetworkConfig { return coapConfig; } -} \ No newline at end of file +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index b894861576..a29a37c766 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.SingleObservation; @@ -32,7 +32,6 @@ import org.eclipse.leshan.server.send.SendListener; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Collection; -import java.util.Map; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVersionedId; @@ -128,10 +127,15 @@ public class LwM2mServerListener { public final SendListener sendListener = new SendListener() { @Override - public void dataReceived(Registration registration, Map map, SendRequest sendRequest) { + public void dataReceived(Registration registration, TimestampedLwM2mNodes data, SendRequest request) { if (registration != null) { - service.onUpdateValueWithSendRequest(registration, sendRequest); + service.onUpdateValueWithSendRequest(registration, request); } } + + @Override + public void onError(Registration registration, String errorMessage, Exception error) { + + } }; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java index f00d475a4b..91ae14108b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.Getter; import lombok.Setter; -import org.eclipse.leshan.server.californium.LeshanServer; +import org.eclipse.leshan.server.LeshanServer; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java index 3dd13cd171..c6716b6bf4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.model.DefaultDDFFileValidator; +import org.eclipse.leshan.core.LwM2m; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; @@ -107,7 +107,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { @Override public ObjectModel getObjectModel(int objectId) { - String version = registration.getSupportedVersion(objectId); + String version = String.valueOf(registration.getSupportedVersion(objectId)); if (version != null) { return this.getObjectModelDynamic(objectId, version); } @@ -116,10 +116,10 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { @Override public Collection getObjectModels() { - Map supportedObjects = this.registration.getSupportedObject(); + Map supportedObjects = this.registration.getSupportedObject(); Collection result = new ArrayList<>(supportedObjects.size()); - for (Map.Entry supportedObject : supportedObjects.entrySet()) { - ObjectModel objectModel = this.getObjectModelDynamic(supportedObject.getKey(), supportedObject.getValue()); + for (Map.Entry supportedObject : supportedObjects.entrySet()) { + ObjectModel objectModel = this.getObjectModelDynamic(supportedObject.getKey(), String.valueOf(supportedObject.getValue())); if (objectModel != null) { result.add(objectModel); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 9b63c2f2b9..b26d8f03e8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -20,7 +20,8 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.link.LinkParamValue; +import org.eclipse.leshan.core.LwM2m; +import org.eclipse.leshan.core.link.attributes.Attribute; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mMultipleResource; @@ -271,7 +272,7 @@ public class LwM2mClient { public ResourceModel getResourceModel(String pathIdVer, LwM2mModelProvider modelProvider) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); - String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); + String verSupportedObject = String.valueOf(registration.getSupportedObject().get(pathIds.getObjectId())); String verRez = getVerFromPathIdVerOrId(pathIdVer); return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) .getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()) : null; @@ -289,7 +290,7 @@ public class LwM2mClient { public ObjectModel getObjectModel(String pathIdVer, LwM2mModelProvider modelProvider) { try { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); - String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); + String verSupportedObject = String.valueOf(registration.getSupportedObject().get(pathIds.getObjectId())); String verRez = getVerFromPathIdVerOrId(pathIdVer); return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()) : null; @@ -370,12 +371,12 @@ public class LwM2mClient { public String isValidObjectVersion(String path) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path)); - String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); + LwM2m.Version verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); if (verSupportedObject == null) { return String.format("Specified object id %s absent in the list supported objects of the client or is security object!", pathIds.getObjectId()); } else { String verRez = getVerFromPathIdVerOrId(path); - if (verRez == null || !verRez.equals(verSupportedObject)) { + if (verRez == null || !verRez.equals(verSupportedObject.toString())) { return String.format("Specified resource id %s is not valid version! Must be version: %s", path, verSupportedObject); } } @@ -432,16 +433,12 @@ public class LwM2mClient { private static Set clientSupportContentFormat(Registration registration) { Set contentFormats = new HashSet<>(); contentFormats.add(ContentFormat.DEFAULT); - LinkParamValue ct = Arrays.stream(registration.getObjectLinks()) + Attribute ct = Arrays.stream(registration.getObjectLinks()) .filter(link -> link.getUriReference().equals("/")) .findFirst() - .map(link -> link.getLinkParams().get("ct")).orElse(null); + .map(link -> link.getAttributes().get("ct")).orElse(null); if (ct != null) { - Set codes = Stream.of(ct.getUnquoted().replaceAll("\"", "").split(" ", -1)) - .map(String::trim) - .map(Integer::parseInt) - .map(ContentFormat::fromCode) - .collect(Collectors.toSet()); + Set codes = Stream.of(ct.getValue()).collect(Collectors.toSet()); contentFormats.addAll(codes); } return contentFormats; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index 09ecf500b6..e124b8142d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -15,12 +15,15 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.LwM2m; -import org.eclipse.leshan.core.attributes.Attribute; -import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeModel; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; @@ -75,8 +78,6 @@ 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 jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; import java.util.Arrays; import java.util.Collection; import java.util.Date; @@ -86,15 +87,16 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import static org.eclipse.leshan.core.attributes.Attribute.GREATER_THAN; -import static org.eclipse.leshan.core.attributes.Attribute.LESSER_THAN; -import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD; -import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; -import static org.eclipse.leshan.core.attributes.Attribute.STEP; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.GREATER_THAN; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.LESSER_THAN; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.MAXIMUM_PERIOD; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.MINIMUM_PERIOD; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.STEP; import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK; import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody; @@ -148,8 +150,9 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, - DownlinkRequestCallback callback, ContentFormat compositeContentFormat) { + DownlinkRequestCallback callback) { try { + ContentFormat compositeContentFormat = this.findFirstContentFormatForComposite(client.getClientSupportContentFormats()); ReadCompositeRequest downlink = new ReadCompositeRequest(compositeContentFormat, compositeContentFormat, request.getObjectIds()); sendCompositeRequest(client, downlink, this.config.getTimeout(), callback); } catch (InvalidRequestException e) { @@ -193,8 +196,8 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } @Override - public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback) { - callback.onSuccess(request, Arrays.asList(client.getRegistration().getSortedObjectLinks())); + public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback) { + callback.onSuccess(request, Arrays.stream(client.getRegistration().getSortedObjectLinks()).map(Link::toCoreLinkFormat).collect(Collectors.toList())); } @Override @@ -279,13 +282,13 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im throw new IllegalArgumentException("Attributes to write are not specified!"); } ObjectAttributes params = request.getAttributes(); - List attributes = new LinkedList<>(); + List> attributes = new LinkedList<>(); addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); addAttribute(attributes, GREATER_THAN, params.getGt()); addAttribute(attributes, LESSER_THAN, params.getLt()); addAttribute(attributes, STEP, params.getSt()); - AttributeSet attributeSet = new AttributeSet(attributes); + LwM2mAttributeSet attributeSet = new LwM2mAttributeSet(attributes); sendSimpleRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); } catch (InvalidRequestException e) { callback.onValidationError(request.toString(), e.getMessage()); @@ -338,9 +341,10 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest, - DownlinkRequestCallback callback, ContentFormat contentFormatComposite) { + DownlinkRequestCallback callback) { try { - WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormatComposite, rpcWriteCompositeRequest.getNodes()); + ContentFormat compositeContentFormat = this.findFirstContentFormatForComposite(client.getClientSupportContentFormats()); + WriteCompositeRequest downlink = new WriteCompositeRequest(compositeContentFormat, rpcWriteCompositeRequest.getNodes()); //TODO: replace config.getTimeout(); sendWriteCompositeRequest(client, downlink, this.config.getTimeout(), callback); } catch (InvalidRequestException e) { @@ -570,17 +574,18 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } - private static void addAttribute(List attributes, String attributeName, T value) { - addAttribute(attributes, attributeName, value, null, null); + private static void addAttribute(List> attributes, LwM2mAttributeModel attribute, T value) { + addAttribute(attributes, attribute, value, null, null); } - private static void addAttribute(List attributes, String attributeName, T value, Function converter) { - addAttribute(attributes, attributeName, value, null, converter); + private static void addAttribute(List> attributes, LwM2mAttributeModel attribute, T value, Function converter) { + addAttribute(attributes, attribute, value, null, converter); } - private static void addAttribute(List attributes, String attributeName, T value, Predicate filter, Function converter) { + private static void addAttribute(List> attributes, LwM2mAttributeModel attributeName, T value, Predicate filter, Function converter) { if (value != null && ((filter == null) || filter.test(value))) { - attributes.add(new Attribute(attributeName, converter != null ? converter.apply(value) : value)); + T valueConvert = (T) converter != null ? (T) converter.apply(value) : value; + attributes.add(new LwM2mAttribute<>(attributeName, valueConvert)); } } @@ -620,7 +625,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } else if (OPAQUE.equals(resourceModel.type)) { return ContentFormat.OPAQUE; } else { - return findFirst(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON); + return findFirstContentFormatForComp(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON); } } else { return getContentFormatForComplex(client); @@ -634,7 +639,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im if (LwM2m.LwM2mVersion.V1_0.equals(client.getRegistration().getLwM2mVersion())) { return ContentFormat.TLV; } else if (LwM2m.LwM2mVersion.V1_1.equals(client.getRegistration().getLwM2mVersion())) { - ContentFormat result = findFirst(client.getClientSupportContentFormats(), null, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON, ContentFormat.TLV, ContentFormat.JSON); + ContentFormat result = findFirstContentFormatForComp(client.getClientSupportContentFormats(), null, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON, ContentFormat.TLV, ContentFormat.JSON); if (result != null) { return result; } else { @@ -644,16 +649,6 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im throw new RuntimeException("The version " + client.getRegistration().getLwM2mVersion() + " is not supported!"); } } - - private static ContentFormat findFirst(Set supported, ContentFormat defaultValue, ContentFormat... desiredFormats) { - for (ContentFormat contentFormat : desiredFormats) { - if (supported.contains(contentFormat)) { - return contentFormat; - } - } - return defaultValue; - } - private String toString(R request) { try { return request != null ? request.toString() : ""; @@ -662,4 +657,29 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im return request.getClass().getSimpleName(); } } + + private ContentFormat findFirstContentFormatForComposite (Set clientSupportContentFormats) { + ContentFormat contentFormat = findFirstContentFormatForComp(clientSupportContentFormats, null, ContentFormat.SENML_JSON, ContentFormat.SENML_CBOR); + if (contentFormat != null) { + return contentFormat; + } else { + throw new RuntimeException("This device does not support Composite Operation"); + } + } + private static ContentFormat findFirstContentFormatForComp(Set clientSupportContentFormats, ContentFormat defaultValue, ContentFormat... desiredFormats) { + AtomicReference compositeContentFormat = new AtomicReference<>(); + clientSupportContentFormats.forEach(c -> { + if (c instanceof Collection) { + for (ContentFormat contentFormat : desiredFormats) { + if (((Collection) c).contains(contentFormat)) { + compositeContentFormat.set(contentFormat); + break; + } + } + } else if (compositeContentFormat.get() == null && c instanceof ContentFormat) { + compositeContentFormat.set(Arrays.stream(desiredFormats).filter(f -> f.equals(c)).findFirst().get()); + } + }); + return compositeContentFormat.get() != null ? compositeContentFormat.get() : defaultValue; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java index 90d4006fb4..a9d49e6dae 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.eclipse.leshan.core.link.Link; -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; @@ -48,7 +46,7 @@ public interface LwM2mDownlinkMsgHandler { void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback); - void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback callback, ContentFormat contentFormatComposite); + void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback callback); void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback); @@ -64,13 +62,13 @@ public interface LwM2mDownlinkMsgHandler { void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback callback); - void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback); + void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback); void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback); void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback); - void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback callback, ContentFormat contentFormatComposite); + void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback callback); void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 5fd20da37d..c725d4f070 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -23,7 +23,6 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; @@ -75,6 +74,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertValueByTypeResource; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; @Slf4j @@ -155,22 +155,16 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } } else if (operationType.isComposite()) { - ContentFormat contentFormatComposite = this.getCompositeContentFormat(client); - if (contentFormatComposite != null) { switch (operationType) { case READ_COMPOSITE: - sendReadCompositeRequest(client, rpcRequest, contentFormatComposite); + sendReadCompositeRequest(client, rpcRequest); break; case WRITE_COMPOSITE: - sendWriteCompositeRequest(client, rpcRequest, contentFormatComposite); + sendWriteCompositeRequest(client, rpcRequest); break; default: throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } - } else { - this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), - ResponseCode.INTERNAL_SERVER_ERROR, "This device does not support Composite Operation"); - } } else { switch (operationType) { case OBSERVE_CANCEL_ALL: @@ -202,12 +196,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { downlinkHandler.sendReadRequest(client, request, rpcCallback); } - private void sendReadCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, ContentFormat contentFormatComposite) { + private void sendReadCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { String[] versionedIds = getIdsFromParameters(client, requestMsg); TbLwM2MReadCompositeRequest request = TbLwM2MReadCompositeRequest.builder().versionedIds(versionedIds).timeout(clientContext.getRequestTimeout(client)).build(); var mainCallback = new TbLwM2MReadCompositeCallback(uplinkHandler, logService, client, versionedIds); var rpcCallback = new RpcReadResponseCompositeCallback(transportService, client, requestMsg, mainCallback); - downlinkHandler.sendReadCompositeRequest(client, request, rpcCallback, contentFormatComposite); + downlinkHandler.sendReadCompositeRequest(client, request, rpcCallback); } private void sendObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { @@ -243,7 +237,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void sendWriteAttributesRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { RpcWriteAttributesRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteAttributesRequest.class); - TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(versionedId) + TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(versionedId) .attributes(requestBody.getAttributes()) .timeout(clientContext.getRequestTimeout(client)).build(); var mainCallback = new TbLwM2MWriteAttributesCallback(logService, client, versionedId); @@ -298,54 +292,89 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { * nodes.put("/1/0/2", 100); * nodes.put("/5/0/1", "coap://localhost:5685"); */ - private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, ContentFormat contentFormatComposite) { + private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { RpcWriteCompositeRequest rpcWriteCompositeRequest = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteCompositeRequest.class); - Map validNodes = validateNodes(client, rpcWriteCompositeRequest.getNodes()); + Map validNodes = validateCompositesNodes(client, rpcWriteCompositeRequest.getNodes()); if (validNodes.size() > 0) { rpcWriteCompositeRequest.setNodes(validNodes); var mainCallback = new TbLwM2MWriteResponseCompositeCallback(uplinkHandler, logService, client, null); var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); - downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback, contentFormatComposite); + downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback); } else { throw new IllegalArgumentException(String.format("nodes: %s is not validate value", rpcWriteCompositeRequest.getNodes().toString())); } } - private Map validateNodes(LwM2mClient client, Map nodes) { + private Map validateCompositesNodes(LwM2mClient client, Map nodes) { Map newNodes = new LinkedHashMap<>(); nodes.forEach((key, value) -> { - String versionedId; + String versionedId = null; + LwM2mPath path = null; try { - LwM2mPath path = new LwM2mPath(fromVersionedIdToObjectId(key)); - if (path.isResource() || path.isResourceInstance()) { - versionedId = key; - } - else { - throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + - "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); - } + path = new LwM2mPath(fromVersionedIdToObjectId(key)); } catch (Exception e) { versionedId = clientContext.getObjectIdByKeyNameFromProfile(client, key); } - // validate value. Must be only primitive, not JsonObject or JsonArray - try { - JsonElement element = JsonUtils.parse(value.toString()); - if (!element.isJsonNull() && !element.isJsonPrimitive()) { + + if (versionedId == null) { + if (path.isResourceInstance()) { + setValueToCompositeNodes(client, newNodes, nodes, key, value.toString()); + } else if (path.isResource()) { + validateResource(client, newNodes, nodes, key , value); + } else if (path.isObjectInstance() && value instanceof Map) { + ((Map) value).forEach((k, v) -> { + validateResource(client, newNodes, nodes, validateResourceId (key, k.toString(), nodes), v); + }); + } else { throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + - "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); - } - else if (versionedId != null) { - newNodes.put(fromVersionedIdToObjectId(versionedId), value); - } - } catch (JsonSyntaxException jse) { - if (versionedId != null) { - newNodes.put(fromVersionedIdToObjectId(versionedId), value); + "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes)); } + } else { + setValueToCompositeNodes(client, newNodes, nodes, versionedId, value.toString()); } }); return newNodes; } + private void validateResource(LwM2mClient client, Map newNodes, Map nodes, String resourceId , Object value) { + if (value instanceof Map) { + ((Map) value).forEach((k, v) -> { + setValueToCompositeNodes(client, newNodes, nodes, validateResourceId (resourceId, k.toString(), nodes), v.toString()); + }); + } else { + setValueToCompositeNodes(client, newNodes, nodes, resourceId, value.toString()); + } + } + + private String validateResourceId (String key, String id, Map nodes) { + try { + Integer.parseInt(id); + return key + "/" + id; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + + "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); + } + } + + private void setValueToCompositeNodes (LwM2mClient client, Map newNodes, Map nodes, String versionedId , String value) { + // validate value. Must be only primitive, not JsonObject or JsonArray + try { + JsonElement element = JsonUtils.parse(value); + if (!element.isJsonNull() && !element.isJsonPrimitive()) { + throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + + "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); + } + // convert value from JsonPrimitive() to resource/ResourceInstance type + ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); + Object newValue = convertValueByTypeResource(value, resourceModel.type, versionedId); + + // add new value after convert + newNodes.put(fromVersionedIdToObjectId(versionedId), newValue); + } catch (JsonSyntaxException jse) { + newNodes.put(fromVersionedIdToObjectId(versionedId), value); + } + } + private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { TbLwM2MCancelObserveRequest downlink = TbLwM2MCancelObserveRequest.builder().versionedId(versionedId).timeout(clientContext.getRequestTimeout(client)).build(); var mainCallback = new TbLwM2MCancelObserveCallback(logService, client, versionedId); @@ -412,16 +441,4 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { log.info("[{}] toServerRpcResponse", toServerResponse); } - - private ContentFormat getCompositeContentFormat(LwM2mClient client) { - if (client.getClientSupportContentFormats().contains(ContentFormat.SENML_JSON)) { - return ContentFormat.SENML_JSON; - } - else if (client.getClientSupportContentFormats().contains(ContentFormat.SENML_CBOR)) { - return ContentFormat.SENML_CBOR; - } - else { - return null; - } - } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java index acb44fe55f..314d88fccb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java @@ -35,7 +35,7 @@ public class RpcDiscoverCallback extends RpcLwM2MDownlinkCallback serializeSuccessfulResponse(DiscoverResponse response) { - return Optional.of(serializer.serialize(response.getObjectLinks())); + return Optional.of(serializer.serializeCoreLinkFormat(response.getObjectLinks())); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java index cc96cea4b7..7709061e32 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.peer.OscoreIdentity; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; @@ -83,25 +84,30 @@ public class TbInMemorySecurityStore implements TbEditableSecurityStore { } } + @Override + public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { + return null; + } + @Override public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { writeLock.lock(); try { String identity = null; if (tbSecurityInfo.getSecurityInfo() != null) { - identity = tbSecurityInfo.getSecurityInfo().getIdentity(); + identity = tbSecurityInfo.getSecurityInfo().getPskIdentity(); if (identity != null) { TbLwM2MSecurityInfo infoByIdentity = securityByIdentity.get(identity); if (infoByIdentity != null && !tbSecurityInfo.getSecurityInfo().getEndpoint().equals(infoByIdentity.getEndpoint())) { throw new NonUniqueSecurityInfoException("PSK Identity " + identity + " is already used"); } - securityByIdentity.put(tbSecurityInfo.getSecurityInfo().getIdentity(), tbSecurityInfo); + securityByIdentity.put(tbSecurityInfo.getSecurityInfo().getPskIdentity(), tbSecurityInfo); } } TbLwM2MSecurityInfo previous = securityByEp.put(tbSecurityInfo.getEndpoint(), tbSecurityInfo); if (previous != null && previous.getSecurityInfo() != null) { - String previousIdentity = previous.getSecurityInfo().getIdentity(); + String previousIdentity = previous.getSecurityInfo().getPskIdentity(); if (previousIdentity != null && !previousIdentity.equals(identity)) { securityByIdentity.remove(previousIdentity); } @@ -116,8 +122,8 @@ public class TbInMemorySecurityStore implements TbEditableSecurityStore { writeLock.lock(); try { TbLwM2MSecurityInfo securityInfo = securityByEp.remove(endpoint); - if (securityInfo != null && securityInfo.getSecurityInfo() != null && securityInfo.getSecurityInfo().getIdentity() != null) { - securityByIdentity.remove(securityInfo.getSecurityInfo().getIdentity()); + if (securityInfo != null && securityInfo.getSecurityInfo() != null && securityInfo.getSecurityInfo().getPskIdentity() != null) { + securityByIdentity.remove(securityInfo.getSecurityInfo().getPskIdentity()); } } finally { writeLock.unlock(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index 39c19c687b..40a281728b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -16,6 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.eclipse.californium.core.coap.Token; +import org.eclipse.californium.core.network.serialization.UdpDataParser; +import org.eclipse.californium.core.network.serialization.UdpDataSerializer; import org.eclipse.californium.core.observe.ObservationStoreException; import org.eclipse.californium.elements.EndpointContext; import org.eclipse.leshan.core.Destroyable; @@ -23,18 +25,18 @@ import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; import org.eclipse.leshan.core.californium.ObserveUtil; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; -import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.peer.LwM2mIdentity; import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.Validate; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; +import org.eclipse.leshan.server.californium.observation.ObservationSerDes; import org.eclipse.leshan.server.redis.RedisRegistrationStore; -import org.eclipse.leshan.server.redis.serialization.IdentitySerDes; -import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.registration.Deregistration; import org.eclipse.leshan.server.registration.ExpirationListener; import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.UpdatedRegistration; import org.slf4j.Logger; @@ -63,7 +65,7 @@ import java.util.concurrent.locks.Lock; import static java.nio.charset.StandardCharsets.UTF_8; -public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationStore, Startable, Stoppable, Destroyable { +public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { /** Default time in seconds between 2 cleaning tasks (used to remove expired registration). */ public static final long DEFAULT_CLEAN_PERIOD = 60; public static final int DEFAULT_CLEAN_LIMIT = 500; @@ -83,6 +85,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto private static final byte[] EXP_EP = "EXP:EP".getBytes(UTF_8); // a sorted set used for registration expiration // (expiration date, Endpoint) + private final RegistrationSerDes registrationSerDes = new RegistrationSerDes(); + private final ObservationSerDes observationSerDes = new ObservationSerDes(new UdpDataParser(), new UdpDataSerializer()); private final RedisConnectionFactory connectionFactory; // Listener use to notify when a registration expires @@ -159,7 +163,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto connection.set(regid_idx, registration.getEndpoint().getBytes(UTF_8)); byte[] addr_idx = toRegAddrKey(registration.getSocketAddress()); connection.set(addr_idx, registration.getEndpoint().getBytes(UTF_8)); - byte[] identity_idx = toRegIdentityKey(registration.getIdentity()); + byte[] identity_idx = toRegIdentityKey(registration.getClientTransportData().getIdentity()); connection.set(identity_idx, registration.getEndpoint().getBytes(UTF_8)); // Add or update expiration @@ -173,7 +177,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (!oldRegistration.getSocketAddress().equals(registration.getSocketAddress())) { removeAddrIndex(connection, oldRegistration); } - if (!oldRegistration.getIdentity().equals(registration.getIdentity())) { + if (!oldRegistration.getClientTransportData().getIdentity().equals(registration.getClientTransportData().getIdentity())) { removeIdentityIndex(connection, oldRegistration); } // remove old observation @@ -231,7 +235,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (!r.getSocketAddress().equals(updatedRegistration.getSocketAddress())) { removeAddrIndex(connection, r); } - if (!r.getIdentity().equals(updatedRegistration.getIdentity())) { + if (!r.getClientTransportData().getIdentity().equals(updatedRegistration.getClientTransportData().getIdentity())) { removeIdentityIndex(connection, r); } @@ -281,7 +285,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } @Override - public Registration getRegistrationByIdentity(Identity identity) { + public Registration getRegistrationByIdentity(LwM2mIdentity identity) { Validate.notNull(identity); try (var connection = connectionFactory.getConnection()) { byte[] ep = connection.get(toRegIdentityKey(identity)); @@ -327,6 +331,26 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } } + @Override + public Collection addObservation(String registrationId, Observation observation, boolean addIfAbsent) { + return null; + } + + @Override + public Observation getObservation(String registrationId, ObservationIdentifier observationId) { + return null; + } + + @Override + public Observation getObservation(ObservationIdentifier observationId) { + return null; + } + + @Override + public Observation removeObservation(String registrationId, ObservationIdentifier observationId) { + return null; + } + private Deregistration removeRegistration(RedisConnection connection, String registrationId, boolean removeOnlyIfNotAlive) { // fetch the client ep by registration ID index byte[] ep = connection.get(toRegIdKey(registrationId)); @@ -371,7 +395,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } private void removeIdentityIndex(RedisConnection connection, Registration r) { - removeSecondaryIndex(connection, toRegIdentityKey(r.getIdentity()), r.getEndpoint()); + removeSecondaryIndex(connection, toRegIdentityKey(r.getClientTransportData().getIdentity()), r.getEndpoint()); } //TODO: JedisCluster didn't implement Transaction, maybe should use some advanced key creation strategies @@ -410,8 +434,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return toKey(REG_EP_ADDR_IDX, addr.getAddress().toString() + ":" + addr.getPort()); } - private byte[] toRegIdentityKey(Identity identity) { - return toKey(REG_EP_IDENTITY, IdentitySerDes.serialize(identity).toString()); + private byte[] toRegIdentityKey(LwM2mIdentity identity) { + return toKey(REG_EP_IDENTITY, identity.toString()); } private byte[] toEndpointKey(String endpoint) { @@ -423,11 +447,11 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } private byte[] serializeReg(Registration registration) { - return RegistrationSerDes.bSerialize(registration); + return registrationSerDes.bSerialize(registration); } private Registration deserializeReg(byte[] data) { - return RegistrationSerDes.deserialize(data); + return registrationSerDes.deserialize(data); } /* *************** Leshan Observation API **************** */ @@ -436,7 +460,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto * The observation is not persisted here, it is done by the Californium layer (in the implementation of the * org.eclipse.californium.core.observe.ObservationStore#add method) */ - @Override + public Collection addObservation(String registrationId, Observation observation) { List removed = new ArrayList<>(); try (var connection = connectionFactory.getConnection()) { @@ -458,9 +482,9 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto for (Observation obs : getObservations(connection, registrationId)) { //TODO: should be able to use CompositeObservation if (((SingleObservation)observation).getPath().equals(((SingleObservation)obs).getPath()) - && !Arrays.equals(observation.getId(), obs.getId())) { + && !observation.getId().equals(obs.getId())) { removed.add(obs); - unsafeRemoveObservation(connection, registrationId, obs.getId()); + unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes()); } } @@ -473,7 +497,6 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return removed; } - @Override public Observation removeObservation(String registrationId, byte[] observationId) { try (var connection = connectionFactory.getConnection()) { @@ -490,7 +513,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto lock = redisLock.obtain(lockKey); lock.lock(); - Observation observation = build(get(new Token(observationId))); +// Observation observation = build(get(new Token(observationId))); + Observation observation = build(null); if (observation != null && registrationId.equals(observation.getRegistrationId())) { unsafeRemoveObservation(connection, registrationId, observationId); return observation; @@ -505,9 +529,9 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } } - @Override public Observation getObservation(String registrationId, byte[] observationId) { - return build(get(new Token(observationId))); +// return build(get(new Token(observationId))); + return build(null); } @Override @@ -554,13 +578,11 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto /* *************** Californium ObservationStore API **************** */ - @Override public org.eclipse.californium.core.observe.Observation putIfAbsent(Token token, org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException { return add(obs, true); } - @Override public org.eclipse.californium.core.observe.Observation put(Token token, org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException { return add(obs, false); @@ -588,7 +610,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (previousValue == null || previousValue.length == 0) { connection.set(key, serializeObs); } else { - return deserializeObs(previousValue); +// return deserializeObs(previousValue); + return null; } } else { previousValue = connection.getSet(key, serializeObs); @@ -599,7 +622,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto // log any collisions if (previousValue != null && previousValue.length != 0) { - previousObservation = deserializeObs(previousValue); +// previousObservation = deserializeObs(previousValue); + previousObservation = null; LOG.warn( "Token collision ? observation from request [{}] will be replaced by observation from request [{}] ", previousObservation.getRequest(), obs.getRequest()); @@ -613,7 +637,6 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return previousObservation; } - @Override public void remove(Token token) { try (var connection = connectionFactory.getConnection()) { byte[] tokenKey = toKey(OBS_TKN, token.getBytes()); @@ -623,7 +646,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (serializedObs == null) return; - org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs); +// org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs); + org.eclipse.californium.core.observe.Observation obs = null; String registrationId = ObserveUtil.extractRegistrationId(obs); Registration registration = getRegistration(connection, registrationId); if (registration == null) { @@ -649,14 +673,14 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } - @Override public org.eclipse.californium.core.observe.Observation get(Token token) { try (var connection = connectionFactory.getConnection()) { byte[] obs = connection.get(toKey(OBS_TKN, token.getBytes())); if (obs == null) { return null; } else { - return deserializeObs(obs); +// return deserializeObs(obs); + return null; } } } @@ -699,24 +723,26 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return removed; } - @Override public void setContext(Token token, EndpointContext correlationContext) { // In Leshan we always set context when we send the request, so this should not be needed to implement this. } private byte[] serializeObs(org.eclipse.californium.core.observe.Observation obs) { - return ObservationSerDes.serialize(obs); + return null; +// return observationSerDes.serialize(obs); } - private org.eclipse.californium.core.observe.Observation deserializeObs(byte[] data) { - return ObservationSerDes.deserialize(data); + private Observation deserializeObs(byte[] data) { +// return observationSerDes.deserialize(data); + return null; } - private Observation build(org.eclipse.californium.core.observe.Observation cfObs) { + private Observation build(Observation cfObs) { if (cfObs == null) return null; - - return ObserveUtil.createLwM2mObservation(cfObs.getRequest()); +// String serializedObservation = observationSerDes.serialize(cfObs); +// return ObserveUtil.createLwM2mObservation(cfObs, serializedObservation); + return null; } /* *************** Expiration handling **************** */ @@ -787,7 +813,6 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto expirationListener = listener; } - @Override public void setExecutor(ScheduledExecutorService executor) { // TODO should we reuse californium executor ? } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java index 87e92c8cca..daadc893bf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.peer.OscoreIdentity; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.data.redis.connection.RedisConnectionFactory; @@ -86,6 +87,11 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { } } + @Override + public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { + return null; + } + @Override public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { SecurityInfo info = tbSecurityInfo.getSecurityInfo(); @@ -94,21 +100,21 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { try (var connection = connectionFactory.getConnection()) { lock = redisLock.obtain(tbSecurityInfo.getEndpoint()); lock.lock(); - if (info != null && info.getIdentity() != null) { - byte[] oldEndpointBytes = connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); + if (info != null && info.getPskIdentity() != null) { + byte[] oldEndpointBytes = connection.hGet(PSKID_SEC.getBytes(), info.getPskIdentity().getBytes()); if (oldEndpointBytes != null) { String oldEndpoint = new String(oldEndpointBytes); if (!oldEndpoint.equals(info.getEndpoint())) { - throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); + throw new NonUniqueSecurityInfoException("PSK Identity " + info.getPskIdentity() + " is already used"); } - connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); + connection.hSet(PSKID_SEC.getBytes(), info.getPskIdentity().getBytes(), info.getEndpoint().getBytes()); } } byte[] previousData = connection.getSet((SEC_EP + tbSecurityInfo.getEndpoint()).getBytes(), tbSecurityInfoSerialized); if (previousData != null && info != null) { - String previousIdentity = ((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(previousData)).getSecurityInfo().getIdentity(); - if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { + String previousIdentity = ((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(previousData)).getSecurityInfo().getPskIdentity(); + if (previousIdentity != null && !previousIdentity.equals(info.getPskIdentity())) { connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes()); } } @@ -147,8 +153,8 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { byte[] data = connection.get((SEC_EP + endpoint).getBytes()); if (data != null && data.length > 0) { SecurityInfo info = ((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(data)).getSecurityInfo(); - if (info != null && info.getIdentity() != null) { - connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); + if (info != null && info.getPskIdentity() != null) { + connection.hDel(PSKID_SEC.getBytes(), info.getPskIdentity().getBytes()); } connection.del((SEC_EP + endpoint).getBytes()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java index 5d7e339584..0cdc75f17e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.store; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.peer.OscoreIdentity; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; @@ -60,7 +61,7 @@ public class TbLwM2mSecurityStore implements TbMainSecurityStore { if (securityInfo == null) { securityInfo = fetchAndPutSecurityInfo(endpoint); } else if (securityInfo.usePSK() && securityInfo.getEndpoint().equals(SecurityMode.NO_SEC.toString()) - && securityInfo.getIdentity().equals(SecurityMode.NO_SEC.toString()) + && securityInfo.getPskIdentity().equals(SecurityMode.NO_SEC.toString()) && Arrays.equals(SecurityMode.NO_SEC.toString().getBytes(), securityInfo.getPreSharedKey())) { return null; } @@ -81,6 +82,11 @@ public class TbLwM2mSecurityStore implements TbMainSecurityStore { return securityInfo; } + @Override + public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { + return null; + } + public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) { TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, CLIENT); doPut(securityInfo); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java index f74630fffc..9aaebaa8bc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java @@ -16,8 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server.store; import lombok.RequiredArgsConstructor; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; -import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore; +import org.eclipse.leshan.server.registration.InMemoryRegistrationStore; +import org.eclipse.leshan.server.registration.RegistrationStore; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Component; @@ -38,7 +38,7 @@ public class TbLwM2mStoreFactory { private final LwM2mCredentialsSecurityInfoValidator validator; @Bean - private CaliforniumRegistrationStore registrationStore() { + private RegistrationStore registrationStore() { return redisConfiguration.isPresent() ? new TbLwM2mRedisRegistrationStore(getConnectionFactory()) : new InMemoryRegistrationStore(config.getCleanPeriodInSec()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java index 88663bae71..1309e9ae2c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java @@ -15,9 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.server.store.util; -import com.eclipsesource.json.Json; -import com.eclipsesource.json.JsonObject; -import com.eclipsesource.json.JsonValue; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.gson.JsonObject; import com.google.protobuf.util.JsonFormat; import lombok.SneakyThrows; import org.eclipse.leshan.core.model.ResourceModel; @@ -26,113 +25,107 @@ import org.eclipse.leshan.core.node.LwM2mNodeException; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.ObjectLink; -import org.eclipse.leshan.core.util.datatype.ULong; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; -import org.thingsboard.server.common.data.device.data.PowerMode; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; import java.lang.reflect.Field; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicLong; public class LwM2MClientSerDes { public static final String VALUE = "value"; @SneakyThrows public static byte[] serialize(LwM2mClient client) { - JsonObject o = Json.object(); - o.add("nodeId", client.getNodeId()); - o.add("endpoint", client.getEndpoint()); + JsonObject o = new JsonObject(); + o.addProperty("nodeId", "client.getNodeId()"); + o.addProperty("endpoint", client.getEndpoint()); - JsonObject resources = Json.object(); + JsonObject resources = new JsonObject(); client.getResources().forEach((k, v) -> { - JsonObject resourceValue = Json.object(); + JsonObject resourceValue = new JsonObject(); resourceValue.add("lwM2mResource", serialize(v.getLwM2mResource())); resourceValue.add("resourceModel", serialize(v.getResourceModel())); resources.add(k, resourceValue); }); o.add("resources", resources); - JsonObject sharedAttributes = Json.object(); + JsonObject sharedAttributes = new JsonObject(); for (Map.Entry entry : client.getSharedAttributes().entrySet()) { - sharedAttributes.add(entry.getKey(), JsonFormat.printer().print(entry.getValue())); + sharedAttributes.addProperty(entry.getKey(), JsonFormat.printer().print(entry.getValue())); } o.add("sharedAttributes", sharedAttributes); - JsonObject keyTsLatestMap = Json.object(); + JsonObject keyTsLatestMap = new JsonObject(); client.getKeyTsLatestMap().forEach((k, v) -> { - keyTsLatestMap.add(k, v.get()); + keyTsLatestMap.addProperty(k, v.get()); }); o.add("keyTsLatestMap", keyTsLatestMap); - o.add("state", client.getState().toString()); + o.addProperty("state", client.getState().toString()); if (client.getSession() != null) { - o.add("session", JsonFormat.printer().print(client.getSession())); + o.addProperty("session", JsonFormat.printer().print(client.getSession())); } if (client.getTenantId() != null) { - o.add("tenantId", client.getTenantId().toString()); + o.addProperty("tenantId", client.getTenantId().toString()); } if (client.getDeviceId() != null) { - o.add("deviceId", client.getDeviceId().toString()); + o.addProperty("deviceId", client.getDeviceId().toString()); } if (client.getProfileId() != null) { - o.add("profileId", client.getProfileId().toString()); + o.addProperty("profileId", client.getProfileId().toString()); } if (client.getPowerMode() != null) { - o.add("powerMode", client.getPowerMode().toString()); + o.addProperty("powerMode", client.getPowerMode().toString()); } if (client.getEdrxCycle() != null) { - o.add("edrxCycle", client.getEdrxCycle()); + o.addProperty("edrxCycle", client.getEdrxCycle()); } if (client.getPsmActivityTimer() != null) { - o.add("psmActivityTimer", client.getPsmActivityTimer()); + o.addProperty("psmActivityTimer", client.getPsmActivityTimer()); } if (client.getPagingTransmissionWindow() != null) { - o.add("pagingTransmissionWindow", client.getPagingTransmissionWindow()); + o.addProperty("pagingTransmissionWindow", client.getPagingTransmissionWindow()); } if (client.getRegistration() != null) { - o.add("registration", RegistrationSerDes.jSerialize(client.getRegistration())); + RegistrationSerDes regDez = new RegistrationSerDes(); + o.addProperty("registration", regDez.jSerialize(client.getRegistration()).toString()); } - o.add("asleep", client.isAsleep()); - o.add("lastUplinkTime", client.getLastUplinkTime()); + o.addProperty("asleep", client.isAsleep()); + o.addProperty("lastUplinkTime", client.getLastUplinkTime()); Field firstEdrxDownlink = LwM2mClient.class.getDeclaredField("firstEdrxDownlink"); firstEdrxDownlink.setAccessible(true); - o.add("firstEdrxDownlink", (boolean) firstEdrxDownlink.get(client)); - o.add("retryAttempts", client.getRetryAttempts().get()); + o.addProperty("firstEdrxDownlink", (boolean) firstEdrxDownlink.get(client)); + o.addProperty("retryAttempts", client.getRetryAttempts().get()); if (client.getLastSentRpcId() != null) { - o.add("lastSentRpcId", client.getLastSentRpcId().toString()); + o.addProperty("lastSentRpcId", client.getLastSentRpcId().toString()); } return o.toString().getBytes(); } private static JsonObject serialize(LwM2mResource resource) { - JsonObject o = Json.object(); - o.add("id", resource.getId()); - o.add("type", resource.getType().toString()); + JsonObject o = new JsonObject(); + o.addProperty("id", resource.getId()); + o.addProperty("type", resource.getType().toString()); if (resource.isMultiInstances()) { - o.add("multiInstances", true); - JsonObject instances = Json.object(); + o.addProperty("multiInstances", true); + JsonObject instances = new JsonObject(); resource.getInstances().forEach((id, in) -> { - JsonObject instance = Json.object(); - instance.add("id", in.getId()); + JsonObject instance = new JsonObject(); + instance.addProperty("id", in.getId()); addValue(instance, in.getType(), in.getValue()); instances.add(id.toString(), instance); }); o.add("instances", instances); } else { - o.add("multiInstances", false); + o.addProperty("multiInstances", false); addValue(o, resource.getType(), resource.getValue()); } @@ -140,95 +133,95 @@ public class LwM2MClientSerDes { } private static LwM2mResource parseLwM2mResource(JsonObject o) { - boolean multiInstances = o.get("multiInstances").asBoolean(); - int id = o.get("id").asInt(); - ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").asString()); + boolean multiInstances = o.get("multiInstances").getAsBoolean(); + int id = o.get("id").getAsInt(); + ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").getAsString()); if (multiInstances) { Map instances = new HashMap<>(); - o.get("instances").asObject().forEach(entry -> { - instances.put(Integer.valueOf(entry.getName()), parseValue(type, entry.getValue())); + o.get("instances").getAsJsonArray().forEach(entry -> { +// instances.put(Integer.valueOf(entry.getAsJsonObject().), parseValue(type, entry.getValue())); }); return LwM2mMultipleResource.newResource(id, instances, type); } else { - return LwM2mSingleResource.newResource(id, parseValue(type, o.get(VALUE))); + return LwM2mSingleResource.newResource(id, parseValue(type, (JsonValue) o.get(VALUE))); } } private static Object parseValue(ResourceModel.Type type, JsonValue value) { switch (type) { case INTEGER: - return value.asLong(); - case FLOAT: - return value.asDouble(); - case BOOLEAN: - return value.asBoolean(); - case OPAQUE: - return Base64.getDecoder().decode(value.asString()); - case STRING: - return value.asString(); - case TIME: - return new Date(value.asLong()); - case OBJLNK: - return ObjectLink.decodeFromString(value.asString()); - case UNSIGNED_INTEGER: - return ULong.valueOf(value.asString()); + return value.value(); +// case FLOAT: +// return value.value(); +// case BOOLEAN: +// return value.asBoolean(); +// case OPAQUE: +// return Base64.getDecoder().decode(value.asString()); +// case STRING: +// return value.asString(); +// case TIME: +// return new Date(value.asLong()); +// case OBJLNK: +// return ObjectLink.decodeFromString(value.asString()); +// case UNSIGNED_INTEGER: +// return ULong.valueOf(value.asString()); default: throw new LwM2mNodeException(String.format("Type %s is not supported", type.name())); } } private static JsonObject serialize(ResourceModel resourceModel) { - JsonObject o = Json.object(); - o.add("id", resourceModel.id); - o.add("name", resourceModel.name); - o.add("operations", resourceModel.operations.toString()); - o.add("multiple", resourceModel.multiple); - o.add("mandatory", resourceModel.mandatory); - o.add("type", resourceModel.type.toString()); - o.add("rangeEnumeration", resourceModel.rangeEnumeration); - o.add("units", resourceModel.units); - o.add("description", resourceModel.description); + JsonObject o = new JsonObject(); + o.addProperty("id", resourceModel.id); + o.addProperty("name", resourceModel.name); + o.addProperty("operations", resourceModel.operations.toString()); + o.addProperty("multiple", resourceModel.multiple); + o.addProperty("mandatory", resourceModel.mandatory); + o.addProperty("type", resourceModel.type.toString()); + o.addProperty("rangeEnumeration", resourceModel.rangeEnumeration); + o.addProperty("units", resourceModel.units); + o.addProperty("description", resourceModel.description); return o; } private static ResourceModel parseResourceModel(JsonObject o) { - Integer id = o.get("id").asInt(); - String name = o.get("name").asString(); - ResourceModel.Operations operations = ResourceModel.Operations.valueOf(o.get("operations").asString()); - Boolean multiple = o.get("multiple").asBoolean(); - Boolean mandatory = o.get("mandatory").asBoolean(); - ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").asString()); - String rangeEnumeration = o.get("rangeEnumeration").asString(); - String units = o.get("units").asString(); - String description = o.get("description").asString(); + Integer id = o.get("id").getAsInt(); + String name = o.get("name").getAsString(); + ResourceModel.Operations operations = ResourceModel.Operations.valueOf(o.get("operations").getAsString()); + Boolean multiple = o.get("multiple").getAsBoolean(); + Boolean mandatory = o.get("mandatory").getAsBoolean(); + ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").getAsString()); + String rangeEnumeration = o.get("rangeEnumeration").getAsString(); + String units = o.get("units").getAsString(); + String description = o.get("description").getAsString(); return new ResourceModel(id, name, operations, multiple, mandatory, type, rangeEnumeration, units, description); } private static void addValue(JsonObject o, ResourceModel.Type type, Object value) { switch (type) { case INTEGER: - o.add(VALUE, (Long) value); + o.addProperty(VALUE, (Long) value); break; case FLOAT: - o.add(VALUE, (Double) value); + o.addProperty(VALUE, (Double) value); break; case BOOLEAN: - o.add(VALUE, (Boolean) value); + o.addProperty(VALUE, (Boolean) value); break; case OPAQUE: - o.add(VALUE, Base64.getEncoder().encodeToString((byte[]) value)); + o.addProperty(VALUE, Base64.getEncoder().encodeToString((byte[]) value)); break; case STRING: - o.add(VALUE, (String) value); + o.addProperty(VALUE, (String) value); break; case TIME: - o.add(VALUE, ((Date) value).getTime()); + o.addProperty(VALUE, ((Date) value).getTime()); break; case OBJLNK: - o.add(VALUE, ((ObjectLink) value).encodeToString()); + o.addProperty(VALUE, ((ObjectLink) value).encodeToString()); break; case UNSIGNED_INTEGER: - o.add(VALUE, value.toString()); + o.addProperty(VALUE, value.toString()); break; default: throw new LwM2mNodeException(String.format("Type %s is not supported", type.name())); @@ -237,111 +230,111 @@ public class LwM2MClientSerDes { @SneakyThrows public static LwM2mClient deserialize(byte[] data) { - JsonObject o = Json.parse(new String(data)).asObject(); - LwM2mClient lwM2mClient = new LwM2mClient(o.get("nodeId").asString(), o.get("endpoint").asString()); - - o.get("resources").asObject().forEach(entry -> { - JsonObject resource = entry.getValue().asObject(); - LwM2mResource lwM2mResource = parseLwM2mResource(resource.get("lwM2mResource").asObject()); - ResourceModel resourceModel = parseResourceModel(resource.get("resourceModel").asObject()); - ResourceValue resourceValue = new ResourceValue(lwM2mResource, resourceModel); - lwM2mClient.getResources().put(entry.getName(), resourceValue); - }); - - for (JsonObject.Member entry : o.get("sharedAttributes").asObject()) { - TransportProtos.TsKvProto.Builder builder = TransportProtos.TsKvProto.newBuilder(); - JsonFormat.parser().merge(entry.getValue().asString(), builder); - lwM2mClient.getSharedAttributes().put(entry.getName(), builder.build()); - } - - o.get("keyTsLatestMap").asObject().forEach(entry -> { - lwM2mClient.getKeyTsLatestMap().put(entry.getName(), new AtomicLong(entry.getValue().asLong())); - }); - - lwM2mClient.setState(LwM2MClientState.valueOf(o.get("state").asString())); - - Class lwM2mClientClass = LwM2mClient.class; - - JsonValue session = o.get("session"); - if (session != null) { - TransportProtos.SessionInfoProto.Builder builder = TransportProtos.SessionInfoProto.newBuilder(); - JsonFormat.parser().merge(session.asString(), builder); - - Field sessionField = lwM2mClientClass.getDeclaredField("session"); - sessionField.setAccessible(true); - sessionField.set(lwM2mClient, builder.build()); - } - - JsonValue tenantId = o.get("tenantId"); - if (tenantId != null) { - Field tenantIdField = lwM2mClientClass.getDeclaredField("tenantId"); - tenantIdField.setAccessible(true); - tenantIdField.set(lwM2mClient, new TenantId(UUID.fromString(tenantId.asString()))); - } - - JsonValue deviceId = o.get("deviceId"); - if (tenantId != null) { - Field deviceIdField = lwM2mClientClass.getDeclaredField("deviceId"); - deviceIdField.setAccessible(true); - deviceIdField.set(lwM2mClient, UUID.fromString(deviceId.asString())); - } - - JsonValue profileId = o.get("profileId"); - if (tenantId != null) { - Field profileIdField = lwM2mClientClass.getDeclaredField("profileId"); - profileIdField.setAccessible(true); - profileIdField.set(lwM2mClient, UUID.fromString(profileId.asString())); - } - - JsonValue powerMode = o.get("powerMode"); - if (powerMode != null) { - Field powerModeField = lwM2mClientClass.getDeclaredField("powerMode"); - powerModeField.setAccessible(true); - powerModeField.set(lwM2mClient, PowerMode.valueOf(powerMode.asString())); - } - - JsonValue edrxCycle = o.get("edrxCycle"); - if (edrxCycle != null) { - Field edrxCycleField = lwM2mClientClass.getDeclaredField("edrxCycle"); - edrxCycleField.setAccessible(true); - edrxCycleField.set(lwM2mClient, edrxCycle.asLong()); - } - - JsonValue psmActivityTimer = o.get("psmActivityTimer"); - if (psmActivityTimer != null) { - Field psmActivityTimerField = lwM2mClientClass.getDeclaredField("psmActivityTimer"); - psmActivityTimerField.setAccessible(true); - psmActivityTimerField.set(lwM2mClient, psmActivityTimer.asLong()); - } - - JsonValue pagingTransmissionWindow = o.get("pagingTransmissionWindow"); - if (pagingTransmissionWindow != null) { - Field pagingTransmissionWindowField = lwM2mClientClass.getDeclaredField("pagingTransmissionWindow"); - pagingTransmissionWindowField.setAccessible(true); - pagingTransmissionWindowField.set(lwM2mClient, pagingTransmissionWindow.asLong()); - } - - JsonValue registration = o.get("registration"); - if (registration != null) { - lwM2mClient.setRegistration(RegistrationSerDes.deserialize(registration.asObject())); - } - - lwM2mClient.setAsleep(o.get("asleep").asBoolean()); - - Field lastUplinkTimeField = lwM2mClientClass.getDeclaredField("lastUplinkTime"); - lastUplinkTimeField.setAccessible(true); - lastUplinkTimeField.setLong(lwM2mClient, o.get("lastUplinkTime").asLong()); - - Field firstEdrxDownlinkField = lwM2mClientClass.getDeclaredField("firstEdrxDownlink"); - firstEdrxDownlinkField.setAccessible(true); - firstEdrxDownlinkField.setBoolean(lwM2mClient, o.get("firstEdrxDownlink").asBoolean()); - - lwM2mClient.getRetryAttempts().set(o.get("retryAttempts").asInt()); - - JsonValue lastSentRpcId = o.get("lastSentRpcId"); - if (lastSentRpcId != null) { - lwM2mClient.setLastSentRpcId(UUID.fromString(lastSentRpcId.asString())); - } +// JsonObject o = new JsonObject(new String(data))); +// LwM2mClient lwM2mClient = new LwM2mClient(o.get("nodeId").getAsString(), o.get("endpoint").getAsString()); + LwM2mClient lwM2mClient = new LwM2mClient("nodeId", "endpoint"); +// o.get("resources").getAsJsonObject().forEach(entry -> { +// JsonObject resource = entry.getValue().asObject(); +// LwM2mResource lwM2mResource = parseLwM2mResource(resource.get("lwM2mResource").getAsJsonObject()); +// ResourceModel resourceModel = parseResourceModel(resource.get("resourceModel").asObject()); +// ResourceValue resourceValue = new ResourceValue(lwM2mResource, resourceModel); +// lwM2mClient.getResources().put(entry.getName(), resourceValue); +// }); +// +// for (JsonObject.Member entry : o.get("sharedAttributes").asObject()) { +// TransportProtos.TsKvProto.Builder builder = TransportProtos.TsKvProto.newBuilder(); +// JsonFormat.parser().merge(entry.getValue().getAsString(), builder); +// lwM2mClient.getSharedAttributes().put(entry.getName(), builder.build()); +// } +// +// o.get("keyTsLatestMap").asObject().forEach(entry -> { +// lwM2mClient.getKeyTsLatestMap().put(entry.getName(), new AtomicLong(entry.getValue().asLong())); +// }); +// +// lwM2mClient.setState(LwM2MClientState.valueOf(o.get("state").getAsString())); +// +// Class lwM2mClientClass = LwM2mClient.class; +// +// JsonValue session = o.get("session"); +// if (session != null) { +// TransportProtos.SessionInfoProto.Builder builder = TransportProtos.SessionInfoProto.newBuilder(); +// JsonFormat.parser().merge(session.asString(), builder); +// +// Field sessionField = lwM2mClientClass.getDeclaredField("session"); +// sessionField.setAccessible(true); +// sessionField.set(lwM2mClient, builder.build()); +// } +// +// JsonValue tenantId = o.get("tenantId"); +// if (tenantId != null) { +// Field tenantIdField = lwM2mClientClass.getDeclaredField("tenantId"); +// tenantIdField.setAccessible(true); +// tenantIdField.set(lwM2mClient, new TenantId(UUID.fromString(tenantId.asString()))); +// } +// +// JsonValue deviceId = o.get("deviceId"); +// if (tenantId != null) { +// Field deviceIdField = lwM2mClientClass.getDeclaredField("deviceId"); +// deviceIdField.setAccessible(true); +// deviceIdField.set(lwM2mClient, UUID.fromString(deviceId.asString())); +// } +// +// JsonValue profileId = o.get("profileId"); +// if (tenantId != null) { +// Field profileIdField = lwM2mClientClass.getDeclaredField("profileId"); +// profileIdField.setAccessible(true); +// profileIdField.set(lwM2mClient, UUID.fromString(profileId.asString())); +// } +// +// JsonValue powerMode = o.get("powerMode"); +// if (powerMode != null) { +// Field powerModeField = lwM2mClientClass.getDeclaredField("powerMode"); +// powerModeField.setAccessible(true); +// powerModeField.set(lwM2mClient, PowerMode.valueOf(powerMode.asString())); +// } +// +// JsonValue edrxCycle = o.get("edrxCycle"); +// if (edrxCycle != null) { +// Field edrxCycleField = lwM2mClientClass.getDeclaredField("edrxCycle"); +// edrxCycleField.setAccessible(true); +// edrxCycleField.set(lwM2mClient, edrxCycle.asLong()); +// } +// +// JsonValue psmActivityTimer = o.get("psmActivityTimer"); +// if (psmActivityTimer != null) { +// Field psmActivityTimerField = lwM2mClientClass.getDeclaredField("psmActivityTimer"); +// psmActivityTimerField.setAccessible(true); +// psmActivityTimerField.set(lwM2mClient, psmActivityTimer.asLong()); +// } +// +// JsonValue pagingTransmissionWindow = o.get("pagingTransmissionWindow"); +// if (pagingTransmissionWindow != null) { +// Field pagingTransmissionWindowField = lwM2mClientClass.getDeclaredField("pagingTransmissionWindow"); +// pagingTransmissionWindowField.setAccessible(true); +// pagingTransmissionWindowField.set(lwM2mClient, pagingTransmissionWindow.asLong()); +// } +// +// JsonValue registration = o.get("registration"); +// if (registration != null) { +// lwM2mClient.setRegistration(RegistrationSerDes.deserialize(registration.asObject())); +// } +// +// lwM2mClient.setAsleep(o.get("asleep").getAsBoolean()); +// +// Field lastUplinkTimeField = lwM2mClientClass.getDeclaredField("lastUplinkTime"); +// lastUplinkTimeField.setAccessible(true); +// lastUplinkTimeField.setLong(lwM2mClient, o.get("lastUplinkTime").asLong()); +// +// Field firstEdrxDownlinkField = lwM2mClientClass.getDeclaredField("firstEdrxDownlink"); +// firstEdrxDownlinkField.setAccessible(true); +// firstEdrxDownlinkField.setBoolean(lwM2mClient, o.get("firstEdrxDownlink").getAsBoolean()); +// +// lwM2mClient.getRetryAttempts().set(o.get("retryAttempts").asInt()); +// +// JsonValue lastSentRpcId = o.get("lastSentRpcId"); +// if (lastSentRpcId != null) { +// lwM2mClient.setLastSentRpcId(UUID.fromString(lastSentRpcId.asString())); +// } return lwM2mClient; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index 4369f5f532..84e1bfa53d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -356,7 +356,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl */ @Override public void onUpdateValueWithSendRequest(Registration registration, SendRequest sendRequest) { - for(var entry : sendRequest.getNodes().entrySet()) { + for(var entry : sendRequest.getTimestampedNodes().getNodes().entrySet()) { LwM2mPath path = entry.getKey(); LwM2mNode node = entry.getValue(); LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); @@ -777,7 +777,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl this.updateResourcesValue(client, (LwM2mResource) v, k.toString(), Mode.REPLACE, code); } else { LwM2mResourceInstance resourceInstance = (LwM2mResourceInstance) v; - LwM2mMultipleResource multipleResource = new LwM2mMultipleResource(v.getId(), resourceInstance.getType(), resourceInstance); + LwM2mMultipleResource multipleResource = new LwM2mMultipleResource(((LwM2mResourceInstance) v).getId(), resourceInstance.getType(), resourceInstance); this.updateResourcesValue(client, multipleResource, k.toString(), Mode.REPLACE, code); } }); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index 527a84df22..201d41ce61 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -15,11 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.utils; -import com.fasterxml.jackson.core.type.TypeReference; import com.google.gson.JsonElement; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.attributes.Attribute; -import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; @@ -30,8 +27,6 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.codec.CodecException; -import org.eclipse.leshan.core.request.SimpleDownlinkRequest; -import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.common.util.JacksonUtil; @@ -52,9 +47,7 @@ import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdate import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; @@ -62,13 +55,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; -import static org.eclipse.leshan.core.attributes.Attribute.GREATER_THAN; -import static org.eclipse.leshan.core.attributes.Attribute.LESSER_THAN; -import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD; -import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; -import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; -import static org.eclipse.leshan.core.attributes.Attribute.STEP; import static org.eclipse.leshan.core.model.ResourceModel.Type.BOOLEAN; import static org.eclipse.leshan.core.model.ResourceModel.Type.FLOAT; import static org.eclipse.leshan.core.model.ResourceModel.Type.INTEGER; @@ -83,6 +69,7 @@ import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaU import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_STATE_ID; + @Slf4j public class LwM2MTransportUtil { @@ -235,7 +222,7 @@ public class LwM2MTransportUtil { } public static String convertObjectIdToVersionedId(String path, Registration registration) { - String ver = registration.getSupportedObject().get(new LwM2mPath(path).getObjectId()); + String ver = String.valueOf(registration.getSupportedObject().get(new LwM2mPath(path).getObjectId())); ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString(); try { String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); @@ -289,28 +276,28 @@ public class LwM2MTransportUtil { * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60"); * Attribute [] attrs = {gt, st}; */ - public static SimpleDownlinkRequest createWriteAttributeRequest(String target, Object params, LwM2mUplinkMsgHandler serviceImpl) { - AttributeSet attrSet = new AttributeSet(createWriteAttributes(params, serviceImpl, target)); - return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null; - } - - private static Attribute[] createWriteAttributes(Object params, LwM2mUplinkMsgHandler serviceImpl, String target) { - List attributeLists = new ArrayList<>(); - Map map = JacksonUtil.convertValue(params, new TypeReference<>() { - }); - map.forEach((k, v) -> { - if (StringUtils.trimToNull(v.toString()) != null) { - Object attrValue = convertWriteAttributes(k, v, serviceImpl, target); - if (attrValue != null) { - Attribute attribute = createAttribute(k, attrValue); - if (attribute != null) { - attributeLists.add(new Attribute(k, attrValue)); - } - } - } - }); - return attributeLists.toArray(Attribute[]::new); - } +// public static SimpleDownlinkRequest createWriteAttributeRequest(String target, Object params, LwM2mUplinkMsgHandler serviceImpl) { +// AttributeSet attrSet = new AttributeSet(createWriteAttributes(params, serviceImpl, target)); +// return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null; +// } + +// private static Attribute[] createWriteAttributes(Object params, LwM2mUplinkMsgHandler serviceImpl, String target) { +// List attributeLists = new ArrayList<>(); +// Map map = JacksonUtil.convertValue(params, new TypeReference<>() { +// }); +// map.forEach((k, v) -> { +// if (StringUtils.trimToNull(v.toString()) != null) { +// Object attrValue = convertWriteAttributes(k, v, serviceImpl, target); +// if (attrValue != null) { +// Attribute attribute = createAttribute(k, attrValue); +// if (attribute != null) { +// attributeLists.add(new Attribute(k, attrValue)); +// } +// } +// } +// }); +// return attributeLists.toArray(Attribute[]::new); +// } /** * "UNSIGNED_INTEGER": // Number -> Integer Example: @@ -360,46 +347,50 @@ public class LwM2MTransportUtil { public static Map convertMultiResourceValuesFromJson(JsonElement newValProto, ResourceModel.Type type, String versionedId) { Map newValues = new HashMap<>(); newValProto.getAsJsonObject().entrySet().forEach((obj) -> { - newValues.put(Integer.valueOf(obj.getKey()), LwM2mValueConverterImpl.getInstance().convertValue(obj.getValue().getAsString(), - STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId)))); + newValues.put(Integer.valueOf(obj.getKey()), convertValueByTypeResource (obj.getValue().getAsString(), type, versionedId)); }); return newValues; } - public static Object convertWriteAttributes(String type, Object value, LwM2mUplinkMsgHandler serviceImpl, String target) { - switch (type) { - /** Integer [0:255]; */ - case DIMENSION: - Long dim = (Long) serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), INTEGER, new LwM2mPath(target)); - return dim >= 0 && dim <= 255 ? dim : null; - /**String;*/ - case OBJECT_VERSION: - return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), STRING, new LwM2mPath(target)); - /**INTEGER */ - case MINIMUM_PERIOD: - case MAXIMUM_PERIOD: - return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), INTEGER, new LwM2mPath(target)); - /**Float; */ - case GREATER_THAN: - case LESSER_THAN: - case STEP: - if (value.getClass().getSimpleName().equals("String")) { - value = Double.valueOf((String) value); - } - return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), FLOAT, new LwM2mPath(target)); - default: - return null; - } + public static Object convertValueByTypeResource (String value, ResourceModel.Type type, String versionedId) { + return LwM2mValueConverterImpl.getInstance().convertValue(value, + STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId))); } - private static Attribute createAttribute(String key, Object attrValue) { - try { - return new Attribute(key, attrValue); - } catch (Exception e) { - log.error("CreateAttribute, not valid parameter key: [{}], attrValue: [{}], error: [{}]", key, attrValue, e.getMessage()); - return null; - } - } +// public static Object convertWriteAttributes(String type, Object value, LwM2mUplinkMsgHandler serviceImpl, String target) { +// switch (type) { +// /** Integer [0:255]; */ +// case DIMENSION: +// Long dim = (Long) serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), INTEGER, new LwM2mPath(target)); +// return dim >= 0 && dim <= 255 ? dim : null; +// /**String;*/ +// case OBJECT_VERSION: +// return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), STRING, new LwM2mPath(target)); +// /**INTEGER */ +// case MINIMUM_PERIOD: +// case MAXIMUM_PERIOD: +// return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), INTEGER, new LwM2mPath(target)); +// /**Float; */ +// case GREATER_THAN: +// case LESSER_THAN: +// case STEP: +// if (value.getClass().getSimpleName().equals("String")) { +// value = Double.valueOf((String) value); +// } +// return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), FLOAT, new LwM2mPath(target)); +// default: +// return null; +// } +// } + +// private static Attribute createAttribute(String key, Object attrValue) { +// try { +// return new Attribute(key, attrValue); +// } catch (Exception e) { +// log.error("CreateAttribute, not valid parameter key: [{}], attrValue: [{}], error: [{}]", key, attrValue, e.getMessage()); +// return null; +// } +// } /** * @param lwM2MClient - diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 89510c6076..9e56d36209 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -327,10 +327,13 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator 65534) { - throw new DeviceCredentialsValidationException(server + " ShortServerId must not be less than 1 and more than 65534!"); + if (!serverConfig.isBootstrapServerIs() && (serverConfig.getShortServerId() < 1 || serverConfig.getShortServerId() > 65534)) { + throw new DeviceCredentialsValidationException("LwM2M Server ShortServerId must not be less than 1 and more than 65534!"); } + if (serverConfig.isBootstrapServerIs() && !(serverConfig.getShortServerId() == null || serverConfig.getShortServerId() ==0)) { + throw new DeviceCredentialsValidationException("Bootstrap Server ShortServerId must be null or '0'!"); + } + String server = serverConfig.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server"; if (!shortServerIds.add(serverConfig.getShortServerId())) { throw new DeviceCredentialsValidationException(server + " \"Short server Id\" value = " + serverConfig.getShortServerId() + ". This value must be a unique value for all servers!"); } diff --git a/pom.xml b/pom.xml index 64ed92bbc6..528ae6353a 100755 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,7 @@ /usr/share/${pkg.name} 2.1.1 4.0.0 - 2.3.1 + 2.4.0-b180830.0359 4.0.2 3.1.0 3.1.0 @@ -67,13 +67,13 @@ 4.5.13 4.4.14 2.8.1 - 2.13.4 - 2.13.4.2 + 2.15.3 + 2.15.3 1.3.4 4.2.1 2.2.6 - 3.0.0 - 2.0.0-M5 + 3.9.1 + 2.0.0-M14 2.9.0 2.3.30 2.0.1 diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index ef592b50cd..65b7906463 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -25,7 +25,7 @@ export const ATTRIBUTE = 'attribute'; export const TELEMETRY = 'telemetry'; export const KEY_NAME = 'keyName'; export const DEFAULT_ID_SERVER = 123; -export const DEFAULT_ID_BOOTSTRAP = 111; +export const DEFAULT_ID_BOOTSTRAP = 0; export const DEFAULT_LOCAL_HOST_NAME = 'localhost'; export const DEFAULT_PORT_SERVER_NO_SEC = 5685; export const DEFAULT_PORT_BOOTSTRAP_NO_SEC = 5687;