diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 338cf166ae..226ef0dc57 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -33,12 +33,10 @@ import org.eclipse.leshan.server.registration.Registration; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.jupiter.api.TestInstance; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; @@ -86,6 +84,7 @@ import org.thingsboard.server.transport.lwm2m.server.client.ResourceUpdateResult import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; +import java.io.IOException; import java.net.ServerSocket; import java.util.ArrayList; import java.util.Arrays; @@ -124,8 +123,6 @@ import static org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegra @Slf4j @DaoSqlTest -//@TestInstance(TestInstance.Lifecycle.PER_CLASS) -//@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @TestPropertySource(properties = { "transport.lwm2m.enabled=true" }) @@ -149,8 +146,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public static final String host = "localhost"; public static final String hostBs = "localhost"; public static final Integer shortServerId = 123; - public static final Integer shortServerIdBs0 = 0; - public static final int serverId = 1; public static final String COAP = "coap://"; public static final String COAPS = "coaps://"; @@ -320,17 +315,10 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte @After public void after() throws Exception { - this.clientDestroy(true); - + this.clientDestroy(); if (executor != null && !executor.isShutdown()) { executor.shutdownNow(); - if (!executor.awaitTermination(2, TimeUnit.SECONDS)) { - log.warn("Executor did not terminate cleanly, forcing GC"); - } } - Thread.sleep(300); - System.gc(); - log.warn("Test lwm2m after completed: {}", this.getClass().getSimpleName()); } private void init() throws Exception { @@ -574,7 +562,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public void createNewClient(Security security, Security securityBs, boolean isRpc, String endpoint, Integer clientDtlsCidLength, boolean queueMode, String deviceIdStr, Integer value3_0_9) throws Exception { - this.clientDestroy(false); + this.clientDestroy(); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint, resources); try (ServerSocket socket = new ServerSocket(0)) { @@ -661,13 +649,30 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte } - private void clientDestroy(boolean isAfter) { + private void clientDestroy() { try { if (lwM2MTestClient != null && lwM2MTestClient.getLeshanClient() != null) { - if (isAfter) { - sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); - awaitDeleteDevice(lwM2MTestClient.getDeviceIdStr()); + boolean serverAlive = false; + for (int port = AbstractLwM2MIntegrationTest.port; port <= securityPortBs; port++) { + try (ServerSocket socket = new ServerSocket(port)) { + log.info("Port {} is free.", port); + } catch (IOException e) { + log.debug("Port {} is busy — CoAP server still active.", port); + serverAlive = true; + break; + } } + if (serverAlive) { + try { + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); + awaitDeleteDevice(lwM2MTestClient.getDeviceIdStr()); + } catch (Exception e) { + log.warn("Failed to cleanup LwM2M observations before destroy: {}", e.getMessage()); + } + } else { + log.info("No active CoAP server found on ports 5685–5688. Skipping observe cleanup."); + } + lwM2MTestClient.destroy(); } } catch (Exception e) { @@ -718,7 +723,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte protected AbstractLwM2MBootstrapServerCredential getBootstrapServerCredentialNoSec(boolean isBootstrap) { AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential(); bootstrapServerCredential.setServerPublicKey(""); - bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs0 : shortServerId); + bootstrapServerCredential.setShortServerId(isBootstrap ? null : shortServerId); bootstrapServerCredential.setBootstrapServerIs(isBootstrap); bootstrapServerCredential.setHost(isBootstrap ? hostBs : host); bootstrapServerCredential.setPort(isBootstrap ? portBs : port); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 3bb127d736..40fb6d3ff0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -70,6 +70,7 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashMap; @@ -141,7 +142,7 @@ public class LwM2MTestClient { private LwM2mTemperatureSensor lwM2mTemperatureSensor12; private String deviceIdStr; - public void init(Security security, Security securityBs, int port, boolean isRpc, + public void init(Security securityLwm2m, Security securityBs, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, LwM2mClientContext clientContext, Integer cIdLength, boolean queueMode, boolean supportFormatOnly_SenMLJSON_SenMLCBOR, Integer value3_0_9) throws InvalidDDFFileException, IOException { @@ -151,38 +152,31 @@ public class LwM2MTestClient { ObjectsInitializer initializer = createFreshInitializer(); - forceNullSecurityId(securityBs); - forceNullSecurityId(security); - if (securityBs != null && security != null) { - // SECURITIES -// securityBs.setId(0); -// security.setId(1); - - LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{securityBs, security}; - initializer.setInstancesForObject(SECURITY, instances); - log.warn("Security BS section: securityBsId [{}] Security Lwm2m section: securityLwm2mId [{}] ", securityBs.getId(), security.getId()); - // SERVER - Server lwm2mServer = new Server(shortServerId, TimeUnit.MINUTES.toSeconds(60)); -// lwm2mServer.setId(0); - instances = new LwM2mInstanceEnabler[]{lwm2mServer}; - initializer.setInstancesForObject(SERVER, instances); + // SECURITY + if (securityLwm2m != null && securityLwm2m.getId() != null) { + forceNullSecurityId(securityLwm2m); + } + if (securityBs!= null && securityBs.getId() != null) { + forceNullSecurityId(securityBs); + } + if (securityBs != null && securityLwm2m != null) { + log.warn("Security Both: securityBs: [{}] and security Lwm2m [{}]", securityBs.getId(), securityLwm2m.getId()); + initializer.setInstancesForObject(SECURITY, securityBs, securityLwm2m); } else if (securityBs != null) { - // SECURITY + log.warn("Security BS only: securityBs: [{}] ", securityBs.getId()); initializer.setInstancesForObject(SECURITY, securityBs); - // SERVER - initializer.setClassForObject(SERVER, Server.class); - log.warn("Security BS section: securityBsId [{}] ", securityBs.getId()); - } else { + } else if (securityLwm2m != null){ // SECURITY - initializer.setInstancesForObject(SECURITY, security); - // SERVER - Server lwm2mServer = new Server(shortServerId, TimeUnit.MINUTES.toSeconds(60)); -// lwm2mServer.setId(0); - initializer.setInstancesForObject(SERVER, lwm2mServer); - log.warn("Security Lwm2m section: securityLwm2mId [{}] Server Lwm2m section: securityLwm2mId [{}] ", security.getId(), lwm2mServer.getId()); + log.warn("Security Lwm2m only: security Lwm2m [{}]", securityLwm2m.getId()); + initializer.setInstancesForObject(SECURITY, securityLwm2m); } - + // SERVER + Server serverLwm2m = new Server(shortServerId, TimeUnit.MINUTES.toSeconds(60)); + initializer.setInstancesForObject(SERVER, serverLwm2m); + // DEVICE initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice(executor, value3_0_9)); + + // OTHER t initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice()); initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice()); initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class); @@ -429,25 +423,43 @@ public class LwM2MTestClient { public void destroy() { if (leshanClient != null) { - leshanClient.destroy(true); - } - if (lwM2MDevice != null) { - lwM2MDevice.destroy(); - } - if (fwLwM2MDevice != null) { - fwLwM2MDevice.destroy(); - } - if (swLwM2MDevice != null) { - swLwM2MDevice.destroy(); - } - if (lwM2MBinaryAppDataContainer != null) { - lwM2MBinaryAppDataContainer.destroy(); + try { + leshanClient.destroy(true); + } catch (Exception e) { + log.warn("Failed to destroy Leshan client", e); + } finally { + leshanClient = null; + } } - if (lwM2MTemperatureSensor != null) { - lwM2MTemperatureSensor.destroy(); + + // ThingsBoard custom LwM2M objects + destroySafe(lwM2MDevice); + destroySafe(fwLwM2MDevice); + destroySafe(swLwM2MDevice); + destroySafe(lwM2MBinaryAppDataContainer); + destroySafe(lwM2MTemperatureSensor); + + lwM2MDevice = null; + fwLwM2MDevice = null; + swLwM2MDevice = null; + lwM2MBinaryAppDataContainer = null; + lwM2MTemperatureSensor = null; + } + + + private void destroySafe(Object obj) { + if (obj == null) return; + try { + Method destroy = obj.getClass().getMethod("destroy"); + destroy.invoke(obj); + } catch (NoSuchMethodException e) { + // не має destroy() — ігноруємо + } catch (Exception e) { + log.warn("Failed to destroy {}", obj.getClass().getSimpleName(), e); } } + public void start(boolean isStartLw) { if (leshanClient != null) { leshanClient.start(); @@ -470,8 +482,7 @@ public class LwM2MTestClient { } private ObjectsInitializer createFreshInitializer() { -// List models = new ArrayList<>(ObjectLoader.loadAllDefault()); - List models = ObjectLoader.loadAllDefault(); + List models = new ArrayList<>(ObjectLoader.loadAllDefault()); for (String resourceName : lwm2mClientResources) { try (InputStream in = LwM2MTestClient.class.getClassLoader() .getResourceAsStream("lwm2m/" + resourceName)) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index 264c3b84c7..a16b782acf 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -96,7 +96,6 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M protected static final String SERVER_STORE_PWD = "server_ks_password"; protected static final String SERVER_CERT_ALIAS = "server"; protected static final String SERVER_CERT_ALIAS_BS = "bootstrap"; - protected static final Security SECURITY_NO_SEC_BS = noSecBootstrap(URI_BS);; protected final X509Certificate serverX509Cert; // server certificate signed by rootCA protected final X509Certificate serverX509CertBs; // serverBs certificate signed by rootCA protected final PublicKey serverPublicKeyFromCert; // server public key used for RPK @@ -179,14 +178,14 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M defaultBootstrapCredentials.setLwm2mServer(serverCredentials); } - public void basicTestConnectionBefore(String clientEndpoint, - String awaitAlias, - LwM2MProfileBootstrapConfigType type, - Set expectedStatuses, - LwM2MClientState finishState) throws Exception { + public void basicTestConnectionStartBS(String clientEndpoint, + String awaitAlias, + LwM2MProfileBootstrapConfigType type, + Set expectedStatuses, + LwM2MClientState finishState) throws Exception { Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(type)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); - this.basicTestConnection(null , SECURITY_NO_SEC_BS, + this.basicTestConnection(null , noSecBootstrap(URI_BS), deviceCredentials, clientEndpoint, transportConfiguration, @@ -244,7 +243,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); this.basicTestConnectionBootstrapRequestTrigger( SECURITY_NO_SEC, - SECURITY_NO_SEC_BS, + noSecBootstrap(URI_BS), deviceCredentials, clientEndpoint, transportConfiguration, @@ -360,7 +359,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M default: throw new IllegalStateException("Unexpected value: " + mode); } - bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs0 : shortServerId); + bootstrapServerCredential.setShortServerId(isBootstrap ? null : shortServerId); bootstrapServerCredential.setBootstrapServerIs(isBootstrap); bootstrapServerCredential.setHost(isBootstrap ? hostBs : host); bootstrapServerCredential.setPort(isBootstrap ? securityPortBs : securityPort); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSNoTriggerTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSNoTriggerTest.java index edfe805cf8..b218c39aec 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSNoTriggerTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSNoTriggerTest.java @@ -27,13 +27,13 @@ public class NoSecLwM2MIntegrationBSNoTriggerTest extends AbstractSecurityLwM2MI public void testWithNoSecConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception { String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "NoTrigger" + BOTH.name(); String awaitAlias = "await on client state (NoSecBS two section)"; - basicTestConnectionBefore(clientEndpoint, awaitAlias, BOTH, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS); + basicTestConnectionStartBS(clientEndpoint, awaitAlias, BOTH, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS); } @Test public void testWithNoSecConnectBsSuccess_UpdateLwm2mSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "NoTrigger" + LWM2M_ONLY.name(); String awaitAlias = "await on client state (NoSecBS Lwm2m section)"; - basicTestConnectionBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS); + basicTestConnectionStartBS(clientEndpoint, awaitAlias, LWM2M_ONLY, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index e4bf935306..275383104e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -58,8 +58,7 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes Hex.decodeHex(keyPsk.toCharArray())); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); - this.basicTestConnection(security, - null, + this.basicTestConnection(security, null, deviceCredentials, clientEndpoint, transportConfiguration, @@ -85,8 +84,7 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_ONE_OBSERVE, getBootstrapServerCredentialsSecure(PSK, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); String awaitAlias = "await on client state (Psk_Lwm2m)"; - Device lwm2mDevice = this.basicTestConnection(security, - null, + Device lwm2mDevice = this.basicTestConnection(security, null, deviceCredentials, clientEndpoint, transportConfiguration, @@ -121,8 +119,7 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_ONE_OBSERVE, getBootstrapServerCredentialsSecure(PSK, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); String awaitAlias = "await on client state (Psk_Lwm2m)"; - Device lwm2mDevice = this.basicTestConnection(security, - null, + Device lwm2mDevice = this.basicTestConnection(security, null, deviceCredentials, clientEndpoint, transportConfiguration, diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java index d1576ede1b..fd8a51b041 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java @@ -118,8 +118,7 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg serverX509CertBs.getEncoded()); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); - this.basicTestConnection(security, - null, + this.basicTestConnection(security, null, deviceCredentials, clientEndpoint, transportConfiguration, diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java index 81b708ba33..b7a6290741 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java @@ -50,8 +50,7 @@ public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegra serverX509Cert.getEncoded()); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); - this.basicTestConnection(security, - null, + this.basicTestConnection(security, null, deviceCredentials, clientEndpoint, transportConfiguration, @@ -77,8 +76,7 @@ public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegra serverX509CertBs.getEncoded()); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); - this.basicTestConnection(security, - null, + this.basicTestConnection(security,null, deviceCredentials, clientEndpoint, transportConfiguration, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/Lwm2mServerIdentifier.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/Lwm2mServerIdentifier.java index a9f81ab655..95aa95597f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/Lwm2mServerIdentifier.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/Lwm2mServerIdentifier.java @@ -23,22 +23,21 @@ package org.thingsboard.server.common.data.device.credentials.lwm2m; public enum Lwm2mServerIdentifier { /** - * Bootstrap Short Server ID (0). - * Reserved for the Bootstrap Server — used exclusively during the bootstrap phase. + * Not used for identifying an LwM2M Server (0). */ - BOOTSTRAP(0, "Bootstrap Short Server ID", true), + NOT_USED_IDENTIFYING_LWM2M_SERVER_MIN(0, "Bootstrap Short Server ID", false), /** * Primary LwM2M Server Short Server ID (1). * Upper boundary for valid LwM2M Server Identifiers (1–65534). */ - PRIMARY_LWM2M_SERVER(1, "LwM2M Server Short Server ID", false), + PRIMARY_LWM2M_SERVER(1, "LwM2M Server Short Server ID", true), /** * Maximum valid LwM2M Server ID (65534). * Upper boundary for valid LwM2M Server Identifiers (1–65534). */ - LWM2M_SERVER_MAX(65534, "LwM2M Server Short Server ID", false), + LWM2M_SERVER_MAX(65534, "LwM2M Server Short Server ID", true), /** * Not used for identifying an LwM2M Server (65535). @@ -46,16 +45,16 @@ public enum Lwm2mServerIdentifier { * MUST NOT be assigned to any LwM2M Server according to OMA-TS-LightweightM2M-Core, §6.2.1. * OMA LwM2M Core / v1.2: Server / Short Server ID): «MAX_ID 65535 is a reserved value and MUST NOT be used for identifying an Object» */ - NOT_USED_IDENTIFYING_LWM2M_SERVER(65535, "Reserved sentinel value (no active server)", false); + NOT_USED_IDENTIFYING_LWM2M_SERVER_MAX(65535, "Reserved sentinel value (no active server)", false); private final int id; private final String description; - private final boolean isBootstrap; + private final boolean isLwm2mServer; - Lwm2mServerIdentifier(int id, String description, boolean isBootstrap) { + Lwm2mServerIdentifier(int id, String description, boolean isLwm2mServer) { this.id = id; this.description = description; - this.isBootstrap = isBootstrap; + this.isLwm2mServer = isLwm2mServer; } /** @@ -73,50 +72,35 @@ public enum Lwm2mServerIdentifier { } /** - * @return true if this ID represents a Bootstrap Server. + * @return true if this ID represents a Lwm2m Server. */ - public boolean isBootstrap() { - return isBootstrap; - } - - /** - * Checks whether a given numeric ID belongs to the Bootstrap Server (0). - * OMA Spec (LwM2M v1.0 / v1.1): - * Short Server ID Resource (Resource ID: 0) - * The Short Server ID identifies a Server Object Instance. - * The value 0 is reserved for the Bootstrap Server. - * A value between 1 and 65534 identifies a LwM2M Server. - * The value 65535 MUST NOT be used. - * @param id Short Server ID value. - * @return true if id == 0. - */ - public static boolean isBootstrap(int id) { - return id == BOOTSTRAP.id; + public boolean isLwm2mServer() { + return isLwm2mServer; } /** * Checks whether a given ID represents a valid LwM2M Server (1–65534). - * * @param id Short Server ID value. * @return true if the ID belongs to a standard LwM2M Server. */ public static boolean isLwm2mServer(int id) { return id >= PRIMARY_LWM2M_SERVER.id && id <= LWM2M_SERVER_MAX.id; } + public static boolean isNotLwm2mServer(int id) { + return id < PRIMARY_LWM2M_SERVER.id || id > LWM2M_SERVER_MAX.id; + } /** * Checks whether the provided ID is within the valid LwM2M range [0–65535]. - * * @param id ID to check. * @return true if valid, false otherwise. */ public static boolean isValid(int id) { - return id >= 0 && id <= 65535; + return id >= NOT_USED_IDENTIFYING_LWM2M_SERVER_MIN.getId() && id <= NOT_USED_IDENTIFYING_LWM2M_SERVER_MAX.getId(); } /** * Returns a {@link Lwm2mServerIdentifier} instance matching the given ID. - * * @param id numeric ID. * @return corresponding enum constant. * @throws IllegalArgumentException if no constant matches the given ID. diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java index 29c130b902..e5eb4be902 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java @@ -26,7 +26,7 @@ public class LwM2MServerSecurityConfig implements Serializable { @Schema(description = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " + "This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. " + - "The values ID:1 and ID:65534 values MUST NOT be used for identifying the LwM2M Server.", example = "123", accessMode = Schema.AccessMode.READ_ONLY) + "The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", accessMode = Schema.AccessMode.READ_ONLY) protected Integer shortServerId = 123; /** Security -> ObjectId = 0 'LWM2M Security' */ @Schema(description = "Is Bootstrap Server or Lwm2m Server. " + diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java index 517bcabac7..aaca3374a2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java @@ -127,7 +127,9 @@ public class LwM2MBootstrapConfig implements Serializable { private BootstrapConfig.ServerConfig setServerConfig (AbstractLwM2MBootstrapServerCredential serverCredential) { BootstrapConfig.ServerConfig serverConfig = new BootstrapConfig.ServerConfig(); - serverConfig.shortId = serverCredential.getShortServerId(); + if (serverCredential.getShortServerId() != null) { + serverConfig.shortId = serverCredential.getShortServerId(); + } serverConfig.lifetime = serverCredential.getLifetime(); serverConfig.defaultMinPeriod = serverCredential.getDefaultMinPeriod(); serverConfig.notifIfDisabled = serverCredential.isNotifIfDisabled(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java index 33d1fb207c..7020869130 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java @@ -48,9 +48,9 @@ import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest; -import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.BOOTSTRAP; import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.LWM2M_SERVER_MAX; import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.PRIMARY_LWM2M_SERVER; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.isLwm2mServer; @Slf4j public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTaskProvider { @@ -147,7 +147,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask log.error("Invalid lwm2mSecurityInstance [{}] by short server id [{}]", path.getObjectInstanceId(), lwm2mShortServerId); } } else { - this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().putIfAbsent(BOOTSTRAP.getId(), path.getObjectInstanceId()); + this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().putIfAbsent(0, path.getObjectInstanceId()); } } else if (path.getObjectId() == 1) { if (link.getAttributes().get("ssid") != null) { @@ -186,35 +186,35 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask } - /** Map - * 1) Only Lwm2m Server - * - Short Server ID == 1 - 65534 lwm2m) + /** Map => LwM2MBootstrapClientInstanceIds + * 1) Both + * - Short Server ID == null bs) * SECURITY = 0; InstanceId = 0 + * - Short Server ID == 1 - 65534 lwm2m) + * SECURITY = 0; InstanceId = 1 * SERVER = 1; InstanceId = 0 - * 2) Both - * - Short Server ID == 0 bs) + * 2) Only BS Server + * - Short Server ID == null bs) * SECURITY = 0; InstanceId = 0 - * SERVER = 1; InstanceId = null + * 3) Only Lwm2m Server * - Short Server ID == 1 - 65534 lwm2m) - * SECURITY = 0; InstanceId = 1 + * SECURITY = 0; InstanceId = 0 * SERVER = 1; InstanceId = 0 * */ public List> toRequests(BootstrapConfig bootstrapConfigNew, ContentFormat contentFormat, String endpoint) { - Integer bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP.getId()) == null ? - 0 : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP.getId()); + Integer bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(null) == null ? + -2 : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(null); List> requests = new ArrayList<>(); Set pathsDelete = new HashSet<>(); ConcurrentHashMap> requestsWrite = new ConcurrentHashMap<>(); /// handle security & handle - int lwm2mSecurityInstanceIdMax = -1; - int lwm2mServerInstanceIdMax = -1; // bootstrap Security new - There can only be one instance of bootstrap at a time. /// bs: handle security only for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfigNew.security).values()) { - if (security.bootstrapServer && security.serverId == BOOTSTRAP.getId()) { + if (security.bootstrapServer && bootstrapSecurityInstanceId > -1) { // delete old bootstrap Security String path = "/" + SECURITY + "/" + bootstrapSecurityInstanceId; pathsDelete.add(path); @@ -230,19 +230,21 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask /// lwm2m server: handle security & server //max Lwm2m Security instance old id if new + int lwm2mSecurityInstanceIdMax = -1; for (Integer shortId : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().keySet()) { - if (shortId >= BOOTSTRAP.getId() && shortId <= LWM2M_SERVER_MAX.getId()) { - lwm2mSecurityInstanceIdMax = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(shortId) > - lwm2mSecurityInstanceIdMax ? this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(shortId) : - lwm2mSecurityInstanceIdMax; + if (isLwm2mServer(shortId)) { + lwm2mSecurityInstanceIdMax = Math.max( + this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(shortId), + lwm2mSecurityInstanceIdMax); } } //max Lwm2m Server instance old id if new + int lwm2mServerInstanceIdMax = -1; for (Integer shortId : this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().keySet()) { - if (shortId >= PRIMARY_LWM2M_SERVER.getId() && shortId <= LWM2M_SERVER_MAX.getId()) { - lwm2mServerInstanceIdMax = this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(shortId) > - lwm2mServerInstanceIdMax ? this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(shortId) : - lwm2mServerInstanceIdMax; + if (isLwm2mServer(shortId)) { + lwm2mServerInstanceIdMax = Math.max( + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(shortId), + lwm2mServerInstanceIdMax); } } // Lwm2m update or new diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java index 113c3451e0..3916849da3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java @@ -21,9 +21,9 @@ import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; import java.util.Map; -import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.BOOTSTRAP; -import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.NOT_USED_IDENTIFYING_LWM2M_SERVER; -import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.isLwm2mServer; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.NOT_USED_IDENTIFYING_LWM2M_SERVER_MIN; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.NOT_USED_IDENTIFYING_LWM2M_SERVER_MAX; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.isNotLwm2mServer; public class LwM2MConfigurationChecker extends ConfigurationChecker { @@ -78,15 +78,16 @@ public class LwM2MConfigurationChecker extends ConfigurationChecker { * This Resource MUST be set when the Bootstrap-Server Resource has false value. * Specific ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server (Section 6.3 of the LwM2M version 1.0 specification). */ - if (!security.bootstrapServer && !isLwm2mServer(srvCfg.shortId)) { - throw new InvalidConfigurationException("Specific ID:" + BOOTSTRAP.getId() + " and ID:" + NOT_USED_IDENTIFYING_LWM2M_SERVER.getId() + " values MUST NOT be used for identifying the LwM2M Server"); + if (!security.bootstrapServer && isNotLwm2mServer(srvCfg.shortId)) { + throw new InvalidConfigurationException("Specific ID:" + NOT_USED_IDENTIFYING_LWM2M_SERVER_MIN.getId() + " and ID:" + NOT_USED_IDENTIFYING_LWM2M_SERVER_MAX.getId() + " values MUST NOT be used for identifying the LwM2M Server"); } } } protected static BootstrapConfig.ServerSecurity getSecurityEntry(BootstrapConfig config, int shortId) { for (Map.Entry es : config.security.entrySet()) { - if (es.getValue().serverId == shortId) { + if ((es.getValue().serverId == null && shortId == 0) || + (es.getValue().serverId != null && es.getValue().serverId == shortId)) { return es.getValue(); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index 431b623da9..c3d02b8ce1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -853,7 +853,8 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl ResourceUpdateResult updateResource = new ResourceUpdateResult(lwM2MClient); request.getObjectInstances().forEach(instance -> instance.getResources().forEach((resId, lwM2mResource) ->{ - this.updateResourcesValue(updateResource, lwM2mResource, versionId + "/" + resId, Mode.REPLACE, 0); + String path = versionId.endsWith("/") ? versionId + resId : versionId + "/" + resId; + this.updateResourcesValue(updateResource, lwM2mResource, path, Mode.REPLACE, 0); }) ); clientContext.update(lwM2MClient); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 2ea8c343d7..ac600f9201 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -69,10 +69,9 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.BOOTSTRAP; import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.LWM2M_SERVER_MAX; import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.PRIMARY_LWM2M_SERVER; -import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.isLwm2mServer; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.isNotLwm2mServer; @Slf4j @Component @@ -342,19 +341,18 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator