diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 9ed8e015f1..18e80b425d 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -220,6 +220,7 @@ services:
image: "${DOCKER_REPO}/${LWM2M_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
ports:
- "5685:5685/udp"
+ - "5686:5686/udp"
environment:
TB_SERVICE_ID: tb-lwm2m-transport
JAVA_OPTS: "${JAVA_OPTS}"
diff --git a/docker/tb-lwm2m-transport.env b/docker/tb-lwm2m-transport.env
index f284803a46..bf1d612410 100644
--- a/docker/tb-lwm2m-transport.env
+++ b/docker/tb-lwm2m-transport.env
@@ -3,6 +3,8 @@ ZOOKEEPER_URL=zookeeper:2181
LWM2M_BIND_ADDRESS=0.0.0.0
LWM2M_BIND_PORT=5685
+LWM2M_SECURITY_BIND_ADDRESS=0.0.0.0
+LWM2M_SECURITY_BIND_PORT=5686
LWM2M_TIMEOUT=10000
METRICS_ENABLED=true
diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml
index d13bb54557..cbbd76a1f6 100644
--- a/msa/black-box-tests/pom.xml
+++ b/msa/black-box-tests/pom.xml
@@ -186,6 +186,10 @@
allure-testng
test
+
+ org.eclipse.leshan
+ leshan-client-cf
+
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java
new file mode 100644
index 0000000000..d0a8b53c32
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java
@@ -0,0 +1,321 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.eclipse.leshan.client.object.Security;
+import org.eclipse.leshan.core.util.Hex;
+import org.junit.Assert;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.common.util.ThingsBoardThreadFactory;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.DeviceProfile;
+import org.thingsboard.server.common.data.DeviceProfileProvisionType;
+import org.thingsboard.server.common.data.DeviceProfileType;
+import org.thingsboard.server.common.data.DeviceTransportType;
+import org.thingsboard.server.common.data.ResourceType;
+import org.thingsboard.server.common.data.TbResource;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKBootstrapClientCredential;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential;
+import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
+import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
+import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
+import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
+import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration;
+import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration;
+import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.msa.connectivity.lwm2m.LwM2MTestClient;
+import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState;
+
+import java.net.ServerSocket;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.eclipse.leshan.client.object.Security.psk;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_ENDPOINT_NO_SEC;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_ENDPOINT_PSK;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_LWM2M_SETTINGS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_PSK_IDENTITY;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_PSK_KEY;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.OBSERVE_ATTRIBUTES_WITHOUT_PARAMS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.SECURE_URI;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.SECURITY_NO_SEC;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.resources;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.shortServerId;
+
+@Slf4j
+public class AbstractLwm2mClientTest extends AbstractContainerTest{
+ protected ScheduledExecutorService executor;
+
+ protected Security security;
+
+ protected final PageLink pageLink = new PageLink(30);
+ protected TenantId tenantId;
+ protected DeviceProfile lwm2mDeviceProfile;
+ protected Device lwM2MDeviceTest;
+ protected LwM2MTestClient lwM2MTestClient;
+
+ public final Set expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS));
+
+ public void connectLwm2mClientNoSec() throws Exception {
+ LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(CLIENT_ENDPOINT_NO_SEC));
+ basicTestConnection(SECURITY_NO_SEC,
+ deviceCredentials,
+ CLIENT_ENDPOINT_NO_SEC,
+ "TestConnection Lwm2m NoSec (msa)");
+ }
+
+ public void connectLwm2mClientPsk() throws Exception {
+ String clientEndpoint = CLIENT_ENDPOINT_PSK;
+ String identity = CLIENT_PSK_IDENTITY;
+ String keyPsk = CLIENT_PSK_KEY;
+ PSKClientCredential clientCredentials = new PSKClientCredential();
+ clientCredentials.setEndpoint(clientEndpoint);
+ clientCredentials.setIdentity(identity);
+ clientCredentials.setKey(keyPsk);
+ Security security = psk(SECURE_URI,
+ shortServerId,
+ identity.getBytes(StandardCharsets.UTF_8),
+ Hex.decodeHex(keyPsk.toCharArray()));
+ LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecurePsk(clientCredentials);
+
+ basicTestConnection(security,
+ deviceCredentials,
+ clientEndpoint,
+ "TestConnection Lwm2m Rpc (msa)");
+ }
+
+ public void basicTestConnection(Security security,
+ LwM2MDeviceCredentials deviceCredentials,
+ String clientEndpoint, String alias) throws Exception {
+ // create lwm2mClient and lwM2MDevice
+ lwM2MDeviceTest = createDeviceWithCredentials(deviceCredentials, clientEndpoint);
+ lwM2MTestClient = createNewClient(security, clientEndpoint, executor);
+
+ LwM2MClientState finishState = ON_REGISTRATION_SUCCESS;
+ await(alias + " - " + ON_REGISTRATION_STARTED)
+ .atMost(40, TimeUnit.SECONDS)
+ .until(() -> {
+ log.warn("msa basicTestConnection started -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates());
+ return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED);
+ });
+ await(alias + " - " + ON_UPDATE_SUCCESS)
+ .atMost(40, TimeUnit.SECONDS)
+ .until(() -> {
+ log.warn("msa basicTestConnection update -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates());
+ return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS);
+ });
+ Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesRegistrationLwm2mSuccess));
+ }
+
+ public LwM2MTestClient createNewClient(Security security,
+ String endpoint, ScheduledExecutorService executor) throws Exception {
+ this.executor = executor;
+ LwM2MTestClient lwM2MTestClient = new LwM2MTestClient(endpoint);
+ try (ServerSocket socket = new ServerSocket(0)) {
+ int clientPort = socket.getLocalPort();
+ lwM2MTestClient.init(security, clientPort);
+ }
+ return lwM2MTestClient;
+ }
+
+ protected void destroyAfter(){
+ clientDestroy();
+ deviceDestroy();
+ deviceProfileDestroy();
+ if (executor != null) {
+ executor.shutdown();
+ }
+ }
+
+ protected void clientDestroy() {
+ try {
+ if (lwM2MTestClient != null) {
+ lwM2MTestClient.destroy();
+ }
+ } catch (Exception e) {
+ log.error("Failed client Destroy", e);
+ }
+ }
+ protected void deviceDestroy() {
+ try {
+ if (lwM2MDeviceTest != null) {
+ testRestClient.deleteDeviceIfExists(lwM2MDeviceTest.getId());
+ }
+ } catch (Exception e) {
+ log.error("Failed device Delete", e);
+ }
+ }
+
+ protected void initTest(String deviceProfileName) throws Exception {
+ if (executor != null) {
+ executor.shutdown();
+ }
+ executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-scheduled-" + deviceProfileName));
+
+ lwm2mDeviceProfile = getDeviceProfile(deviceProfileName);
+ tenantId = lwm2mDeviceProfile.getTenantId();
+
+ for (String resourceName : resources) {
+ TbResource lwModel = new TbResource();
+ lwModel.setResourceType(ResourceType.LWM2M_MODEL);
+ lwModel.setTitle(resourceName);
+ lwModel.setFileName(resourceName);
+ lwModel.setTenantId(tenantId);
+ byte[] bytes = IOUtils.toByteArray(AbstractLwm2mClientTest.class.getClassLoader().getResourceAsStream("lwm2m-registry/" + resourceName));
+ lwModel.setData(bytes);
+ testRestClient.postTbResourceIfNotExists(lwModel);
+ }
+ }
+
+ protected DeviceProfile getDeviceProfile(String deviceProfileName) throws Exception {
+ DeviceProfile deviceProfile = getDeviceProfileIfExists(deviceProfileName);
+ if (deviceProfile == null) {
+ deviceProfile = testRestClient.postDeviceProfile(createDeviceProfile(deviceProfileName));
+ }
+ return deviceProfile;
+ }
+
+ protected DeviceProfile getDeviceProfileIfExists(String deviceProfileName) throws Exception {
+ return testRestClient.getDeviceProfiles(pageLink).getData().stream()
+ .filter(x -> x.getName().equals(deviceProfileName))
+ .findFirst()
+ .orElse(null);
+ }
+
+
+ protected DeviceProfile createDeviceProfile(String deviceProfileName) throws Exception {
+ DeviceProfile deviceProfile = new DeviceProfile();
+ deviceProfile.setName(deviceProfileName);
+ deviceProfile.setType(DeviceProfileType.DEFAULT);
+ deviceProfile.setTransportType(DeviceTransportType.LWM2M);
+ deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
+ deviceProfile.setDescription(deviceProfile.getName());
+
+ DeviceProfileData deviceProfileData = new DeviceProfileData();
+ deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration());
+ deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null));
+ deviceProfileData.setTransportConfiguration(getTransportConfiguration());
+ deviceProfile.setProfileData(deviceProfileData);
+ return deviceProfile;
+ }
+
+ protected void deviceProfileDestroy(){
+ try {
+ if (lwm2mDeviceProfile != null) {
+ testRestClient.deleteDeviceProfileIfExists(lwm2mDeviceProfile);
+ }
+ } catch (Exception e) {
+ log.error("Failed deviceProfile Delete", e);
+ }
+ }
+
+ protected Device createDeviceWithCredentials(LwM2MDeviceCredentials deviceCredentials, String clientEndpoint) throws Exception {
+ Device device = createDevice(deviceCredentials, clientEndpoint);
+ return device;
+ }
+
+ protected Device createDevice(LwM2MDeviceCredentials credentials, String clientEndpoint) throws Exception {
+ Device device = testRestClient.getDeviceByNameIfExists(clientEndpoint);
+ if (device == null) {
+ device = new Device();
+ device.setName(clientEndpoint);
+ device.setDeviceProfileId(lwm2mDeviceProfile.getId());
+ device.setTenantId(tenantId);
+ device = testRestClient.postDevice("", device);
+ }
+
+ DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
+ deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
+ deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials));
+ deviceCredentials = testRestClient.postDeviceCredentials(deviceCredentials);
+ assertThat(deviceCredentials).isNotNull();
+ return device;
+ }
+
+ protected LwM2MDeviceCredentials getDeviceCredentialsNoSec(LwM2MClientCredential clientCredentials) {
+ LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials();
+ credentials.setClient(clientCredentials);
+ LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials();
+ NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential();
+ bootstrapCredentials.setBootstrapServer(serverCredentials);
+ bootstrapCredentials.setLwm2mServer(serverCredentials);
+ credentials.setBootstrap(bootstrapCredentials);
+ return credentials;
+ }
+
+ public NoSecClientCredential createNoSecClientCredentials(String clientEndpoint) {
+ NoSecClientCredential clientCredentials = new NoSecClientCredential();
+ clientCredentials.setEndpoint(clientEndpoint);
+ return clientCredentials;
+ }
+
+ protected Lwm2mDeviceProfileTransportConfiguration getTransportConfiguration() {
+ List bootstrapServerCredentials = new ArrayList<>();
+ Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration();
+ TelemetryMappingConfiguration observeAttrConfiguration = JacksonUtil.fromString(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, TelemetryMappingConfiguration.class);
+ OtherConfiguration clientLwM2mSettings = JacksonUtil.fromString(CLIENT_LWM2M_SETTINGS, OtherConfiguration.class);
+ transportConfiguration.setBootstrapServerUpdateEnable(true);
+ transportConfiguration.setObserveAttr(observeAttrConfiguration);
+ transportConfiguration.setClientLwM2mSettings(clientLwM2mSettings);
+ transportConfiguration.setBootstrap(bootstrapServerCredentials);
+ return transportConfiguration;
+ }
+
+ protected LwM2MDeviceCredentials getDeviceCredentialsSecurePsk(LwM2MClientCredential clientCredentials) {
+ LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials();
+ credentials.setClient(clientCredentials);
+ LwM2MBootstrapClientCredentials bootstrapCredentials;
+ bootstrapCredentials = getBootstrapClientCredentialsPsk(clientCredentials);
+ credentials.setBootstrap(bootstrapCredentials);
+ return credentials;
+ }
+
+ private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsPsk(LwM2MClientCredential clientCredentials) {
+ LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials();
+ PSKBootstrapClientCredential serverCredentials = new PSKBootstrapClientCredential();
+ if (clientCredentials != null) {
+ serverCredentials.setClientSecretKey(((PSKClientCredential) clientCredentials).getKey());
+ serverCredentials.setClientPublicKeyOrId(((PSKClientCredential) clientCredentials).getIdentity());
+ }
+ bootstrapCredentials.setBootstrapServer(serverCredentials);
+ bootstrapCredentials.setLwm2mServer(serverCredentials);
+ return bootstrapCredentials;
+ }
+}
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
index 83c9c927aa..c76627d3a1 100644
--- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
@@ -23,6 +23,7 @@ import io.restassured.config.RestAssuredConfig;
import io.restassured.filter.log.RequestLoggingFilter;
import io.restassured.filter.log.ResponseLoggingFilter;
import io.restassured.http.ContentType;
+import io.restassured.internal.ValidatableResponseImpl;
import io.restassured.path.json.JsonPath;
import io.restassured.response.ValidatableResponse;
import io.restassured.specification.RequestSpecification;
@@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EventInfo;
+import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.asset.Asset;
@@ -63,6 +65,7 @@ import java.util.List;
import java.util.Map;
import static io.restassured.RestAssured.given;
+import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_OK;
import static org.hamcrest.Matchers.is;
@@ -548,6 +551,43 @@ public class TestRestClient {
.as(new TypeRef<>() {});
}
+ public ValidatableResponse postTbResourceIfNotExists(TbResource lwModel) {
+ return given().spec(requestSpec).body(lwModel)
+ .post("/api/resource")
+ .then()
+ .statusCode(anyOf(is(HTTP_OK), is(HTTP_BAD_REQUEST)));
+ }
+ public void deleteDeviceProfileIfExists(DeviceProfile deviceProfile) {
+ given().spec(requestSpec)
+ .delete("/api/deviceProfile/" + deviceProfile.getId().getId().toString())
+ .then()
+ .statusCode(anyOf(is(HTTP_OK), is(HTTP_NOT_FOUND)));
+ }
+
+ public Device getDeviceByNameIfExists(String deviceName) {
+ ValidatableResponse response = given().spec(requestSpec)
+ .pathParams("deviceName", deviceName)
+ .get("/api/tenant/devices?deviceName={deviceName}")
+ .then()
+ .statusCode(anyOf(is(HTTP_OK), is(HTTP_NOT_FOUND)));
+ if(((ValidatableResponseImpl) response).extract().response().getStatusCode()==HTTP_OK){
+ return response.extract()
+ .as(Device.class);
+ } else {
+ return null;
+ }
+ }
+
+ public DeviceCredentials postDeviceCredentials(DeviceCredentials deviceCredentials) {
+ return given().spec(requestSpec).body(deviceCredentials)
+ .post("/api/device/credentials")
+ .then()
+ .assertThat()
+ .statusCode(HTTP_OK)
+ .extract()
+ .as(DeviceCredentials.class);
+ }
+
private void addTimePageLinkToParam(Map params, TimePageLink pageLink) {
this.addPageLinkToParam(params, pageLink);
if (pageLink.getStartTime() != null) {
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java
new file mode 100644
index 0000000000..15658140b0
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa.connectivity;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.thingsboard.server.msa.AbstractLwm2mClientTest;
+import org.thingsboard.server.msa.DisableUIListeners;
+
+import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL;
+import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD;
+
+@DisableUIListeners
+public class Lwm2mClientNoSecTest extends AbstractLwm2mClientTest {
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD);
+ initTest("lwm2m-NoSec");
+ }
+
+ @AfterMethod
+ public void tearDown() {
+ destroyAfter();
+ }
+
+ @Test
+ public void connectLwm2mClientNoSecWithLwm2mServer() throws Exception {
+ connectLwm2mClientNoSec();
+ }
+}
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java
new file mode 100644
index 0000000000..fbd8139d8f
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa.connectivity;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.thingsboard.server.msa.AbstractLwm2mClientTest;
+import org.thingsboard.server.msa.DisableUIListeners;
+
+import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL;
+import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD;
+
+@DisableUIListeners
+public class Lwm2mClientPskTest extends AbstractLwm2mClientTest {
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD);
+ initTest("lwm2m-Psk");
+ }
+
+ @AfterMethod
+ public void tearDown() {
+ destroyAfter();
+ }
+
+ @Test
+ public void connectLwm2mClientPskWithLwm2mServer() throws Exception {
+ connectLwm2mClientPsk();
+ }
+}
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java
new file mode 100644
index 0000000000..2910a2b7da
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java
@@ -0,0 +1,157 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa.connectivity.lwm2m;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
+import org.eclipse.leshan.client.servers.LwM2mServer;
+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;
+import org.thingsboard.common.util.ThingsBoardThreadFactory;
+
+import javax.security.auth.Destroyable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Slf4j
+public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
+
+ private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 5, 6, 7, 9);
+
+ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(getClass().getSimpleName() + "-test-scope"));
+
+ private final AtomicInteger state = new AtomicInteger(0);
+
+ private final AtomicInteger updateResult = new AtomicInteger(0);
+
+ @Override
+ public ReadResponse read(LwM2mServer identity, int resourceId) {
+ if (!identity.isSystem())
+ log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId);
+ switch (resourceId) {
+ case 3:
+ return ReadResponse.success(resourceId, getState());
+ case 5:
+ return ReadResponse.success(resourceId, getUpdateResult());
+ case 6:
+ return ReadResponse.success(resourceId, getPkgName());
+ case 7:
+ return ReadResponse.success(resourceId, getPkgVersion());
+ case 9:
+ return ReadResponse.success(resourceId, getFirmwareUpdateDeliveryMethod());
+ default:
+ return super.read(identity, resourceId);
+ }
+ }
+
+ @Override
+ public ExecuteResponse execute(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, arguments);
+ }
+ }
+
+ @Override
+ public WriteResponse write(LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) {
+ log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId);
+
+ switch (resourceId) {
+ case 0:
+ startDownloading();
+ return WriteResponse.success();
+ case 1:
+ startDownloading();
+ return WriteResponse.success();
+ default:
+ return super.write(identity, replace, resourceId, value);
+ }
+ }
+
+ private int getState() {
+ return state.get();
+ }
+
+ private int getUpdateResult() {
+ return updateResult.get();
+ }
+
+ private String getPkgName() {
+ return "firmware";
+ }
+
+ private String getPkgVersion() {
+ return "1.0.0";
+ }
+
+ private int getFirmwareUpdateDeliveryMethod() {
+ return 1;
+ }
+
+ @Override
+ public List getAvailableResourceIds(ObjectModel model) {
+ return supportedResources;
+ }
+
+ @Override
+ public void destroy() {
+ scheduler.shutdownNow();
+ }
+
+ private void startDownloading() {
+ scheduler.schedule(() -> {
+ try {
+ state.set(1);
+ fireResourceChange(3);
+ Thread.sleep(100);
+ state.set(2);
+ fireResourceChange(3);
+ } catch (Exception e) {
+ }
+ }, 100, TimeUnit.MILLISECONDS);
+ }
+
+ private void startUpdating() {
+ scheduler.schedule(() -> {
+ try {
+ state.set(3);
+ fireResourceChange(3);
+ Thread.sleep(100);
+ updateResult.set(1);
+ fireResourceChange(5);
+ } catch (Exception e) {
+ }
+ }, 100, TimeUnit.MILLISECONDS);
+ }
+
+}
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java
new file mode 100644
index 0000000000..4c20199c8e
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java
@@ -0,0 +1,337 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa.connectivity.lwm2m;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.californium.elements.config.Configuration;
+import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
+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.LwM2mObjectEnabler;
+import org.eclipse.leshan.client.resource.ObjectsInitializer;
+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;
+import org.eclipse.leshan.core.model.ObjectLoader;
+import org.eclipse.leshan.core.model.ObjectModel;
+import org.eclipse.leshan.core.model.StaticModel;
+import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
+import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
+import org.eclipse.leshan.core.request.BootstrapRequest;
+import org.eclipse.leshan.core.request.DeregisterRequest;
+import org.eclipse.leshan.core.request.RegisterRequest;
+import org.eclipse.leshan.core.request.UpdateRequest;
+import org.junit.Assert;
+import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH;
+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;
+import static org.eclipse.leshan.core.LwM2mId.SECURITY;
+import static org.eclipse.leshan.core.LwM2mId.SERVER;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_TIMEOUT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_FAILURE;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_TIMEOUT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_EXPECTED_ERROR;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_FAILURE;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_TIMEOUT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_FAILURE;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_STARTED;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_TIMEOUT;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.resources;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.serverId;
+import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.shortServerId;
+
+
+@Slf4j
+@Data
+public class LwM2MTestClient {
+
+ private final String endpoint;
+ private LeshanClient leshanClient;
+ private SimpleLwM2MDevice lwM2MDevice;
+
+ private Set clientStates;
+
+ private FwLwM2MDevice fwLwM2MDevice;
+ private Map clientDtlsCid;
+
+ public void init(Security security, int clientPort) throws InvalidDDFFileException, IOException {
+ Assert.assertNull("client already initialized", leshanClient);
+
+ List models = new ArrayList<>();
+ for (String resourceName : resources) {
+ models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m-registry/" + resourceName), resourceName));
+ }
+ LwM2mModel model = new StaticModel(models);
+ ObjectsInitializer initializer = new ObjectsInitializer(model);
+
+ // 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());
+ initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class);
+ initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice());
+
+ 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."
+ */
+ 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();
+
+ // Set some DTLS stuff
+ // These configuration values are always overwritten by CLI therefore set them to transient.
+ clientCoapConfig.setTransient(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY);
+ clientCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH);
+ boolean supportDeprecatedCiphers = false;
+ clientCoapConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !supportDeprecatedCiphers);
+
+ // Set Californium Configuration
+
+ endpointsBuilder.setConfiguration(clientCoapConfig);
+ endpointsBuilder.setClientAddress(new InetSocketAddress(clientPort).getAddress());
+
+
+ // creates EndpointsProvider
+ List endpointsProvider = new ArrayList<>();
+ endpointsProvider.add(endpointsBuilder.build());
+
+ // Configure Registration Engine
+ DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory();
+ /**
+ * Force reconnection/rehandshake on registration update.
+ */
+ int comPeriodInSec = 5;
+ if (comPeriodInSec > 0) engineFactory.setCommunicationPeriod(comPeriodInSec * 1000);
+
+ /**
+ * 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.setResumeOnConnect(true);
+
+ /**
+ * Client use queue mode.
+ */
+ engineFactory.setQueueMode(false);
+
+ // Create client
+ LeshanClientBuilder builder = new LeshanClientBuilder(endpoint);
+ 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);
+
+ clientStates = new HashSet<>();
+ clientDtlsCid = new HashMap<>();
+ clientStates.add(ON_INIT);
+ leshanClient = builder.build();
+
+ LwM2mClientObserver observer = new LwM2mClientObserver() {
+ @Override
+ public void onBootstrapStarted(LwM2mServer bsserver, BootstrapRequest request) {
+ clientStates.add(ON_BOOTSTRAP_STARTED);
+ }
+
+ @Override
+ public void onBootstrapSuccess(LwM2mServer bsserver, BootstrapRequest request) {
+ clientStates.add(ON_BOOTSTRAP_SUCCESS);
+ }
+
+ @Override
+ public void onBootstrapFailure(LwM2mServer bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+ clientStates.add(ON_BOOTSTRAP_FAILURE);
+ }
+
+ @Override
+ public void onBootstrapTimeout(LwM2mServer bsserver, BootstrapRequest request) {
+ clientStates.add(ON_BOOTSTRAP_TIMEOUT);
+ }
+
+ @Override
+ public void onRegistrationStarted(LwM2mServer server, RegisterRequest request) {
+ clientStates.add(ON_REGISTRATION_STARTED);
+ }
+
+ @Override
+ public void onRegistrationSuccess(LwM2mServer server, RegisterRequest request, String registrationID) {
+ clientStates.add(ON_REGISTRATION_SUCCESS);
+ }
+
+ @Override
+ public void onRegistrationFailure(LwM2mServer server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+ clientStates.add(ON_REGISTRATION_FAILURE);
+ }
+
+ @Override
+ public void onRegistrationTimeout(LwM2mServer server, RegisterRequest request) {
+ clientStates.add(ON_REGISTRATION_TIMEOUT);
+ }
+
+ @Override
+ public void onUpdateStarted(LwM2mServer server, UpdateRequest request) {
+ clientStates.add(ON_UPDATE_STARTED);
+ }
+
+ @Override
+ public void onUpdateSuccess(LwM2mServer server, UpdateRequest request) {
+ clientStates.add(ON_UPDATE_SUCCESS);
+ }
+
+ @Override
+ public void onUpdateFailure(LwM2mServer server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+ clientStates.add(ON_UPDATE_FAILURE);
+ }
+
+ @Override
+ public void onUpdateTimeout(LwM2mServer server, UpdateRequest request) {
+ clientStates.add(ON_UPDATE_TIMEOUT);
+ }
+
+ @Override
+ public void onDeregistrationStarted(LwM2mServer server, DeregisterRequest request) {
+ clientStates.add(ON_DEREGISTRATION_STARTED);
+ }
+
+ @Override
+ public void onDeregistrationSuccess(LwM2mServer server, DeregisterRequest request) {
+ clientStates.add(ON_DEREGISTRATION_SUCCESS);
+ }
+
+ @Override
+ public void onDeregistrationFailure(LwM2mServer server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+ clientStates.add(ON_DEREGISTRATION_FAILURE);
+ }
+
+ @Override
+ public void onDeregistrationTimeout(LwM2mServer server, DeregisterRequest request) {
+ clientStates.add(ON_DEREGISTRATION_TIMEOUT);
+ }
+
+ @Override
+ public void onUnexpectedError(Throwable unexpectedError) {
+ clientStates.add(ON_EXPECTED_ERROR);
+ }
+ };
+ 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);
+ }
+ });
+
+ leshanClient.start();
+ }
+
+ public void destroy() {
+ if (leshanClient != null) {
+ leshanClient.destroy(true);
+ }
+ if (lwM2MDevice != null) {
+ lwM2MDevice.destroy();
+ }
+ if (fwLwM2MDevice != null) {
+ fwLwM2MDevice.destroy();
+ }
+ }
+}
\ No newline at end of file
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java
new file mode 100644
index 0000000000..b49d54f27e
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java
@@ -0,0 +1,192 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa.connectivity.lwm2m;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.leshan.core.model.ResourceModel.Type;
+import org.eclipse.leshan.core.node.LwM2mPath;
+import org.eclipse.leshan.core.node.ObjectLink;
+import org.eclipse.leshan.core.node.codec.CodecException;
+import org.eclipse.leshan.core.node.codec.LwM2mValueConverter;
+import org.eclipse.leshan.core.util.Hex;
+import org.thingsboard.server.common.data.StringUtils;
+
+import java.math.BigInteger;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Base64;
+import java.util.Date;
+
+import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
+
+@Slf4j
+public class LwM2mValueConverterImpl implements LwM2mValueConverter {
+
+ private static final LwM2mValueConverterImpl INSTANCE = new LwM2mValueConverterImpl();
+
+ public static LwM2mValueConverterImpl getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public Object convertValue(Object value, Type currentType, Type expectedType, LwM2mPath resourcePath)
+ throws CodecException {
+ if (value == null) {
+ return null;
+ }
+ if (expectedType == null) {
+ /** unknown resource, trusted value */
+ return value;
+ }
+
+ if (currentType == expectedType) {
+ /** expected type */
+ return value;
+ }
+ if (currentType == null) {
+ currentType = OPAQUE;
+ }
+
+ switch (expectedType) {
+ case INTEGER:
+ switch (currentType) {
+ case FLOAT:
+ log.debug("Trying to convert float value [{}] to Integer", value);
+ Long longValue = ((Double) value).longValue();
+ if ((double) value == longValue.doubleValue()) {
+ return longValue;
+ }
+ case STRING:
+ log.debug("Trying to convert String value [{}] to Integer", value);
+ return Long.parseLong((String) value);
+ default:
+ break;
+ }
+ break;
+ case FLOAT:
+ switch (currentType) {
+ case INTEGER:
+ log.debug("Trying to convert integer value [{}] to float", value);
+ Double floatValue = ((Long) value).doubleValue();
+ if ((long) value == floatValue.longValue()) {
+ return floatValue;
+ }
+ case STRING:
+ log.debug("Trying to convert String value [{}] to Float", value);
+ return Float.valueOf((String) value);
+ default:
+ break;
+ }
+ break;
+ case BOOLEAN:
+ switch (currentType) {
+ case STRING:
+ log.debug("Trying to convert string value {} to boolean", value);
+ if (StringUtils.equalsIgnoreCase((String) value, "true")) {
+ return true;
+ } else if (StringUtils.equalsIgnoreCase((String) value, "false")) {
+ return false;
+ }
+ break;
+ case INTEGER:
+ log.debug("Trying to convert int value {} to boolean", value);
+ Long val = (Long) value;
+ if (val == 1) {
+ return true;
+ } else if (val == 0) {
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case TIME:
+ switch (currentType) {
+ case INTEGER:
+ log.debug("Trying to convert long value {} to date", value);
+ /* let's assume we received the millisecond since 1970/1/1 */
+ return new Date(((Number) value).longValue() * 1000L);
+ case STRING:
+ log.debug("Trying to convert string value {} to date", value);
+ /** let's assume we received an ISO 8601 format date */
+ try {
+ return new Date(Long.decode(value.toString()));
+ /**
+ DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
+ XMLGregorianCalendar cal = datatypeFactory.newXMLGregorianCalendar((String) value);
+ return cal.toGregorianCalendar().getTime();
+ **/
+ } catch (IllegalArgumentException e) {
+ log.debug("Unable to convert string to date", e);
+ throw new CodecException("Unable to convert string (%s) to date for resource %s", value,
+ resourcePath);
+ }
+ default:
+ break;
+ }
+ break;
+ case STRING:
+ switch (currentType) {
+ case BOOLEAN:
+ case INTEGER:
+ case FLOAT:
+ return String.valueOf(value);
+ case TIME:
+ String DATE_FORMAT = "MMM d, yyyy HH:mm a";
+ Long timeValue;
+ try {
+ timeValue = ((Date) value).getTime();
+ }
+ catch (Exception e){
+ timeValue = new BigInteger((byte [])value).longValue();
+ }
+ DateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
+ return formatter.format(new Date(timeValue));
+ case OPAQUE:
+ return Hex.encodeHexString((byte[])value);
+ case OBJLNK:
+ return ObjectLink.decodeFromString((String) value);
+ default:
+ break;
+ }
+ break;
+ case OPAQUE:
+ if (currentType == Type.STRING) {
+ /** let's assume we received an hexadecimal string */
+ log.debug("Trying to convert hexadecimal/base64 string [{}] to byte array", value);
+ try {
+ return Hex.decodeHex(((String)value).toCharArray());
+ } catch (IllegalArgumentException e) {
+ try {
+ return Base64.getDecoder().decode((String) value);
+ } catch (IllegalArgumentException ea) {
+ throw new CodecException("Unable to convert hexastring or base64 [%s] to byte array for resource %s",
+ value, resourcePath);
+ }
+ }
+ }
+ break;
+ case OBJLNK:
+ if (currentType == Type.STRING) {
+ return ObjectLink.fromPath(value.toString());
+ }
+ default:
+ }
+ throw new CodecException("Invalid value type for resource %s, expected %s, got %s", resourcePath, expectedType,
+ currentType);
+ }
+}
\ No newline at end of file
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java
new file mode 100644
index 0000000000..dfb4c71a1e
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa.connectivity.lwm2m;
+
+import org.eclipse.californium.elements.config.Configuration;
+import org.eclipse.leshan.client.object.Security;
+
+import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH;
+import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_NODE_ID;
+import static org.eclipse.leshan.client.object.Security.noSec;
+
+public class Lwm2mTestHelper {
+
+ // Models
+ public static final String[] resources = new String[]{ "0.xml", "1.xml", "2.xml", "3.xml", "5.xml"};
+ public static final int serverId = 1;
+
+ public static final int port = 5685;
+ public static final int securityPort = 5686;
+ public static final Integer shortServerId = 123;
+
+ public static final String host = "localhost";
+ public static final String COAP = "coap://";
+ public static final String COAPS = "coaps://";
+ public static final String URI = COAP + host + ":" + port;
+ public static final String SECURE_URI = COAPS + host + ":" + securityPort;
+ public static final String CLIENT_ENDPOINT_NO_SEC = "LwNoSec00000000";
+ public static final Security SECURITY_NO_SEC = noSec(URI, shortServerId);
+
+ public static final String CLIENT_ENDPOINT_PSK = "LwPsk00000000";
+ public static final String CLIENT_PSK_IDENTITY = "SOME_PSK_ID";
+ public static final String CLIENT_PSK_KEY = "73656372657450534b73656372657450";
+
+
+ public static final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS =
+ " {\n" +
+ " \"keyName\": {},\n" +
+ " \"observe\": [],\n" +
+ " \"attribute\": [],\n" +
+ " \"telemetry\": [],\n" +
+ " \"attributeLwm2m\": {}\n" +
+ " }";
+
+ public static final String CLIENT_LWM2M_SETTINGS =
+ " {\n" +
+ " \"edrxCycle\": null,\n" +
+ " \"powerMode\": \"DRX\",\n" +
+ " \"fwUpdateResource\": null,\n" +
+ " \"fwUpdateStrategy\": 1,\n" +
+ " \"psmActivityTimer\": null,\n" +
+ " \"swUpdateResource\": null,\n" +
+ " \"swUpdateStrategy\": 1,\n" +
+ " \"pagingTransmissionWindow\": null,\n" +
+ " \"clientOnlyObserveAfterConnect\": 1\n" +
+ " }";
+
+ public static final int BINARY_APP_DATA_CONTAINER = 19;
+ public static final int OBJECT_INSTANCE_ID_0 = 0;
+ public static final int OBJECT_INSTANCE_ID_1 = 1;
+
+ public enum LwM2MClientState {
+
+ ON_INIT(0, "onInit"),
+ ON_BOOTSTRAP_STARTED(1, "onBootstrapStarted"),
+ ON_BOOTSTRAP_SUCCESS(2, "onBootstrapSuccess"),
+ ON_BOOTSTRAP_FAILURE(3, "onBootstrapFailure"),
+ ON_BOOTSTRAP_TIMEOUT(4, "onBootstrapTimeout"),
+ ON_REGISTRATION_STARTED(5, "onRegistrationStarted"),
+ ON_REGISTRATION_SUCCESS(6, "onRegistrationSuccess"),
+ ON_REGISTRATION_FAILURE(7, "onRegistrationFailure"),
+ ON_REGISTRATION_TIMEOUT(7, "onRegistrationTimeout"),
+ ON_UPDATE_STARTED(8, "onUpdateStarted"),
+ ON_UPDATE_SUCCESS(9, "onUpdateSuccess"),
+ ON_UPDATE_FAILURE(10, "onUpdateFailure"),
+ ON_UPDATE_TIMEOUT(11, "onUpdateTimeout"),
+ ON_DEREGISTRATION_STARTED(12, "onDeregistrationStarted"),
+ ON_DEREGISTRATION_SUCCESS(13, "onDeregistrationSuccess"),
+ ON_DEREGISTRATION_FAILURE(14, "onDeregistrationFailure"),
+ ON_DEREGISTRATION_TIMEOUT(15, "onDeregistrationTimeout"),
+ ON_EXPECTED_ERROR(16, "onUnexpectedError"),
+ ON_READ_CONNECTION_ID (17, "onReadConnection"),
+ ON_WRITE_CONNECTION_ID (18, "onWriteConnection");
+
+ public int code;
+ public String type;
+
+ LwM2MClientState(int code, String type) {
+ this.code = code;
+ this.type = type;
+ }
+
+ public static LwM2MClientState fromLwM2MClientStateByType(String type) {
+ for (LwM2MClientState to : LwM2MClientState.values()) {
+ if (to.type.equals(type)) {
+ return to;
+ }
+ }
+ throw new IllegalArgumentException(String.format("Unsupported Client State type : %s", type));
+ }
+
+ public static LwM2MClientState fromLwM2MClientStateByCode(int code) {
+ for (LwM2MClientState to : LwM2MClientState.values()) {
+ if (to.code == code) {
+ return to;
+ }
+ }
+ throw new IllegalArgumentException(String.format("Unsupported Client State code : %s", code));
+ }
+ }
+
+ public static void setDtlsConnectorConfigCidLength(Configuration serverCoapConfig, Integer cIdLength) {
+ serverCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH);
+ serverCoapConfig.setTransient(DTLS_CONNECTION_ID_NODE_ID);
+ serverCoapConfig.set(DTLS_CONNECTION_ID_LENGTH, cIdLength);
+ if ( cIdLength > 4) {
+ serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, 0);
+ } else {
+ serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java
new file mode 100644
index 0000000000..d2aa1af4d2
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java
@@ -0,0 +1,218 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa.connectivity.lwm2m;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
+import org.eclipse.leshan.client.servers.LwM2mServer;
+import org.eclipse.leshan.core.Destroyable;
+import org.eclipse.leshan.core.model.ObjectModel;
+import org.eclipse.leshan.core.model.ResourceModel;
+import org.eclipse.leshan.core.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;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.PrimitiveIterator;
+import java.util.Random;
+import java.util.TimeZone;
+
+@Slf4j
+public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
+
+
+ private static final Random RANDOM = new Random();
+ private static final int min = 5;
+ private static final int max = 50;
+ private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min,max + 1).iterator();
+ private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21);
+
+
+ public SimpleLwM2MDevice() {
+ }
+
+
+
+ @Override
+ public ReadResponse read(LwM2mServer identity, int resourceId) {
+ if (!identity.isSystem())
+ log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId);
+ switch (resourceId) {
+ case 0:
+ return ReadResponse.success(resourceId, getManufacturer());
+ case 1:
+ return ReadResponse.success(resourceId, getModelNumber());
+ case 2:
+ return ReadResponse.success(resourceId, getSerialNumber());
+ case 3:
+ return ReadResponse.success(resourceId, getFirmwareVersion());
+ case 6:
+ return ReadResponse.success(resourceId, getAvailablePowerSources(), ResourceModel.Type.INTEGER);
+ case 9:
+ return ReadResponse.success(resourceId, getBatteryLevel());
+ case 10:
+ return ReadResponse.success(resourceId, getMemoryFree());
+ case 11:
+ Map errorCodes = new HashMap<>();
+ errorCodes.put(0, getErrorCode());
+ return ReadResponse.success(resourceId, errorCodes, ResourceModel.Type.INTEGER);
+ case 14:
+ return ReadResponse.success(resourceId, getUtcOffset());
+ case 15:
+ return ReadResponse.success(resourceId, getTimezone());
+ case 16:
+ return ReadResponse.success(resourceId, getSupportedBinding());
+ case 17:
+ return ReadResponse.success(resourceId, getDeviceType());
+ case 18:
+ return ReadResponse.success(resourceId, getHardwareVersion());
+ case 19:
+ return ReadResponse.success(resourceId, getSoftwareVersion());
+ case 20:
+ return ReadResponse.success(resourceId, getBatteryStatus());
+ case 21:
+ return ReadResponse.success(resourceId, getMemoryTotal());
+ default:
+ return super.read(identity, resourceId);
+ }
+ }
+
+ @Override
+ public ExecuteResponse execute(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(LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) {
+ log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId);
+
+ switch (resourceId) {
+ case 13:
+ return WriteResponse.notFound();
+ case 14:
+ setUtcOffset((String) value.getValue());
+ fireResourceChange(resourceId);
+ return WriteResponse.success();
+ case 15:
+ setTimezone((String) value.getValue());
+ fireResourceChange(resourceId);
+ return WriteResponse.success();
+ default:
+ return super.write(identity, replace, resourceId, value);
+ }
+ }
+
+ private String getManufacturer() {
+ return "Thingsboard Demo Lwm2mDevice";
+ }
+
+ private String getModelNumber() {
+ return "Model 500";
+ }
+
+ private String getSerialNumber() {
+ return "Thingsboard-500-000-0001";
+ }
+
+ private String getFirmwareVersion() {
+ return "1.0.2";
+ }
+
+ private long getErrorCode() {
+ return 0;
+ }
+
+ private Map getAvailablePowerSources() {
+ Map availablePowerSources = new HashMap<>();
+ availablePowerSources.put(0, 1L);
+ availablePowerSources.put(1, 2L);
+ availablePowerSources.put(2, 5L);
+ return availablePowerSources;
+ }
+
+ private int getBatteryLevel() {
+ return randomIterator.nextInt();
+// return 42;
+ }
+
+ private long getMemoryFree() {
+ return Runtime.getRuntime().freeMemory() / 1024;
+ }
+
+ private String utcOffset = new SimpleDateFormat("X").format(Calendar.getInstance().getTime());
+
+ private String getUtcOffset() {
+ return utcOffset;
+ }
+
+ private void setUtcOffset(String t) {
+ utcOffset = t;
+ }
+
+ private String timeZone = TimeZone.getDefault().getID();
+
+ private String getTimezone() {
+ return timeZone;
+ }
+
+ private void setTimezone(String t) {
+ timeZone = t;
+ }
+
+ private String getSupportedBinding() {
+ return "U";
+ }
+
+ private String getDeviceType() {
+ return "Demo";
+ }
+
+ private String getHardwareVersion() {
+ return "1.0.1";
+ }
+
+ private String getSoftwareVersion() {
+ return "1.0.2";
+ }
+
+ private int getBatteryStatus() {
+ return RANDOM.nextInt(7);
+ }
+
+ private long getMemoryTotal() {
+ return Runtime.getRuntime().totalMemory() / 1024;
+ }
+
+ @Override
+ public List getAvailableResourceIds(ObjectModel model) {
+ return supportedResources;
+ }
+
+ @Override
+ public void destroy() {
+ }
+}
diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/0.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/0.xml
new file mode 100644
index 0000000000..81e8523880
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/0.xml
@@ -0,0 +1,405 @@
+
+
+
+
+
+
+
diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/1.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/1.xml
new file mode 100644
index 0000000000..f31e839c96
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/1.xml
@@ -0,0 +1,360 @@
+
+
+
+
+
+
+
diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/2.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/2.xml
new file mode 100644
index 0000000000..4ea5805b36
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/2.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/3.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/3.xml
new file mode 100644
index 0000000000..e71c2045c2
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/3.xml
@@ -0,0 +1,331 @@
+
+
+
+
+
+
+
diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/5.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/5.xml
new file mode 100644
index 0000000000..ddcea323b0
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/5.xml
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
diff --git a/msa/black-box-tests/src/test/resources/tb-transports/lwm2m/conf/logback.xml b/msa/black-box-tests/src/test/resources/tb-transports/lwm2m/conf/logback.xml
new file mode 100644
index 0000000000..f8ecd5d96f
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/tb-transports/lwm2m/conf/logback.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+ /var/log/tb-lwm2m-transport/${TB_SERVICE_ID}/tb-lwm2m-transport.log
+
+ /var/log/tb-lwm2m-transport/${TB_SERVICE_ID}/tb-lwm2m-transport.%d{yyyy-MM-dd}.%i.log
+ 100MB
+ 30
+ 3GB
+
+
+ %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+ %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/msa/tb/README.md b/msa/tb/README.md
index 6987c42bfa..2ece5a3e2b 100644
--- a/msa/tb/README.md
+++ b/msa/tb/README.md
@@ -21,7 +21,7 @@ In this example `thingsboard/tb` image will be used. You can choose any other im
Execute the following command to run this docker directly:
`
-$ docker run -it -p 9090:9090 -p 1883:1883 -p 5683:5683/udp -p 5685:5685/udp -v ~/.mytb-data:/data --name mytb thingsboard/tb
+$ docker run -it -p 9090:9090 -p 1883:1883 -p 5683:5683/udp -p 5685:5685/udp -p 5686:5686/udp -v ~/.mytb-data:/data --name mytb thingsboard/tb
`
Where:
@@ -32,6 +32,7 @@ Where:
- `-p 1883:1883` - connect local port 1883 to exposed internal MQTT port 1883
- `-p 5683:5683` - connect local port 5683 to exposed internal COAP port 5683
- `-p 5685:5685` - connect local port 5685 to exposed internal COAP port 5685 (lwm2m)
+- `-p 5686:5686` - connect local port 5686 to exposed internal COAPS port 5686 (lwm2m)
- `-v ~/.mytb-data:/data` - mounts the host's dir `~/.mytb-data` to ThingsBoard DataBase data directory
- `--name mytb` - friendly local name of this machine
- `thingsboard/tb` - docker image, can be also `thingsboard/tb-postgres` or `thingsboard/tb-cassandra`
@@ -47,6 +48,7 @@ Where:
> $ VBoxManage controlvm "default" natpf1 "tcp-port1883,tcp,,1883,,1883"
> $ VBoxManage controlvm "default" natpf1 "tcp-port5683,tcp,,5683,,5683"
> $ VBoxManage controlvm "default" natpf1 "tcp-port5683,tcp,,5685,,5685"
+> $ VBoxManage controlvm "default" natpf1 "tcp-port5683,tcp,,5686,,5686"
> ```
After executing `docker run` command you can open `http://{your-host-ip}:9090` in you browser (for ex. `http://localhost:9090`). You should see ThingsBoard login page.
diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile
index 3d4997dd88..18071b249b 100644
--- a/msa/tb/docker-cassandra/Dockerfile
+++ b/msa/tb/docker-cassandra/Dockerfile
@@ -87,6 +87,7 @@ EXPOSE 9090
EXPOSE 1883
EXPOSE 5683/udp
EXPOSE 5685/udp
+EXPOSE 5686/udp
VOLUME ["/data"]
diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile
index dcf766f00e..984e390496 100644
--- a/msa/tb/docker-postgres/Dockerfile
+++ b/msa/tb/docker-postgres/Dockerfile
@@ -69,6 +69,7 @@ EXPOSE 9090
EXPOSE 1883
EXPOSE 5683/udp
EXPOSE 5685/udp
+EXPOSE 5686/udp
VOLUME ["/data"]