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 @@ + + + + + + + LWM2M Security + + 0 + urn:oma:lwm2m:oma:0:1.2 + 1.1 + 1.2 + Multiple + Mandatory + + + LWM2M Server URI + + Single + Mandatory + String + 0..255 + + + + + Bootstrap-Server + + Single + Mandatory + Boolean + + + + + + Security Mode + + Single + Mandatory + Integer + 0..4 + + + + + Public Key or Identity + + Single + Mandatory + Opaque + + + + + + Server Public Key + + Single + Mandatory + Opaque + + + + + + Secret Key + + Single + Mandatory + Opaque + + + + + + SMS Security Mode + + Single + Optional + Integer + 0..255 + + + + + SMS Binding Key Parameters + + Single + Optional + Opaque + 6 + + + + + SMS Binding Secret Key(s) + + Single + Optional + Opaque + 16,32,48 + + + + + LwM2M Server SMS Number + + Single + Optional + String + + + + + + Short Server ID + + Single + Optional + Integer + 1..65534 + + + + + Client Hold Off Time + + Single + Optional + Integer + + s + + + + Bootstrap-Server Account Timeout + + Single + Optional + Integer + + s + + + + Matching Type + + Single + Optional + Integer + 0..3 + + + + + SNI + + Single + Optional + String + + + + + + Certificate Usage + + Single + Optional + Integer + 0..3 + + + + + DTLS/TLS Ciphersuite + + Multiple + Optional + Integer + + + + + OSCORE Security Mode + + Single + Optional + Objlnk + + + + + + Groups To Use by Client + + Multiple + Optional + Integer + 0..65535 + + + + + Signature Algorithms Supported by Server + + Multiple + Optional + Integer + 0..65535 + + + + Signature Algorithms To Use by Client + + Multiple + Optional + Integer + 0..65535 + + + + + Signature Algorithm Certs Supported by Server + + Multiple + Optional + Integer + 0..65535 + + + + + TLS 1.3 Features To Use by Client + + Single + Optional + Integer + 0..65535 + + + + + TLS Extensions Supported by Server + + Single + Optional + Integer + 0..65535 + + + + + TLS Extensions To Use by Client + + Single + Optional + Integer + 0..65535 + + + + + Secondary LwM2M Server URI + + Multiple + Optional + String + 0..255 + + + + MQTT Server + + Single + Optional + Objlnk + + + + + LwM2M COSE Security + + Multiple + Optional + Objlnk + + + + + RDS Destination Port + + Single + Optional + Integer + 0..15 + + + + RDS Source Port + + Single + Optional + Integer + 0..15 + + + + RDS Application ID + + Single + Optional + String + + + + + + + + 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 @@ + + + + + + + LwM2M Server + + 1 + urn:oma:lwm2m:oma:1:1.2 + 1.2 + 1.2 + Multiple + Mandatory + + + Short Server ID + R + Single + Mandatory + Integer + 1..65534 + + + + + Lifetime + RW + Single + Mandatory + Integer + + s + + + + Default Minimum Period + RW + Single + Optional + Integer + + s + + + + Default Maximum Period + RW + Single + Optional + Integer + + s + + + + Disable + E + Single + Optional + + + + + + + Disable Timeout + RW + Single + Optional + Integer + + s + + + + Notification Storing When Disabled or Offline + RW + Single + Mandatory + Boolean + + + + + + Binding + RW + Single + Mandatory + String + + + + + + Registration Update Trigger + E + Single + Mandatory + + + + + + + Bootstrap-Request Trigger + E + Single + Optional + + + + + + + APN Link + RW + Single + Optional + Objlnk + + + + + + TLS-DTLS Alert Code + R + Single + Optional + Integer + 0..255 + + + + + Last Bootstrapped + R + Single + Optional + Time + + + + + + Registration Priority Order + R + Single + Optional + Integer + + + + + + Initial Registration Delay Timer + RW + Single + Optional + Integer + + s + + + + Registration Failure Block + R + Single + Optional + Boolean + + + + + + Bootstrap on Registration Failure + R + Single + Optional + Boolean + + + + + + Communication Retry Count + RW + Single + Optional + Integer + + + + + + Communication Retry Timer + RW + Single + Optional + Integer + + s + + + + Communication Sequence Delay Timer + RW + Single + Optional + Integer + + s + + + + Communication Sequence Retry Count + RW + Single + Optional + Integer + + + + + + Trigger + RW + Single + Optional + Boolean + + + + + + Preferred Transport + RW + Single + Optional + String + The possible values are those listed in the LwM2M Core Specification + + + + Mute Send + RW + Single + Optional + Boolean + + + + + + Alternate APN Links + RW + Multiple + Optional + Objlnk + + + + + + Supported Server Versions + RW + Multiple + Optional + String + + + + + + Default Notification Mode + RW + Single + Optional + Integer + 0..1 + + + + + Profile ID Hash Algorithm + RW + Single + Optional + Integer + 0..255 + + + + + + + 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 @@ + + + + + + + LwM2M Access Control + + 2 + urn:oma:lwm2m:oma:2:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Object ID + R + Single + Mandatory + Integer + 1..65534 + + + + + Object Instance ID + R + Single + Mandatory + Integer + 0..65535 + + + + + ACL + RW + Multiple + Optional + Integer + 0..31 + + + + + Access Control Owner + RW + Single + Mandatory + Integer + 0..65535 + + + + + + + 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 @@ + + + + + + + Device + + 3 + urn:oma:lwm2m:oma:3:1.0 + 1.1 + 1.2 + Single + Mandatory + + + Manufacturer + R + Single + Optional + String + + + + + + Model Number + R + Single + Optional + String + + + + + + Serial Number + R + Single + Optional + String + + + + + + Firmware Version + R + Single + Optional + String + + + + + + Reboot + E + Single + Mandatory + + + + + + + Factory Reset + E + Single + Optional + + + + + + + Available Power Sources + R + Multiple + Optional + Integer + 0..7 + + + + + Power Source Voltage + R + Multiple + Optional + Integer + + + + + + Power Source Current + R + Multiple + Optional + Integer + + + + + + Battery Level + R + Single + Optional + Integer + 0..100 + /100 + + + + Memory Free + R + Single + Optional + Integer + + + + + + Error Code + R + Multiple + Mandatory + Integer + 0..32 + + + + + Reset Error Code + E + Single + Optional + + + + + + + Current Time + RW + Single + Optional + Time + + + + + + UTC Offset + RW + Single + Optional + String + + + + + + Timezone + RW + Single + Optional + String + + + + + + Supported Binding and Modes + R + Single + Mandatory + String + + + + + Device Type + R + Single + Optional + String + + + + + Hardware Version + R + Single + Optional + String + + + + + Software Version + R + Single + Optional + String + + + + + Battery Status + R + Single + Optional + Integer + 0..6 + + + + Memory Total + R + Single + Optional + Integer + + + + + ExtDevInfo + R + Multiple + Optional + Objlnk + + + + + + + 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 @@ + + + + + + + Firmware Update V_1_0 + + 5 + urn:oma:lwm2m:oma:5 + 1.0 + 1.2 + Single + Optional + + + Package + W + Single + Mandatory + Opaque + + + + + + Package URI + RW + Single + Mandatory + String + 0..255 + + + + + Update + E + Single + Mandatory + + + + + + + State + R + Single + Mandatory + Integer + 0..3 + + + + + Update Result + R + Single + Mandatory + Integer + 0..9 + + + + + PkgName + R + Single + Optional + String + 0..255 + + + + + PkgVersion + R + Single + Optional + String + 0..255 + + + + + Firmware Update Protocol Support + R + Multiple + Optional + Integer + 0..5 + + + + + Firmware Update Delivery Method + R + Single + Mandatory + Integer + 0..2 + + + + + + + diff --git a/docker/tb-transports/coap/conf/logback.xml b/msa/black-box-tests/src/test/resources/tb-transports/lwm2m/conf/logback.xml similarity index 86% rename from docker/tb-transports/coap/conf/logback.xml rename to msa/black-box-tests/src/test/resources/tb-transports/lwm2m/conf/logback.xml index 22621a93de..f8ecd5d96f 100644 --- a/docker/tb-transports/coap/conf/logback.xml +++ b/msa/black-box-tests/src/test/resources/tb-transports/lwm2m/conf/logback.xml @@ -21,10 +21,10 @@ - /var/log/tb-coap-transport/${TB_SERVICE_ID}/tb-coap-transport.log + /var/log/tb-lwm2m-transport/${TB_SERVICE_ID}/tb-lwm2m-transport.log - /var/log/tb-coap-transport/${TB_SERVICE_ID}/tb-coap-transport.%d{yyyy-MM-dd}.%i.log + /var/log/tb-lwm2m-transport/${TB_SERVICE_ID}/tb-lwm2m-transport.%d{yyyy-MM-dd}.%i.log 100MB 30 3GB @@ -47,6 +47,8 @@ + + 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"]