From 988495b4593b3ea409513c4d5c54caf49e7b8826 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Tue, 30 Sep 2025 13:16:22 +0300 Subject: [PATCH 01/16] fix_lwm2m: add to Front bootstrap Short Server id 0 or 65535 --- application/src/main/resources/thingsboard.yml | 2 +- .../lwm2m-device-config-server.component.html | 5 +++-- .../lwm2m/lwm2m-device-config-server.component.ts | 14 ++++++-------- .../src/assets/locale/locale.constant-en_US.json | 1 + 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 944520c4e5..69f0b29b74 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1193,7 +1193,7 @@ transport: # Enable/disable Bootstrap Server enabled: "${LWM2M_ENABLED_BS:true}" # Default value in LwM2M client after start in mode Bootstrap for the object : name "LWM2M Security" field: "Short Server ID" (deviceProfile: Bootstrap.BOOTSTRAP SERVER.Short ID) - id: "${LWM2M_SERVER_ID_BS:111}" + id: "${LWM2M_SERVER_ID_BS:0}" # LwM2M bootstrap server bind address. Bind to all interfaces by default bind_address: "${LWM2M_BS_BIND_ADDRESS:0.0.0.0}" # LwM2M bootstrap server bind port diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html index e4d5135e74..fc1f6adf47 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html @@ -58,12 +58,13 @@ {{ 'device-profile.lwm2m.short-id' | translate }} help - + + {{ 'device-profile.lwm2m.short-id-required' | translate }} - {{ 'device-profile.lwm2m.short-id-pattern' | translate }} + {{ (isBootstrap ? 'device-profile.lwm2m.short-id-pattern-bs' : 'device-profile.lwm2m.short-id-pattern') | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts index 6c0f87c058..b2c5a2fc07 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts @@ -74,8 +74,10 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc currentSecurityMode = null; bootstrapDisabled = false; - shortServerIdMin = 1; - shortServerIdMax = 65534; + readonly shortServerIdMin = 1; + readonly shortServerIdMax = 65534; + readonly shortServerIdBsMin = 0; + readonly shortServerIdBsMax = 65535; @Input() @coerceBoolean() @@ -94,18 +96,13 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc } ngOnInit(): void { - if (this.isBootstrap) { - this.shortServerIdMin = 0; - this.shortServerIdMax = 65535; - } this.serverFormGroup = this.fb.group({ host: ['', Validators.required], port: ['', [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]], securityMode: [Lwm2mSecurityType.NO_SEC], serverPublicKey: [''], clientHoldOffTime: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - shortServerId: ['', - [Validators.required, Validators.min(this.shortServerIdMin), Validators.max(this.shortServerIdMax), Validators.pattern('[0-9]*')]], + shortServerId: ['', [Validators.required, Validators.pattern(this.isBootstrap ? '^(0|65535)$' : '[0-9]*')]], bootstrapServerAccountTimeout: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], binding: [''], lifetime: [null, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], @@ -129,6 +126,7 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc this.serverFormGroup.get('serverPublicKey').patchValue(serverSecurityConfig.serverCertificate, {emitEvent: false}); } }); + this.serverFormGroup.valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(value => { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 1866ab7d0b..05a1f5ceb3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2302,6 +2302,7 @@ "short-id-required": "Short server ID is required.", "short-id-range": "Short server ID should be in a range from {{ min }} to {{ max }}.", "short-id-pattern": "Short server ID must be a positive integer.", + "short-id-pattern-bs": "Short server ID must be only 0 or 65535", "lifetime": "Client registration lifetime", "lifetime-required": "Client registration lifetime is required.", "lifetime-pattern": "Client registration lifetime must be a positive integer.", From 53ff45d43755343eb26a87fe0322a5f4b9c9307e Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Tue, 30 Sep 2025 14:22:47 +0300 Subject: [PATCH 02/16] fix_lwm2m:comments #1 --- .../lwm2m/lwm2m-device-config-server.component.html | 3 +-- .../device/lwm2m/lwm2m-device-config-server.component.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html index fc1f6adf47..f1cafd7e81 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html @@ -58,8 +58,7 @@ {{ 'device-profile.lwm2m.short-id' | translate }} help - - + {{ 'device-profile.lwm2m.short-id-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts index b2c5a2fc07..7d0ce0f887 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts @@ -98,11 +98,16 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc ngOnInit(): void { this.serverFormGroup = this.fb.group({ host: ['', Validators.required], - port: ['', [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]], + port: ['', [Validators.required, Validators.min(5683), Validators.max(5688), Validators.pattern('[0-9]*')]], securityMode: [Lwm2mSecurityType.NO_SEC], serverPublicKey: [''], clientHoldOffTime: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - shortServerId: ['', [Validators.required, Validators.pattern(this.isBootstrap ? '^(0|65535)$' : '[0-9]*')]], + // shortServerId: ['', [Validators.required, Validators.pattern(this.isBootstrap ? '^(' + this.shortServerIdBsMin+ '|' + this.shortServerIdBsMax + ')$' : '[0-9]*'), !this.isBootstrap ? [Validators.min(this.shortServerIdMin), Validators.max(this.shortServerIdMax)] : [] ]], + shortServerId: ['', [Validators.required, Validators.pattern(this.isBootstrap ? '^(' + this.shortServerIdBsMin+ '|' + this.shortServerIdBsMax + ')$' : '[0-9]*'), + ...(!this.isBootstrap ? [ + Validators.min(this.shortServerIdMin), + Validators.max(this.shortServerIdMax) + ] : []) ]], bootstrapServerAccountTimeout: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], binding: [''], lifetime: [null, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], From 4546597339ddd62d60c068424d7dfae6514ef275 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 30 Sep 2025 15:10:07 +0300 Subject: [PATCH 03/16] UI: Improved validation shortServerId in lwm2m transport configuration --- .../lwm2m/lwm2m-device-config-server.component.html | 4 +++- .../lwm2m/lwm2m-device-config-server.component.ts | 12 +++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html index f1cafd7e81..65a2400e16 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html @@ -58,7 +58,9 @@ {{ 'device-profile.lwm2m.short-id' | translate }} help - + {{ 'device-profile.lwm2m.short-id-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts index 7d0ce0f887..1630a1d220 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts @@ -98,16 +98,14 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc ngOnInit(): void { this.serverFormGroup = this.fb.group({ host: ['', Validators.required], - port: ['', [Validators.required, Validators.min(5683), Validators.max(5688), Validators.pattern('[0-9]*')]], + port: ['', [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]], securityMode: [Lwm2mSecurityType.NO_SEC], serverPublicKey: [''], clientHoldOffTime: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - // shortServerId: ['', [Validators.required, Validators.pattern(this.isBootstrap ? '^(' + this.shortServerIdBsMin+ '|' + this.shortServerIdBsMax + ')$' : '[0-9]*'), !this.isBootstrap ? [Validators.min(this.shortServerIdMin), Validators.max(this.shortServerIdMax)] : [] ]], - shortServerId: ['', [Validators.required, Validators.pattern(this.isBootstrap ? '^(' + this.shortServerIdBsMin+ '|' + this.shortServerIdBsMax + ')$' : '[0-9]*'), - ...(!this.isBootstrap ? [ - Validators.min(this.shortServerIdMin), - Validators.max(this.shortServerIdMax) - ] : []) ]], + shortServerId: ['', this.isBootstrap + ? [Validators.required, Validators.pattern('^(' + this.shortServerIdBsMin+ '|' + this.shortServerIdBsMax + ')$' )] + : [Validators.required, Validators.pattern('[0-9]*'),Validators.min(this.shortServerIdMin), Validators.max(this.shortServerIdMax)] + ], bootstrapServerAccountTimeout: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], binding: [''], lifetime: [null, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], From f19a1ba2a7632a733d7ec909b347f576953db4f6 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Thu, 9 Oct 2025 18:58:27 +0300 Subject: [PATCH 04/16] lwm2m: bootstrap new --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 3 +- .../transport/lwm2m/Lwm2mTestHelper.java | 2 - .../lwm2m/client/LwM2MTestClient.java | 25 +- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 11 +- .../rpc/sql/RpcLwm2mIntegrationReadTest.java | 32 +- .../AbstractSecurityLwM2MIntegrationTest.java | 20 +- .../NoSecLwM2MIntegrationBSNoTriggerTest.java | 40 +++ .../NoSecLwM2MIntegrationBSTriggerTest.java | 61 ++++ .../sql/NoSecLwM2MIntegrationTest.java | 50 --- .../security/sql/PskLwm2mIntegrationTest.java | 4 +- .../security/sql/RpkLwM2MIntegrationTest.java | 4 +- .../sql/X509_NoTrustLwM2MIntegrationTest.java | 5 +- .../LwM2mDefaultBootstrapSessionManager.java | 17 +- ...LwM2MBootstrapConfigStoreTaskProvider.java | 307 ++++++++---------- .../lwm2m/utils/LwM2MTransportUtil.java | 2 + .../lwm2m-device-config-server.component.ts | 2 +- 16 files changed, 313 insertions(+), 272 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSNoTriggerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSTriggerTest.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index ddf8bca43f..50051c08d5 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 @@ -147,7 +147,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public static final Integer shortServerId = 123; public static final Integer shortServerIdBs0 = 0; public static final int serverId = 1; - public static final int serverIdBs = 0; public static final String COAP = "coap://"; public static final String COAPS = "coaps://"; @@ -705,7 +704,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte return bootstrap; } - private AbstractLwM2MBootstrapServerCredential getBootstrapServerCredentialNoSec(boolean isBootstrap) { + protected AbstractLwM2MBootstrapServerCredential getBootstrapServerCredentialNoSec(boolean isBootstrap) { AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential(); bootstrapServerCredential.setServerPublicKey(""); bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs0 : shortServerId); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java index 6159400bb6..ce073d137d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java @@ -24,8 +24,6 @@ public class Lwm2mTestHelper { public static final int TEMPERATURE_SENSOR = 3303; // Ids in Client - public static final int OBJECT_ID_0 = 0; - public static final int OBJECT_ID_1 = 1; public static final int OBJECT_INSTANCE_ID_0 = 0; public static final int OBJECT_INSTANCE_ID_1 = 1; public static final int OBJECT_INSTANCE_ID_2 = 2; 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 2ae1432cac..7c5e2973ac 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 @@ -88,10 +88,7 @@ import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; import static org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder.getDefaultPathEncoder; -import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.serverId; -import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.serverIdBs; import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.shortServerId; -import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.shortServerIdBs0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE; @@ -169,31 +166,29 @@ public class LwM2MTestClient { LwM2mModel model = new StaticModel(models); ObjectsInitializer initializer = new ObjectsInitializer(model); if (securityBs != null && security != null) { - // SECURITY - security.setId(serverId); - securityBs.setId(serverIdBs); + // SECURITIES + securityBs.setId(0); + security.setId(1); LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{securityBs, security}; - initializer.setClassForObject(SECURITY, Security.class); initializer.setInstancesForObject(SECURITY, instances); // SERVER Server lwm2mServer = new Server(shortServerId, TimeUnit.MINUTES.toSeconds(60)); - lwm2mServer.setId(serverId); - Server serverBs = new Server(shortServerIdBs0, TimeUnit.MINUTES.toSeconds(60)); - serverBs.setId(serverIdBs); - instances = new LwM2mInstanceEnabler[]{serverBs, lwm2mServer}; - initializer.setClassForObject(SERVER, Server.class); + lwm2mServer.setId(0); + instances = new LwM2mInstanceEnabler[]{lwm2mServer}; + initializer.setInstancesForObject(SERVER, instances); } else if (securityBs != null) { // SECURITY - initializer.setInstancesForObject(SECURITY, securityBs); - // SERVER + securityBs.setId(0); initializer.setClassForObject(SERVER, Server.class); + initializer.setInstancesForObject(SECURITY, securityBs); } else { // SECURITY + security.setId(0); initializer.setInstancesForObject(SECURITY, security); // SERVER Server lwm2mServer = new Server(shortServerId, TimeUnit.MINUTES.toSeconds(60)); - lwm2mServer.setId(serverId); + lwm2mServer.setId(0); initializer.setInstancesForObject(SERVER, lwm2mServer); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index be85294f08..1253993e08 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -43,12 +43,11 @@ import static org.awaitility.Awaitility.await; 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.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; @@ -140,10 +139,10 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg }); } }); - String ver_Id_0 = lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_0).version; - String ver_Id_1 = lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_1).version; - objectIdVer_0 = "/" + OBJECT_ID_0 + "_" + ver_Id_0; - objectIdVer_1 = "/" + OBJECT_ID_1 + "_" + ver_Id_1; + String ver_Id_0 = lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(SECURITY).version; + String ver_Id_1 = lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(SERVER).version; + objectIdVer_0 = "/" + SECURITY + "_" + ver_Id_0; + objectIdVer_1 = "/" + SERVER + "_" + ver_Id_1; objectIdVer_2 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + ACCESS_CONTROL)).findFirst().get(); objectIdVer_3 = (String) expectedObjectIdVers.stream().filter(PREDICATE_3).findFirst().get(); objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java index 90b373b16c..c0779ad944 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java @@ -21,8 +21,10 @@ import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.node.LwM2mPath; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; +import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -53,18 +55,18 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest try { expectedObjectIdVers.forEach(expected -> { try { - String actualResult = sendRPCById((String) expected); String expectedObjectId = pathIdVerToObjectId((String) expected); LwM2mPath expectedPath = new LwM2mPath(expectedObjectId); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String expectedObjectInstances = "LwM2mObject [id=" + expectedPath.getObjectId() + ", instances={0=LwM2mObjectInstance [id=0, resources="; - if (expectedPath.getObjectId() == 1) { - expectedObjectInstances = "LwM2mObject [id=1, instances={1="; - } else if (expectedPath.getObjectId() == 2) { - expectedObjectInstances = "LwM2mObject [id=2, instances={}]"; + if (expectedPath.getObjectId() > ACCESS_CONTROL) { + String actualResult = sendRPCByIdSync((String) expected); + if (StringUtils.isNoneBlank(actualResult)) { + log.warn(" expectedPath: [{}]", expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expectedObjectInstances = "LwM2mObject [id=" + expectedPath.getObjectId() + ", instances={0=LwM2mObjectInstance [id=0, resources="; + assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances)); + } } - assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances)); } catch (Exception e) { e.printStackTrace(); } @@ -83,7 +85,7 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest public void testReadAllInstancesInClientById_Result_CONTENT_Value_IsInstances_IsResources() throws Exception { expectedObjectIdVerInstances.forEach(expected -> { try { - String actualResult = sendRPCById((String) expected); + String actualResult = sendRPCByIdAsync((String) expected); String expectedObjectId = pathIdVerToObjectId((String) expected); LwM2mPath expectedPath = new LwM2mPath(expectedObjectId); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); @@ -104,7 +106,7 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest @Test public void testReadMultipleResourceById_Result_CONTENT_Value_IsLwM2mMultipleResource() throws Exception { String expectedIdVer = objectInstanceIdVer_3 + "/" + RESOURCE_ID_11; - String actualResult = sendRPCById(expectedIdVer); + String actualResult = sendRPCByIdAsync(expectedIdVer); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String expected = "LwM2mMultipleResource [id=" + RESOURCE_ID_11 + ", values={"; @@ -117,7 +119,7 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest @Test public void testReadSingleResourceById_Result_CONTENT_Value_IsLwM2mSingleResource() throws Exception { String expectedIdVer = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; - String actualResult = sendRPCById(expectedIdVer); + String actualResult = sendRPCByIdAsync(expectedIdVer); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value="; @@ -228,10 +230,14 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest assertEquals(actualValue, expectedValue); } - private String sendRPCById(String path) throws Exception { + private String sendRPCByIdAsync(String path) throws Exception { String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } + private String sendRPCByIdSync(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPost("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); + } private String sendRPCByKey(String key) throws Exception { String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"key\": \"" + key + "\"}}"; 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 6dda2de9fe..264c3b84c7 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 @@ -69,6 +69,7 @@ import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; import static org.eclipse.leshan.client.object.Security.noSecBootstrap; import static org.eclipse.leshan.client.object.Security.psk; +import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.junit.Assert.assertEquals; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; @@ -77,7 +78,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClient import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; @DaoSqlTest @@ -191,7 +192,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M transportConfiguration, awaitAlias, expectedStatuses, - true, + false, finishState, false); } @@ -231,8 +232,15 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M } - public void basicTestConnectionBootstrapRequestTriggerBefore(String clientEndpoint, String awaitAlias, LwM2MProfileBootstrapConfigType type) throws Exception { - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(type)); + public void basicTestConnectionBootstrapRequestTriggerBefore(String clientEndpoint, String awaitAlias, LwM2MProfileBootstrapConfigType type, int cnt) throws Exception { + List bootstrapServerCredentialsNoSec = getBootstrapServerCredentialsNoSec(type); + for (int i = 2; i <= cnt; i++) { + AbstractLwM2MBootstrapServerCredential bsCredential = getBootstrapServerCredentialNoSec(false); + bsCredential.setHost("0.0.0." + i); + bsCredential.setShortServerId(bsCredential.getShortServerId() + i); + bootstrapServerCredentialsNoSec.add(bsCredential); + } + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, bootstrapServerCredentialsNoSec); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); this.basicTestConnectionBootstrapRequestTrigger( SECURITY_NO_SEC, @@ -275,8 +283,8 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M }); Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesLwm2m)); - String executedPath = "/" + OBJECT_ID_1 + "_" + lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_1).version - + "/" +serverId + "/" + RESOURCE_ID_9; + String executedPath = "/" + SERVER + "_" + lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(SERVER).version + + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9; lwM2MTestClient.setClientStates(new HashSet<>()); String actualResult = sendRPCSecurityExecuteById(executedPath, deviceIdStr, endpoint); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); 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 new file mode 100644 index 0000000000..2622cac18c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSNoTriggerTest.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.sql; + +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; + +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.LWM2M_ONLY; + +public class NoSecLwM2MIntegrationBSNoTriggerTest extends AbstractSecurityLwM2MIntegrationTest { + + @Test + 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); + } + + @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); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSTriggerTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSTriggerTest.java new file mode 100644 index 0000000000..af5571864f --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSTriggerTest.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.sql; + +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; + +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOOTSTRAP_ONLY; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.LWM2M_ONLY; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; + +public class NoSecLwM2MIntegrationBSTriggerTest extends AbstractSecurityLwM2MIntegrationTest { + + @Test + public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateBootstrapSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOOTSTRAP_ONLY.name(); + String awaitAlias = "await on client state (NoSecBS Trigger Bootstrap section)"; + basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOOTSTRAP_ONLY, 1); + } + + @Test + public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateTwoSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOTH.name(); + String awaitAlias = "await on client state (NoSecBS Trigger Two section)"; + basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOTH, 1); + } + + @Test + public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateLwm2mSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + LWM2M_ONLY.name(); + String awaitAlias = "await on client state (NoSecBS Trigger Lwm2m section)"; + basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, 1); + } + @Test + public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateLwm2mSection_3_AndLm2m_1_ConnectLwm2mSuccess() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + LWM2M_ONLY.name(); + String awaitAlias = "await on client state (NoSecBS Trigger Lwm2m section)"; + basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, 3); + } + + @Test + public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateNoneSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + NONE.name(); + String awaitAlias = "await on client state (NoSecBS Trigger None section)"; + basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, NONE, 1); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java index d4a464b9c6..50bb87467f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java @@ -19,12 +19,6 @@ import org.junit.Test; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOOTSTRAP_ONLY; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.LWM2M_ONLY; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; - public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { //Lwm2m only @@ -34,48 +28,4 @@ public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationT LwM2MDeviceCredentials clientCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); super.basicTestConnectionObserveSingleTelemetry(SECURITY_NO_SEC, clientCredentials, clientEndpoint, false, false); } - - // Bootstrap + Lwm2m - @Test - public void testWithNoSecConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + BOTH.name(); - String awaitAlias = "await on client state (NoSecBS two section)"; - basicTestConnectionBefore(clientEndpoint, awaitAlias, BOTH, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS); - } - - @Test - public void testWithNoSecConnectBsSuccess_UpdateLwm2mSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + LWM2M_ONLY.name(); - String awaitAlias = "await on client state (NoSecBS Lwm2m section)"; - basicTestConnectionBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS); - } - - // Bs trigger - @Test - public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateTwoSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOTH.name(); - String awaitAlias = "await on client state (NoSecBS Trigger Two section)"; - basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOTH); - } - - @Test - public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateBootstrapSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOOTSTRAP_ONLY.name(); - String awaitAlias = "await on client state (NoSecBS Trigger Bootstrap section)"; - basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOOTSTRAP_ONLY); - } - - @Test - public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateLwm2mSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + LWM2M_ONLY.name(); - String awaitAlias = "await on client state (NoSecBS Trigger Lwm2m section)"; - basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, LWM2M_ONLY); - } - - @Test - public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateNoneSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + NONE.name(); - String awaitAlias = "await on client state (NoSecBS Trigger None section)"; - basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, NONE); - } } 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 3b61dfe49f..e4bf935306 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 @@ -175,12 +175,12 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setIdentity(identity); clientCredentials.setKey(keyPsk); - Security securityBs = pskBootstrap(SECURE_URI_BS, + Security securityPskBs = pskBootstrap(SECURE_URI_BS, identity.getBytes(StandardCharsets.UTF_8), Hex.decodeHex(keyPsk.toCharArray())); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); - this.basicTestConnection(null, securityBs, + this.basicTestConnection(null, securityPskBs, deviceCredentials, clientEndpoint, transportConfiguration, diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java index e94b8a6319..fbe84a28b2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java @@ -113,13 +113,13 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes RPKClientCredential clientCredentials = new RPKClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded())); - Security securityBs = rpkBootstrap(SECURE_URI_BS, + Security securityRpkBs = rpkBootstrap(SECURE_URI_BS, certificate.getPublicKey().getEncoded(), privateKey.getEncoded(), serverX509CertBs.getPublicKey().getEncoded()); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, clientPrivateKeyFromCertTrust, certificate, RPK, false); - this.basicTestConnection(null, securityBs, + this.basicTestConnection(null, securityRpkBs, 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 1ba408ac70..d1576ede1b 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 @@ -51,15 +51,14 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg X509ClientCredential clientCredentials = new X509ClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setCert(Base64.getEncoder().encodeToString(certificate.getEncoded())); - Security security = x509(SECURE_URI, + Security securityX509 = x509(SECURE_URI, shortServerId, certificate.getEncoded(), privateKey.getEncoded(), 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(securityX509, null, deviceCredentials, clientEndpoint, transportConfiguration, diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java index 516f118630..9adceb2860 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.coap.Request; import org.eclipse.leshan.core.peer.IpPeer; import org.eclipse.leshan.core.peer.LwM2mPeer; import org.eclipse.leshan.core.peer.PskIdentity; @@ -112,8 +113,10 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession } catch (InvalidConfigurationException e){ log.error("Failed put to lwM2MBootstrapSessionClients by endpoint [{}]", request.getEndpointName(), e); } + String msg = String.format("Bootstrap session started... %s", ((Request) request.getCoapRequest()).getLocalAddress().toString()); + log.warn(String.format("%s: %s", request.getEndpointName(), msg)); this.sendLogs(request.getEndpointName(), - String.format("%s: Bootstrap session started...", LOG_LWM2M_INFO, request.getEndpointName())); + String.format("%s: %s", LOG_LWM2M_INFO, msg)); } return session; } @@ -135,7 +138,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession session.setModel(modelProvider.getObjectModel(session, tasks.supportedObjects)); // set Requests to Send - log.info("tasks.requestsToSend = [{}]", tasks.requestsToSend); + log.warn("tasks.requestsToSend = [{}]", tasks.requestsToSend); session.setRequests(tasks.requestsToSend); // prepare list where we will store Responses @@ -182,14 +185,16 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession session.getResponses().add(response); String msg = String.format("%s: receives success response for: %s %s %s", LOG_LWM2M_INFO, request.getClass().getSimpleName(), request.getPath().toString(), response.toString()); + log.warn(msg); this.sendLogs(bsSession.getEndpoint(), msg); // on success for NOT bootstrap finish request we send next request return BootstrapPolicy.continueWith(nextRequest(bsSession)); } else { // on success for bootstrap finish request we stop the session - this.sendLogs(bsSession.getEndpoint(), - String.format("%s: receives success response for bootstrap finish.", LOG_LWM2M_INFO)); + String msg = String.format("%s: receives success response for bootstrap finish.", LOG_LWM2M_INFO); + log.info(msg); + this.sendLogs(bsSession.getEndpoint(), msg); this.tasksProvider.remove(bsSession.getEndpoint()); return BootstrapPolicy.finished(); } @@ -228,7 +233,9 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession @Override public void end(BootstrapSession bsSession) { - this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session finished.", LOG_LWM2M_INFO)); + String msg = String.format("%s: Bootstrap session finished.", LOG_LWM2M_INFO); + log.warn(msg); + this.sendLogs(bsSession.getEndpoint(), msg); this.tasksProvider.remove(bsSession.getEndpoint()); } 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 f24f068365..3223179486 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 @@ -17,15 +17,12 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.store; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.link.Link; -import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.request.BootstrapDeleteRequest; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; -import org.eclipse.leshan.core.request.BootstrapReadRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; -import org.eclipse.leshan.core.response.BootstrapReadResponse; import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; @@ -33,7 +30,6 @@ import org.eclipse.leshan.server.bootstrap.BootstrapSession; import org.eclipse.leshan.server.bootstrap.BootstrapUtil; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; -import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -43,14 +39,18 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.stream.Collectors; -import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; +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.transport.lwm2m.utils.LwM2MTransportUtil.BOOTSTRAP_DEFAULT_SHORT_ID_0; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LWM2M_DEFAULT_SHORT_ID_1; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LWM2M_DEFAULT_SHORT_ID_65534; @Slf4j public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTaskProvider { @@ -77,11 +77,11 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask @Override public Tasks getTasks(BootstrapSession session, List previousResponse) { // BootstrapConfig config = store.get(session.getEndpoint(), session.getClientTransportData().getIdentity(), session); - BootstrapConfig config = store.get(session); - if (config == null) { + BootstrapConfig configNew = store.get(session); + if (configNew == null) { return null; } - if (previousResponse == null && shouldStartWithDiscover(config)) { + if (previousResponse == null && shouldStartWithDiscover(configNew)) { Tasks tasks = new Tasks(); tasks.requestsToSend = new ArrayList<>(1); tasks.requestsToSend.add(new BootstrapDiscoverRequest()); @@ -96,47 +96,25 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask tasks.supportedObjects = this.supportedObjects; // handle bootstrap discover response if (previousResponse != null) { - if (previousResponse.get(0) instanceof BootstrapDiscoverResponse) { - BootstrapDiscoverResponse discoverResponse = (BootstrapDiscoverResponse) previousResponse.get(0); + if (previousResponse.get(0) instanceof BootstrapDiscoverResponse discoverResponse) { if (discoverResponse.isSuccess()) { this.initAfterBootstrapDiscover(discoverResponse); - findSecurityInstanceId(discoverResponse.getObjectLinks(), session.getEndpoint()); + /// Short Server Ids - in old config + findInstancesIdOldByServerId(discoverResponse, session.getEndpoint()); } else { - log.warn( - "Bootstrap Discover return error {} : to continue bootstrap session without autoIdForSecurityObject mode. {}", - discoverResponse, session); - } - if (this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID_0) == null) { log.error( "Unable to find bootstrap server instance in Security Object (0) in response {}: unable to continue bootstrap session with autoIdForSecurityObject mode. {}", discoverResponse, session); return null; } - tasks.requestsToSend = new ArrayList<>(1); - tasks.requestsToSend.add(new BootstrapReadRequest("/1")); - tasks.last = false; - return tasks; - } - BootstrapReadResponse readResponse = (BootstrapReadResponse) previousResponse.get(0); - Integer bootstrapServerIdOld = null; - if (readResponse.isSuccess()) { - findServerInstanceId(readResponse, session.getEndpoint()); - if (this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getSecurityInstances().size() > 0 && this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getServerInstances().size() > 0) { - bootstrapServerIdOld = this.findBootstrapServerId(session.getEndpoint()); - } - } else { - log.warn( - "Bootstrap ReadResponse return error {} : to continue bootstrap session without find Server Instance Id. {}", - readResponse, session); } // create requests from config - tasks.requestsToSend = this.toRequests(config, - config.contentFormat != null ? config.contentFormat : session.getContentFormat(), - bootstrapServerIdOld, session.getEndpoint()); + tasks.requestsToSend = this.toRequests(configNew, + configNew.contentFormat != null ? configNew.contentFormat : session.getContentFormat(), session.getEndpoint()); } else { // create requests from config - tasks.requestsToSend = BootstrapUtil.toRequests(config, - config.contentFormat != null ? config.contentFormat : session.getContentFormat()); + tasks.requestsToSend = BootstrapUtil.toRequests(configNew, + configNew.contentFormat != null ? configNew.contentFormat : session.getContentFormat()); } return tasks; } @@ -148,81 +126,57 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask /** * "Short Server ID": This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. - * The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server. - * "Short Server ID": + * "Short Lwm2m Server ID": * - Link Instance (lwm2m Server) hase linkParams with key = "ssid" value = "shortId" (ver lvm2m = 1.1). - * - Link Instance (bootstrap Server) hase not linkParams with key = "ssid" (ver lvm2m = 1.0). + * The values ID:0 values MUST NOT be used for identifying the LwM2M Server only BS. */ - protected void findSecurityInstanceId(Link[] objectLinks, String endpoint) { - log.info("Object after discover: [{}]", objectLinks); - for (Link link : objectLinks) { - if (link.getUriReference().startsWith("/0/")) { - try { - LwM2mPath path = new LwM2mPath(link.getUriReference()); - if (path.isObjectInstance()) { - if (link.getAttributes().get("ssid") != null) { - int serverId = Integer.parseInt(link.getAttributes().get("ssid").getCoreLinkValue()); - if (!lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(serverId)) { - lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(serverId, path.getObjectInstanceId()); - } else { - log.error("Invalid lwm2mSecurityInstance by [{}]", path.getObjectInstanceId()); - } - lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(serverId, path.getObjectInstanceId()); + protected void findInstancesIdOldByServerId(BootstrapDiscoverResponse discoverResponses, String endpoint) { + log.info("Object after discover: [{}]", Arrays.toString(discoverResponses.getObjectLinks())); + for (Link link : discoverResponses.getObjectLinks()) { + LwM2mPath path = new LwM2mPath(link.getUriReference()); + if (path.isObjectInstance()) { + int lwm2mShortServerId = 0; + if (path.getObjectId() == 0) { + if (link.getAttributes().get("ssid") != null) { + lwm2mShortServerId = Integer.parseInt(link.getAttributes().get("ssid").getCoreLinkValue()); + if (validateLwm2mShortServerId(lwm2mShortServerId)) { + this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().putIfAbsent(lwm2mShortServerId, path.getObjectInstanceId()); + } else { + log.error("Invalid lwm2mSecurityInstance [{}] by short server id [{}]", path.getObjectInstanceId(), lwm2mShortServerId); + } + } else { + this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().putIfAbsent(BOOTSTRAP_DEFAULT_SHORT_ID_0, path.getObjectInstanceId()); + } + } else if (path.getObjectId() == 1) { + if (link.getAttributes().get("ssid") != null) { + lwm2mShortServerId = Integer.parseInt(link.getAttributes().get("ssid").getCoreLinkValue()); + if (validateLwm2mShortServerId(lwm2mShortServerId)) { + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().putIfAbsent(lwm2mShortServerId, path.getObjectInstanceId()); } else { - if (!this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(0)) { - this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(BOOTSTRAP_DEFAULT_SHORT_ID_0, path.getObjectInstanceId()); - } else { - log.error("Invalid bootstrapSecurityInstance by [{}]", path.getObjectInstanceId()); - } + log.error("Invalid lwm2mServerInstance [{}] by short server id [{}]", path.getObjectInstanceId(), lwm2mShortServerId); } } - } catch (Exception e) { - // ignore if this is not a LWM2M path - log.error("Invalid LwM2MPath starting by \"/0/\""); } } } } - protected void findServerInstanceId(BootstrapReadResponse readResponse, String endpoint) { - try { - ((LwM2mObject) readResponse.getContent()).getInstances().values().forEach(instance -> { - var shId = OPAQUE.equals(instance.getResource(0).getType()) ? new BigInteger((byte[]) instance.getResource(0).getValue()).intValue() : instance.getResource(0).getValue(); - int shortId; - if (shId instanceof Long) { - shortId = ((Long) shId).intValue(); - } else { - shortId = (int) shId; - } - this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().put(shortId, instance.getId()); - }); - } catch (Exception e) { - log.error("Failed find Server Instance Id. ", e); - } - } - - protected Integer findBootstrapServerId(String endpoint) { - Integer bootstrapServerIdOld = null; - Map filteredMap = this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().entrySet() - .stream().filter(x -> !this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(x.getKey())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - if (filteredMap.size() > 0) { - bootstrapServerIdOld = filteredMap.keySet().stream().findFirst().get(); - } - return bootstrapServerIdOld; - } - public BootstrapConfigStore getStore() { return this.store; } private void initAfterBootstrapDiscover(BootstrapDiscoverResponse response) { Link[] links = response.getObjectLinks(); + AtomicReference verDefault = new AtomicReference<>("1.0"); Arrays.stream(links).forEach(link -> { LwM2mPath path = new LwM2mPath(link.getUriReference()); - if (!path.isRoot() && path.getObjectId() < 3) { + if (path.isRoot()) { + if (link.hasAttribute() && link.getAttributes().get("lwm2m") != null) { + verDefault.set(link.getAttributes().get("lwm2m").getValue().toString()); + } + } else if (path.getObjectId() <= ACCESS_CONTROL) { if (path.isObject()) { - String ver = link.getAttributes().get("ver") != null ? link.getAttributes().get("ver").getCoreLinkValue() : "1.0"; + String ver = (link.hasAttribute() && link.getAttributes().get("ver") != null) ? link.getAttributes().get("ver").getCoreLinkValue() : verDefault.get(); this.supportedObjects.put(path.getObjectId(), ver); } } @@ -230,84 +184,103 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask } - public List> toRequests(BootstrapConfig bootstrapConfig, + /** Map + * 1) Only Lwm2m Server + * - Short Server ID == 1 - 65534 lwm2m) + * SECURITY = 0; InstanceId = 0 + * SERVER = 1; InstanceId = 0 + * 2) Both + * - Short Server ID == 0 or 65535 bs) + * SECURITY = 0; InstanceId = 0 + * SERVER = 1; InstanceId = null + * - Short Server ID == 1 - 65534 lwm2m) + * SECURITY = 0; InstanceId = 1 + * SERVER = 1; InstanceId = 0 + * */ + public List> toRequests(BootstrapConfig bootstrapConfigNew, ContentFormat contentFormat, - Integer bootstrapServerIdOld, String endpoint) { + Integer bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID_0); List> requests = new ArrayList<>(); Set pathsDelete = new HashSet<>(); - List> requestsWrite = new ArrayList<>(); - boolean isBsServer = false; - boolean isLwServer = false; - /** Map */ - Map instances = new HashMap<>(); - Integer bootstrapServerIdNew = null; - // handle security - int lwm2mSecurityInstanceId = 0; - int bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID_0); - for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfig.security).values()) { - if (security.bootstrapServer) { - requestsWrite.add(toWriteRequest(bootstrapSecurityInstanceId, security, contentFormat)); - isBsServer = true; - bootstrapServerIdNew = security.serverId; - instances.put(security.serverId, bootstrapSecurityInstanceId); - } else { - if (lwm2mSecurityInstanceId == bootstrapSecurityInstanceId) { - lwm2mSecurityInstanceId++; - } - requestsWrite.add(toWriteRequest(lwm2mSecurityInstanceId, security, contentFormat)); - instances.put(security.serverId, lwm2mSecurityInstanceId); - isLwServer = true; - if (!isBsServer && this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(security.serverId) && - lwm2mSecurityInstanceId != this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(security.serverId)) { - pathsDelete.add("/0/" + this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(security.serverId)); - } - /** - * If there is an instance in the serverInstances with serverId which we replace in the securityInstances - */ - // find serverId in securityInstances by id (instance) - Integer serverIdOld = null; - for (Map.Entry entry : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().entrySet()) { - if (entry.getValue().equals(lwm2mSecurityInstanceId)) { - serverIdOld = entry.getKey(); - } - } - if (!isBsServer && serverIdOld != null && this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().containsKey(serverIdOld)) { - pathsDelete.add("/1/" + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(serverIdOld)); - } - lwm2mSecurityInstanceId++; + 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_DEFAULT_SHORT_ID_0) { + // delete old bootstrap Security + String path = "/" + SECURITY + "/" + bootstrapSecurityInstanceId; + pathsDelete.add(path); + // add new bootstrap Security + requestsWrite.put(path, toWriteRequest(bootstrapSecurityInstanceId, security, contentFormat)); } } - // handle server - for (Map.Entry server : bootstrapConfig.servers.entrySet()) { - int securityInstanceId = instances.get(server.getValue().shortId); - requestsWrite.add(toWriteRequest(securityInstanceId, server.getValue(), contentFormat)); - if (!isBsServer) { - /** Delete instance if bootstrapServerIdNew not equals bootstrapServerIdOld or securityInstanceBsIdNew not equals serverInstanceBsIdOld */ - if (bootstrapServerIdNew != null && server.getValue().shortId == bootstrapServerIdNew && - (bootstrapServerIdNew != bootstrapServerIdOld || securityInstanceId != this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(bootstrapServerIdOld))) { - pathsDelete.add("/1/" + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(bootstrapServerIdOld)); - /** Delete instance if serverIdNew is present in serverInstances and securityInstanceIdOld by serverIdNew not equals serverInstanceIdOld */ - } else if (this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().containsKey(server.getValue().shortId) && - securityInstanceId != this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(server.getValue().shortId)) { - pathsDelete.add("/1/" + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(server.getValue().shortId)); - } + + /** lwm2m servers: Multiple instances of lwm2m servers can run simultaneously by SHORT_ID + if update -> delete and write by InstanceId + if new -> only write with InstanceIdMax++ + */ + + /// lwm2m server: handle security & server + //max Lwm2m Security instance old id if new + for (Integer shortId : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().keySet()) { + if (shortId >= BOOTSTRAP_DEFAULT_SHORT_ID_0 && shortId <= LWM2M_DEFAULT_SHORT_ID_65534) { + lwm2mSecurityInstanceIdMax = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(shortId) > + lwm2mSecurityInstanceIdMax ? this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(shortId) : + lwm2mSecurityInstanceIdMax; } } - // handle acl - for (Map.Entry acl : bootstrapConfig.acls.entrySet()) { - requestsWrite.add(toWriteRequest(acl.getKey(), acl.getValue(), contentFormat)); + //max Lwm2m Server instance old id if new + for (Integer shortId : this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().keySet()) { + if (shortId >= LWM2M_DEFAULT_SHORT_ID_1 && shortId <= LWM2M_DEFAULT_SHORT_ID_65534) { + lwm2mServerInstanceIdMax = this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(shortId) > + lwm2mServerInstanceIdMax ? this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(shortId) : + lwm2mServerInstanceIdMax; + } } - // handle delete - if (isBsServer && isLwServer) { - requests.add(new BootstrapDeleteRequest("/0")); - requests.add(new BootstrapDeleteRequest("/1")); - } else { - pathsDelete.forEach(pathDelete -> requests.add(new BootstrapDeleteRequest(pathDelete))); + // Lwm2m update or new + for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfigNew.security).values()) { + if (!security.bootstrapServer) { + // Security + boolean isUpdate = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(security.serverId); + Integer secureInstanceId; + Integer serverInstanceId; + if (isUpdate) { + secureInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(security.serverId); + serverInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(security.serverId); + pathsDelete.add("/" + SECURITY + "/" + secureInstanceId); + pathsDelete.add("/" + SERVER + "/" + serverInstanceId); + } else { + secureInstanceId = ++lwm2mSecurityInstanceIdMax; + serverInstanceId = ++lwm2mServerInstanceIdMax; + } + requestsWrite.put("/" + SECURITY + "/" + secureInstanceId, toWriteRequest(secureInstanceId, security, contentFormat)); + new TreeMap<>(bootstrapConfigNew.servers).values().stream() + .filter(server -> server.shortId == security.serverId) + .findFirst() + .ifPresent(server -> + requestsWrite.put( + "/" + SERVER + "/" + serverInstanceId, + toWriteRequest(serverInstanceId, server, contentFormat) + ) + ); + } } - // handle write - if (requestsWrite.size() > 0) { - requests.addAll(requestsWrite); + + /// handle acl + for (Map.Entry acl : bootstrapConfigNew.acls.entrySet()) { + requestsWrite.put("/" + ACCESS_CONTROL + "/" + acl.getKey(), toWriteRequest(acl.getKey(), acl.getValue(), contentFormat)); + } + /// handle delete + pathsDelete.forEach(pathDelete -> requests.add(new BootstrapDeleteRequest(pathDelete))); + + /// handle write + if (!requestsWrite.isEmpty()) { + requests.addAll(requestsWrite.values()); } return (requests); } @@ -315,9 +288,13 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask private void initSupportedObjectsDefault() { this.supportedObjects = new HashMap<>(); - this.supportedObjects.put(0, "1.1"); - this.supportedObjects.put(1, "1.1"); - this.supportedObjects.put(2, "1.0"); + this.supportedObjects.put(SECURITY, "1.1"); + this.supportedObjects.put(SERVER, "1.1"); + this.supportedObjects.put(ACCESS_CONTROL, "1.0"); + } + + private boolean validateLwm2mShortServerId(int id){ + return id >= LWM2M_DEFAULT_SHORT_ID_1 && id <= LWM2M_DEFAULT_SHORT_ID_65534; } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index 160ca3d905..7464361c24 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -82,6 +82,8 @@ public class LwM2MTransportUtil { public static final String LOG_LWM2M_ERROR = "error"; public static final String LOG_LWM2M_WARN = "warn"; public static final int BOOTSTRAP_DEFAULT_SHORT_ID_0 = 0; + public static final int LWM2M_DEFAULT_SHORT_ID_1 = 1; + public static final int LWM2M_DEFAULT_SHORT_ID_65534 = 65534; public static LwM2mOtaConvert convertOtaUpdateValueToString(String pathIdVer, Object value, ResourceModel.Type currentType) { String path = fromVersionedIdToObjectId(pathIdVer); diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts index 1630a1d220..6756ea2874 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts @@ -104,7 +104,7 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc clientHoldOffTime: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], shortServerId: ['', this.isBootstrap ? [Validators.required, Validators.pattern('^(' + this.shortServerIdBsMin+ '|' + this.shortServerIdBsMax + ')$' )] - : [Validators.required, Validators.pattern('[0-9]*'),Validators.min(this.shortServerIdMin), Validators.max(this.shortServerIdMax)] + : [Validators.required, Validators.pattern('[0-9]*'), Validators.min(this.shortServerIdMin), Validators.max(this.shortServerIdMax)] ], bootstrapServerAccountTimeout: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], binding: [''], From 74b101f4b644108bd929597759e70568bc7d00da Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Fri, 10 Oct 2025 17:13:40 +0300 Subject: [PATCH 05/16] lwm2m: bootstrap new: update tests --- .../lwm2m/client/LwM2MTestClient.java | 55 ++++++++++++------- ...LwM2MIntegrationBS3SectionTriggerTest.java | 29 ++++++++++ ...nBSLwm2mOnlyNoneTriggerOneSectionTest.java | 37 +++++++++++++ .../NoSecLwM2MIntegrationBSNoTriggerTest.java | 1 - ...ntegrationBSOnlyTriggerOneSectionTest.java | 29 ++++++++++ .../NoSecLwM2MIntegrationBSTriggerTest.java | 32 +---------- ...LwM2MBootstrapConfigStoreTaskProvider.java | 13 +++-- 7 files changed, 140 insertions(+), 56 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBS3SectionTriggerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSLwm2mOnlyNoneTriggerOneSectionTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSOnlyTriggerOneSectionTest.java 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 7c5e2973ac..4d6265c393 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 @@ -68,6 +68,7 @@ import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandle import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.io.IOException; +import java.io.InputStream; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashMap; @@ -77,6 +78,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; 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; @@ -146,25 +148,9 @@ public class LwM2MTestClient { this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler; this.clientContext = clientContext; - List models = ObjectLoader.loadAllDefault(); - for (String resourceName : lwm2mClientResources) { - models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); - } - if (this.modelResources != null) { - List modelsRes = new ArrayList<>(); - for (String resourceName : this.modelResources) { - modelsRes.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); - } - Set idsToRemove = new HashSet<>(); - for (ObjectModel model : modelsRes) { - idsToRemove.add(model.id); - } - models.removeIf(model -> idsToRemove.contains(model.id)); - models.addAll(modelsRes); - } + ObjectsInitializer initializer = createFreshInitializer(); + - LwM2mModel model = new StaticModel(models); - ObjectsInitializer initializer = new ObjectsInitializer(model); if (securityBs != null && security != null) { // SECURITIES securityBs.setId(0); @@ -179,9 +165,9 @@ public class LwM2MTestClient { initializer.setInstancesForObject(SERVER, instances); } else if (securityBs != null) { // SECURITY - securityBs.setId(0); initializer.setClassForObject(SERVER, Server.class); initializer.setInstancesForObject(SECURITY, securityBs); + log.warn("Security section: securityBsId [{}] ", securityBs.getId()); } else { // SECURITY security.setId(0); @@ -478,4 +464,35 @@ public class LwM2MTestClient { LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(endpoint); Mockito.doAnswer(invocationOnMock -> null).when(defaultLwM2mUplinkMsgHandlerTest).initAttributes(lwM2MClient, true); } + + private ObjectsInitializer createFreshInitializer() { + List models = new ArrayList<>(ObjectLoader.loadAllDefault()); + for (String resourceName : lwm2mClientResources) { + try (InputStream in = LwM2MTestClient.class.getClassLoader() + .getResourceAsStream("lwm2m/" + resourceName)) { + models.addAll(ObjectLoader.loadDdfFile(in, resourceName)); + } catch (IOException | InvalidDDFFileException e) { + log.warn("Failed to load resource {}", resourceName, e); + } + } + if (this.modelResources != null) { + List modelsRes = new ArrayList<>(); + for (String resourceName : this.modelResources) { + try (InputStream in = LwM2MTestClient.class.getClassLoader() + .getResourceAsStream("lwm2m/" + resourceName)) { + modelsRes.addAll(ObjectLoader.loadDdfFile(in, resourceName)); + } catch (IOException | InvalidDDFFileException e) { + log.warn("Failed to load resource {}", resourceName, e); + } + } + Set idsToRemove = modelsRes.stream() + .map(m -> m.id) + .collect(Collectors.toSet()); + models.removeIf(m -> idsToRemove.contains(m.id)); + models.addAll(modelsRes); + } + LwM2mModel model = new StaticModel(models); + return new ObjectsInitializer(model); + } } + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBS3SectionTriggerTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBS3SectionTriggerTest.java new file mode 100644 index 0000000000..af8284484d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBS3SectionTriggerTest.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.sql; + +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.LWM2M_ONLY; +public class NoSecLwM2MIntegrationBS3SectionTriggerTest extends AbstractSecurityLwM2MIntegrationTest { + + @Test + public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTrigger_3_ConnectBsSuccess_UpdateLwm2mSection_3_AndLm2m_1_ConnectLwm2mSuccess() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger_3" + LWM2M_ONLY.name(); + String awaitAlias = "await on client state (NoSecBS Trigger Lwm2m section)"; + basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, 3); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSLwm2mOnlyNoneTriggerOneSectionTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSLwm2mOnlyNoneTriggerOneSectionTest.java new file mode 100644 index 0000000000..4fceba60f3 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSLwm2mOnlyNoneTriggerOneSectionTest.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.sql; + +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.LWM2M_ONLY; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; +public class NoSecLwM2MIntegrationBSLwm2mOnlyNoneTriggerOneSectionTest extends AbstractSecurityLwM2MIntegrationTest { + + @Test + public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateLwm2mSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + LWM2M_ONLY.name(); + String awaitAlias = "await on client state (NoSecBS Trigger Lwm2m section)"; + basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, 1); + } + + @Test + public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateNoneSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + NONE.name(); + String awaitAlias = "await on client state (NoSecBS Trigger None section)"; + basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, NONE, 1); + } +} 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 2622cac18c..edfe805cf8 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 @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.security.sql; import org.junit.Test; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; - import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.LWM2M_ONLY; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSOnlyTriggerOneSectionTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSOnlyTriggerOneSectionTest.java new file mode 100644 index 0000000000..5510cdf614 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSOnlyTriggerOneSectionTest.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.sql; + +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOOTSTRAP_ONLY; +public class NoSecLwM2MIntegrationBSOnlyTriggerOneSectionTest extends AbstractSecurityLwM2MIntegrationTest { + + @Test + public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateBootstrapSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOOTSTRAP_ONLY.name(); + String awaitAlias = "await on client state (NoSecBS Trigger Bootstrap section)"; + basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOOTSTRAP_ONLY, 1); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSTriggerTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSTriggerTest.java index af5571864f..b007d16bde 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSTriggerTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationBSTriggerTest.java @@ -17,45 +17,15 @@ package org.thingsboard.server.transport.lwm2m.security.sql; import org.junit.Test; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; - -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOOTSTRAP_ONLY; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.LWM2M_ONLY; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; public class NoSecLwM2MIntegrationBSTriggerTest extends AbstractSecurityLwM2MIntegrationTest { - @Test - public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateBootstrapSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOOTSTRAP_ONLY.name(); - String awaitAlias = "await on client state (NoSecBS Trigger Bootstrap section)"; - basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOOTSTRAP_ONLY, 1); - } - @Test public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateTwoSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOTH.name(); String awaitAlias = "await on client state (NoSecBS Trigger Two section)"; basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOTH, 1); } - - @Test - public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateLwm2mSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + LWM2M_ONLY.name(); - String awaitAlias = "await on client state (NoSecBS Trigger Lwm2m section)"; - basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, 1); - } - @Test - public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateLwm2mSection_3_AndLm2m_1_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + LWM2M_ONLY.name(); - String awaitAlias = "await on client state (NoSecBS Trigger Lwm2m section)"; - basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, 3); - } - - @Test - public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateNoneSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + NONE.name(); - String awaitAlias = "await on client state (NoSecBS Trigger None section)"; - basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, NONE, 1); - } } + 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 3223179486..8e5c1cf4bf 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 @@ -98,14 +98,16 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask if (previousResponse != null) { if (previousResponse.get(0) instanceof BootstrapDiscoverResponse discoverResponse) { if (discoverResponse.isSuccess()) { - this.initAfterBootstrapDiscover(discoverResponse); + this.initAfterBootstrapDiscover(discoverResponse); /// Short Server Ids - in old config findInstancesIdOldByServerId(discoverResponse, session.getEndpoint()); + log.warn( + "Bootstrap server instance successfully found in Security Object (0) in response {}. Continuing bootstrap session. Session: {}", + discoverResponse, session); } else { - log.error( - "Unable to find bootstrap server instance in Security Object (0) in response {}: unable to continue bootstrap session with autoIdForSecurityObject mode. {}", + log.warn( + "Unable to find bootstrap server instance in Security Object (0) in response {}. Continuing bootstrap session with autoIdForSecurityObject mode, ignoring information from discoverResponse. Session: {}", discoverResponse, session); - return null; } } // create requests from config @@ -200,7 +202,8 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask public List> toRequests(BootstrapConfig bootstrapConfigNew, ContentFormat contentFormat, String endpoint) { - Integer bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID_0); + Integer bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID_0) == null ? + 0 : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID_0); List> requests = new ArrayList<>(); Set pathsDelete = new HashSet<>(); ConcurrentHashMap> requestsWrite = new ConcurrentHashMap<>(); From ea8ab484dfe8b20dba68c22a7351e38471ce4832 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Mon, 13 Oct 2025 17:08:21 +0300 Subject: [PATCH 06/16] lwm2m: bootstrap new: update tests-2 --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 17 ++- .../lwm2m/client/LwM2MTestClient.java | 42 +++++- .../lwm2m/Lwm2mServerIdentifier.java | 137 ++++++++++++++++++ ...LwM2MBootstrapConfigStoreTaskProvider.java | 22 +-- .../store/LwM2MConfigurationChecker.java | 8 +- .../lwm2m/utils/LwM2MTransportUtil.java | 3 - .../validator/DeviceProfileDataValidator.java | 13 +- .../DeviceProfileDataValidatorTest.java | 10 +- .../lwm2m-device-config-server.component.ts | 5 +- .../assets/locale/locale.constant-en_US.json | 2 +- 10 files changed, 221 insertions(+), 38 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/Lwm2mServerIdentifier.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 50051c08d5..8a62795863 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,10 +33,12 @@ 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; @@ -120,11 +122,13 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfil import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; import static org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegrationTest.CLIENT_LWM2M_SETTINGS_19; -@TestPropertySource(properties = { - "transport.lwm2m.enabled=true", -}) @Slf4j @DaoSqlTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@TestPropertySource(properties = { + "transport.lwm2m.enabled=true" +}) public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportIntegrationTest { @SpyBean @@ -317,9 +321,16 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte @After public void after() throws Exception { this.clientDestroy(true); + 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.info("✅ Test teardown completed: {}", this.getClass().getSimpleName()); } private void init() throws Exception { 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 4d6265c393..c368dbfe54 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 @@ -69,6 +69,7 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashMap; @@ -153,29 +154,30 @@ public class LwM2MTestClient { if (securityBs != null && security != null) { // SECURITIES - securityBs.setId(0); - security.setId(1); + forceNullSecurityId(securityBs); + forceNullSecurityId(security); 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); } else if (securityBs != null) { // SECURITY - initializer.setClassForObject(SERVER, Server.class); +; forceNullSecurityId(securityBs); initializer.setInstancesForObject(SECURITY, securityBs); - log.warn("Security section: securityBsId [{}] ", securityBs.getId()); + // SERVER + initializer.setClassForObject(SERVER, Server.class); + log.warn("Security BS section: securityBsId [{}] ", securityBs.getId()); } else { // SECURITY - security.setId(0); + forceNullSecurityId(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()); } initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice(executor, value3_0_9)); @@ -494,5 +496,29 @@ public class LwM2MTestClient { LwM2mModel model = new StaticModel(models); return new ObjectsInitializer(model); } + + private void forceNullSecurityId(Security securityBs) { + if (securityBs == null) { + return; + } + try { + Field field = securityBs.getClass().getDeclaredField("id"); + field.setAccessible(true); + field.set(securityBs, null); + log.info("[forceNullSecurityId] Set id=null for {}", securityBs); + } catch (NoSuchFieldException e) { + try { + // Якщо поле в батьківському класі (наприклад SecurityObjectInstance) + Field field = securityBs.getClass().getSuperclass().getDeclaredField("id"); + field.setAccessible(true); + field.set(securityBs, null); + log.info("[forceNullSecurityId] Set id=null for {} (via superclass)", securityBs); + } catch (Exception ex) { + log.error("[forceNullSecurityId] Field 'id' not found for {}", securityBs.getClass(), ex); + } + } catch (Exception e) { + log.error("[forceNullSecurityId] Failed to set id=null for {}", securityBs.getClass(), e); + } + } } 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 new file mode 100644 index 0000000000..a9f81ab655 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/Lwm2mServerIdentifier.java @@ -0,0 +1,137 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.credentials.lwm2m; + +/** + * Enum representing predefined LwM2M Short Server Identifiers. + *

+ * See OMA Lightweight M2M Specification for details about the server identifier space. + */ +public enum Lwm2mServerIdentifier { + + /** + * Bootstrap Short Server ID (0). + * Reserved for the Bootstrap Server — used exclusively during the bootstrap phase. + */ + BOOTSTRAP(0, "Bootstrap Short Server ID", true), + + /** + * 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), + + /** + * 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), + + /** + * Not used for identifying an LwM2M Server (65535). + * Reserved sentinel value representing "no server associated" or "invalid ID". + * 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); + + private final int id; + private final String description; + private final boolean isBootstrap; + + Lwm2mServerIdentifier(int id, String description, boolean isBootstrap) { + this.id = id; + this.description = description; + this.isBootstrap = isBootstrap; + } + + /** + * @return the integer value of this Short Server ID. + */ + public int getId() { + return id; + } + + /** + * @return a human-readable description of this Server ID. + */ + public String getDescription() { + return description; + } + + /** + * @return true if this ID represents a Bootstrap 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; + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * 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. + */ + public static Lwm2mServerIdentifier fromId(int id) { + for (Lwm2mServerIdentifier s : values()) { + if (s.id == id) { + return s; + } + } + throw new IllegalArgumentException("Unknown Lwm2mServerIdentifier: " + id); + } + + @Override + public String toString() { + return name() + "(" + id + ") - " + description; + } +} 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 8e5c1cf4bf..33d1fb207c 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.transport.lwm2m.utils.LwM2MTransportUtil.BOOTSTRAP_DEFAULT_SHORT_ID_0; -import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LWM2M_DEFAULT_SHORT_ID_1; -import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LWM2M_DEFAULT_SHORT_ID_65534; +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; @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_DEFAULT_SHORT_ID_0, path.getObjectInstanceId()); + this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().putIfAbsent(BOOTSTRAP.getId(), path.getObjectInstanceId()); } } else if (path.getObjectId() == 1) { if (link.getAttributes().get("ssid") != null) { @@ -192,7 +192,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask * SECURITY = 0; InstanceId = 0 * SERVER = 1; InstanceId = 0 * 2) Both - * - Short Server ID == 0 or 65535 bs) + * - Short Server ID == 0 bs) * SECURITY = 0; InstanceId = 0 * SERVER = 1; InstanceId = null * - Short Server ID == 1 - 65534 lwm2m) @@ -202,8 +202,8 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask public List> toRequests(BootstrapConfig bootstrapConfigNew, ContentFormat contentFormat, String endpoint) { - Integer bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID_0) == null ? - 0 : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID_0); + Integer bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP.getId()) == null ? + 0 : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP.getId()); List> requests = new ArrayList<>(); Set pathsDelete = new HashSet<>(); ConcurrentHashMap> requestsWrite = new ConcurrentHashMap<>(); @@ -214,7 +214,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask // 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_DEFAULT_SHORT_ID_0) { + if (security.bootstrapServer && security.serverId == BOOTSTRAP.getId()) { // delete old bootstrap Security String path = "/" + SECURITY + "/" + bootstrapSecurityInstanceId; pathsDelete.add(path); @@ -231,7 +231,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask /// lwm2m server: handle security & server //max Lwm2m Security instance old id if new for (Integer shortId : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().keySet()) { - if (shortId >= BOOTSTRAP_DEFAULT_SHORT_ID_0 && shortId <= LWM2M_DEFAULT_SHORT_ID_65534) { + 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; @@ -239,7 +239,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask } //max Lwm2m Server instance old id if new for (Integer shortId : this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().keySet()) { - if (shortId >= LWM2M_DEFAULT_SHORT_ID_1 && shortId <= LWM2M_DEFAULT_SHORT_ID_65534) { + 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; @@ -297,7 +297,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask } private boolean validateLwm2mShortServerId(int id){ - return id >= LWM2M_DEFAULT_SHORT_ID_1 && id <= LWM2M_DEFAULT_SHORT_ID_65534; + return id >= PRIMARY_LWM2M_SERVER.getId() && id <= LWM2M_SERVER_MAX.getId(); } @Override 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 aba29aed24..113c3451e0 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,6 +21,10 @@ 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; + public class LwM2MConfigurationChecker extends ConfigurationChecker { @Override @@ -74,8 +78,8 @@ 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 && (srvCfg.shortId < 1 && srvCfg.shortId > 65534 )) { - throw new InvalidConfigurationException("Specific ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server"); + 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"); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index 7464361c24..d5ae8febe1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -81,9 +81,6 @@ public class LwM2MTransportUtil { public static final String LOG_LWM2M_INFO = "info"; public static final String LOG_LWM2M_ERROR = "error"; public static final String LOG_LWM2M_WARN = "warn"; - public static final int BOOTSTRAP_DEFAULT_SHORT_ID_0 = 0; - public static final int LWM2M_DEFAULT_SHORT_ID_1 = 1; - public static final int LWM2M_DEFAULT_SHORT_ID_65534 = 65534; public static LwM2mOtaConvert convertOtaUpdateValueToString(String pathIdVer, Object value, ResourceModel.Type currentType) { String path = fromVersionedIdToObjectId(pathIdVer); 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 dfd0ee82bc..9b59b2eee6 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,6 +69,11 @@ 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; + @Slf4j @Component public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator { @@ -339,12 +344,12 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator 65535) { - throw new DeviceCredentialsValidationException("Bootstrap Server ShortServerId must be in range [0 - 65535]!"); + if (serverConfig.getShortServerId() != BOOTSTRAP.getId()) { + throw new DeviceCredentialsValidationException("Bootstrap Server ShortServerId must be in range [" + BOOTSTRAP.getId() + "]!"); } } else { - if (serverConfig.getShortServerId() < 1 || serverConfig.getShortServerId() > 65534) { - throw new DeviceCredentialsValidationException("LwM2M Server ShortServerId must be in range [1 - 65534]!"); + if (!isLwm2mServer(serverConfig.getShortServerId())) { + throw new DeviceCredentialsValidationException("LwM2M Server ShortServerId must be in range [" + PRIMARY_LWM2M_SERVER.getId() + " - " + LWM2M_SERVER_MAX.getId() + "!"); } } } else { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java index b69165be7c..d80b7a6519 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java @@ -48,6 +48,10 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.BDDMockito.willReturn; import static org.mockito.Mockito.verify; +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.NOT_USED_IDENTIFYING_LWM2M_SERVER; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.Lwm2mServerIdentifier.PRIMARY_LWM2M_SERVER; @SpringBootTest(classes = DeviceProfileDataValidator.class) class DeviceProfileDataValidatorTest { @@ -74,8 +78,8 @@ class DeviceProfileDataValidatorTest { " \"clientOnlyObserveAfterConnect\": 1\n" + " }"; - private static final String msgErrorLwm2mRange = "LwM2M Server ShortServerId must be in range [1 - 65534]!"; - private static final String msgErrorBsRange = "Bootstrap Server ShortServerId must be in range [0 - 65535]!"; + private static final String msgErrorLwm2mRange = "LwM2M Server ShortServerId must be in range [" + PRIMARY_LWM2M_SERVER.getId() + " - " + LWM2M_SERVER_MAX.getId() + "]!"; + private static final String msgErrorBsRange = "Bootstrap Server ShortServerId must be in range [" + BOOTSTRAP.getId() + " - " + NOT_USED_IDENTIFYING_LWM2M_SERVER.getId() + "]!"; private static final String msgErrorNotNull = " Server ShortServerId must not be null!"; private static final String host = "localhost"; private static final String hostBs = "localhost"; @@ -153,7 +157,7 @@ class DeviceProfileDataValidatorTest { @Test void testValidateDeviceProfile_Lwm2mShortServerId_More_65534_Error_BootstrapShortServerId_Ok() { - verifyValidationError(65535, 111, msgErrorLwm2mRange); + verifyValidationError(NOT_USED_IDENTIFYING_LWM2M_SERVER.getId(), 111, msgErrorLwm2mRange); } @Test diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts index 6756ea2874..71f4264f94 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts @@ -76,8 +76,7 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc readonly shortServerIdMin = 1; readonly shortServerIdMax = 65534; - readonly shortServerIdBsMin = 0; - readonly shortServerIdBsMax = 65535; + readonly shortServerIdBs = 0; @Input() @coerceBoolean() @@ -103,7 +102,7 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc serverPublicKey: [''], clientHoldOffTime: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], shortServerId: ['', this.isBootstrap - ? [Validators.required, Validators.pattern('^(' + this.shortServerIdBsMin+ '|' + this.shortServerIdBsMax + ')$' )] + ? [Validators.required, Validators.pattern('^(' + this.shortServerIdBs + ')$' )] : [Validators.required, Validators.pattern('[0-9]*'), Validators.min(this.shortServerIdMin), Validators.max(this.shortServerIdMax)] ], bootstrapServerAccountTimeout: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 05a1f5ceb3..912eeedc5f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2302,7 +2302,7 @@ "short-id-required": "Short server ID is required.", "short-id-range": "Short server ID should be in a range from {{ min }} to {{ max }}.", "short-id-pattern": "Short server ID must be a positive integer.", - "short-id-pattern-bs": "Short server ID must be only 0 or 65535", + "short-id-pattern-bs": "Short server ID must be only 0", "lifetime": "Client registration lifetime", "lifetime-required": "Client registration lifetime is required.", "lifetime-pattern": "Client registration lifetime must be a positive integer.", From d030c4af87f23642e2c9c174564205e6600ce712 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Mon, 13 Oct 2025 20:04:58 +0300 Subject: [PATCH 07/16] lwm2m: bootstrap new: update tests-3 --- .../dao/service/validator/DeviceProfileDataValidator.java | 2 +- .../dao/service/validator/DeviceProfileDataValidatorTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 9b59b2eee6..2ea8c343d7 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 @@ -349,7 +349,7 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Mon, 13 Oct 2025 23:11:14 +0300 Subject: [PATCH 08/16] lwm2m: bootstrap new: update tests-4 --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 16 ++++++++-------- .../transport/lwm2m/client/LwM2MTestClient.java | 10 ++++++---- 2 files changed, 14 insertions(+), 12 deletions(-) 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 8a62795863..4828bbbdde 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 @@ -124,8 +124,8 @@ import static org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegra @Slf4j @DaoSqlTest -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +//@TestInstance(TestInstance.Lifecycle.PER_CLASS) +//@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @TestPropertySource(properties = { "transport.lwm2m.enabled=true" }) @@ -324,13 +324,13 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte if (executor != null && !executor.isShutdown()) { executor.shutdownNow(); - if (!executor.awaitTermination(2, TimeUnit.SECONDS)) { - log.warn("⚠️ Executor did not terminate cleanly, forcing GC"); - } +// if (!executor.awaitTermination(2, TimeUnit.SECONDS)) { +// log.warn("⚠️ Executor did not terminate cleanly, forcing GC"); +// } } - Thread.sleep(300); - System.gc(); - log.info("✅ Test teardown completed: {}", this.getClass().getSimpleName()); +// Thread.sleep(300); +// System.gc(); +// log.info("✅ Test teardown completed: {}", this.getClass().getSimpleName()); } private void init() throws Exception { 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 c368dbfe54..bf5a4aa10d 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 @@ -154,28 +154,30 @@ public class LwM2MTestClient { if (securityBs != null && security != null) { // SECURITIES - forceNullSecurityId(securityBs); - forceNullSecurityId(security); + 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); } else if (securityBs != null) { // SECURITY -; forceNullSecurityId(securityBs); +; securityBs.setId(0);; initializer.setInstancesForObject(SECURITY, securityBs); // SERVER initializer.setClassForObject(SERVER, Server.class); log.warn("Security BS section: securityBsId [{}] ", securityBs.getId()); } else { // SECURITY - forceNullSecurityId(security); + security.setId(0); 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()); } From 2857b8c1cbd6eb1e36fda7a240a3a84275e3261c Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Tue, 14 Oct 2025 10:22:52 +0300 Subject: [PATCH 09/16] lwm2m: bootstrap new: update tests-5 --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 12 +++--- .../lwm2m/client/LwM2MTestClient.java | 37 ++++++++++--------- 2 files changed, 25 insertions(+), 24 deletions(-) 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 4828bbbdde..338cf166ae 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 @@ -324,13 +324,13 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte if (executor != null && !executor.isShutdown()) { executor.shutdownNow(); -// if (!executor.awaitTermination(2, TimeUnit.SECONDS)) { -// log.warn("⚠️ Executor did not terminate cleanly, forcing GC"); -// } + if (!executor.awaitTermination(2, TimeUnit.SECONDS)) { + log.warn("Executor did not terminate cleanly, forcing GC"); + } } -// Thread.sleep(300); -// System.gc(); -// log.info("✅ Test teardown completed: {}", this.getClass().getSimpleName()); + Thread.sleep(300); + System.gc(); + log.warn("Test lwm2m after completed: {}", this.getClass().getSimpleName()); } private void init() throws Exception { 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 bf5a4aa10d..3bb127d736 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 @@ -151,33 +151,33 @@ public class LwM2MTestClient { ObjectsInitializer initializer = createFreshInitializer(); - + forceNullSecurityId(securityBs); + forceNullSecurityId(security); if (securityBs != null && security != null) { // SECURITIES - securityBs.setId(0); - security.setId(1); +// 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); +// lwm2mServer.setId(0); instances = new LwM2mInstanceEnabler[]{lwm2mServer}; initializer.setInstancesForObject(SERVER, instances); } else if (securityBs != null) { // SECURITY -; securityBs.setId(0);; initializer.setInstancesForObject(SECURITY, securityBs); // SERVER initializer.setClassForObject(SERVER, Server.class); log.warn("Security BS section: securityBsId [{}] ", securityBs.getId()); } else { // SECURITY - security.setId(0); initializer.setInstancesForObject(SECURITY, security); // SERVER Server lwm2mServer = new Server(shortServerId, TimeUnit.MINUTES.toSeconds(60)); - lwm2mServer.setId(0); +// lwm2mServer.setId(0); initializer.setInstancesForObject(SERVER, lwm2mServer); log.warn("Security Lwm2m section: securityLwm2mId [{}] Server Lwm2m section: securityLwm2mId [{}] ", security.getId(), lwm2mServer.getId()); } @@ -470,7 +470,8 @@ public class LwM2MTestClient { } private ObjectsInitializer createFreshInitializer() { - List models = new ArrayList<>(ObjectLoader.loadAllDefault()); +// List models = new ArrayList<>(ObjectLoader.loadAllDefault()); + List models = ObjectLoader.loadAllDefault(); for (String resourceName : lwm2mClientResources) { try (InputStream in = LwM2MTestClient.class.getClassLoader() .getResourceAsStream("lwm2m/" + resourceName)) { @@ -499,27 +500,27 @@ public class LwM2MTestClient { return new ObjectsInitializer(model); } - private void forceNullSecurityId(Security securityBs) { - if (securityBs == null) { + private void forceNullSecurityId(Security security) { + if (security == null) { return; } try { - Field field = securityBs.getClass().getDeclaredField("id"); + Field field = security.getClass().getDeclaredField("id"); field.setAccessible(true); - field.set(securityBs, null); - log.info("[forceNullSecurityId] Set id=null for {}", securityBs); + field.set(security, null); + log.info("[forceNullSecurityId] Set id=null for {}", security); } catch (NoSuchFieldException e) { try { // Якщо поле в батьківському класі (наприклад SecurityObjectInstance) - Field field = securityBs.getClass().getSuperclass().getDeclaredField("id"); + Field field = security.getClass().getSuperclass().getDeclaredField("id"); field.setAccessible(true); - field.set(securityBs, null); - log.info("[forceNullSecurityId] Set id=null for {} (via superclass)", securityBs); + field.set(security, null); + log.info("[forceNullSecurityId] Set id=null for {} (via superclass)", security); } catch (Exception ex) { - log.error("[forceNullSecurityId] Field 'id' not found for {}", securityBs.getClass(), ex); + log.error("[forceNullSecurityId] Field 'id' not found for {}", security.getClass(), ex); } } catch (Exception e) { - log.error("[forceNullSecurityId] Failed to set id=null for {}", securityBs.getClass(), e); + log.error("[forceNullSecurityId] Failed to set id=null for {}", security.getClass(), e); } } } From 83d866c6baf9d6692316457cedfbd61132ff8757 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Tue, 14 Oct 2025 18:20:51 +0300 Subject: [PATCH 10/16] lwm2m: bootstrap new: update tests-6 --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 45 ++++---- .../lwm2m/client/LwM2MTestClient.java | 101 ++++++++++-------- .../AbstractSecurityLwM2MIntegrationTest.java | 17 ++- .../NoSecLwM2MIntegrationBSNoTriggerTest.java | 4 +- .../security/sql/PskLwm2mIntegrationTest.java | 9 +- .../sql/X509_NoTrustLwM2MIntegrationTest.java | 3 +- .../sql/X509_TrustLwM2MIntegrationTest.java | 6 +- .../lwm2m/Lwm2mServerIdentifier.java | 46 +++----- .../bootstrap/LwM2MServerSecurityConfig.java | 2 +- .../secure/LwM2MBootstrapConfig.java | 4 +- ...LwM2MBootstrapConfigStoreTaskProvider.java | 46 ++++---- .../store/LwM2MConfigurationChecker.java | 13 +-- .../uplink/DefaultLwM2mUplinkMsgHandler.java | 3 +- .../validator/DeviceProfileDataValidator.java | 22 ++-- .../DeviceProfileDataValidatorTest.java | 7 +- 15 files changed, 162 insertions(+), 166 deletions(-) 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 Date: Tue, 14 Oct 2025 21:53:40 +0300 Subject: [PATCH 11/16] lwm2m: bootstrap new: update tests-7 --- .../service/validator/DeviceProfileDataValidatorTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java index 18bfa1fd81..4c427544d4 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java @@ -127,7 +127,7 @@ class DeviceProfileDataValidatorTest { @Test void testValidateDeviceProfile_Lwm2mBootstrap_ShortServerId_Ok() { Integer shortServerId = 123; - Integer shortServerIdBs = 0; + Integer shortServerIdBs = null; DeviceProfile deviceProfile = getDeviceProfile(shortServerId, shortServerIdBs); validator.validateDataImpl(tenantId, deviceProfile); @@ -135,8 +135,8 @@ class DeviceProfileDataValidatorTest { } @Test - void testValidateDeviceProfile_Lwm2mShortServerId_Ok_BootstrapShortServerId_null_Error() { - verifyValidationError(123, null, "Bootstrap" + msgErrorNotNull); + void testValidateDeviceProfile_Lwm2mShortServerId_Ok_BootstrapShortServerId_0_Error() { + verifyValidationError(123, 0, msgErrorBsRange); } @Test From c593f3e95b48fdfa15c6fde9697d5ba266f2e802 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Thu, 16 Oct 2025 17:43:21 +0300 Subject: [PATCH 12/16] lwm2m: bootstrap new: add reboot device and reboot device bootstrap --- .../lwm2m/Lwm2mServerIdentifier.java | 25 ++-- ...LwM2MBootstrapConfigStoreTaskProvider.java | 2 +- .../validator/DeviceProfileDataValidator.java | 6 +- .../device-credentials-lwm2m.component.html | 14 +- .../device-credentials-lwm2m.component.ts | 128 +++++++++++++++++- .../device/device-credentials.component.html | 3 +- .../device/device-credentials.component.ts | 4 + .../lwm2m-device-config-server.component.html | 4 +- .../lwm2m-device-config-server.component.ts | 6 +- .../assets/locale/locale.constant-en_US.json | 4 +- 10 files changed, 164 insertions(+), 32 deletions(-) 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 95aa95597f..f4f020776b 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 @@ -47,11 +47,11 @@ public enum Lwm2mServerIdentifier { */ NOT_USED_IDENTIFYING_LWM2M_SERVER_MAX(65535, "Reserved sentinel value (no active server)", false); - private final int id; + private final Integer id; private final String description; private final boolean isLwm2mServer; - Lwm2mServerIdentifier(int id, String description, boolean isLwm2mServer) { + Lwm2mServerIdentifier(Integer id, String description, boolean isLwm2mServer) { this.id = id; this.description = description; this.isLwm2mServer = isLwm2mServer; @@ -60,7 +60,7 @@ public enum Lwm2mServerIdentifier { /** * @return the integer value of this Short Server ID. */ - public int getId() { + public Integer getId() { return id; } @@ -83,20 +83,11 @@ public enum Lwm2mServerIdentifier { * @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 isLwm2mServer(Integer id) { + return id != null && 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 >= NOT_USED_IDENTIFYING_LWM2M_SERVER_MIN.getId() && id <= NOT_USED_IDENTIFYING_LWM2M_SERVER_MAX.getId(); + public static boolean isNotLwm2mServer(Integer id) { + return id == null || id < PRIMARY_LWM2M_SERVER.id || id > LWM2M_SERVER_MAX.id; } /** @@ -105,7 +96,7 @@ public enum Lwm2mServerIdentifier { * @return corresponding enum constant. * @throws IllegalArgumentException if no constant matches the given ID. */ - public static Lwm2mServerIdentifier fromId(int id) { + public static Lwm2mServerIdentifier fromId(Integer id) { for (Lwm2mServerIdentifier s : values()) { if (s.id == id) { return s; 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 7020869130..67f4aa32f6 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 @@ -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(0, path.getObjectInstanceId()); + this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().putIfAbsent(null, path.getObjectInstanceId()); } } else if (path.getObjectId() == 1) { if (link.getAttributes().get("ssid") != null) { 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 ac600f9201..401b199598 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 @@ -343,7 +343,11 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator - + @@ -72,6 +72,12 @@ device.lwm2m-security-config.client-public-key-hint + @@ -103,5 +109,11 @@ + diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts index ca91c14682..ec9f8ff5fb 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, OnDestroy } from '@angular/core'; +import {Component, forwardRef, Input, OnDestroy} from '@angular/core'; import { ControlValueAccessor, UntypedFormBuilder, @@ -32,9 +32,12 @@ import { Lwm2mSecurityType, Lwm2mSecurityTypeTranslationMap } from '@shared/models/lwm2m-security-config.models'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import {Subject, throwError, timeout, catchError, of} from 'rxjs'; +import {map, takeUntil} from 'rxjs/operators'; import { isDefinedAndNotNull } from '@core/utils'; +import { HttpClient } from '@angular/common/http'; +import {DeviceId} from "@shared/models/id/device-id"; +import {Observable} from "rxjs/internal/Observable"; @Component({ selector: 'tb-device-credentials-lwm2m', @@ -65,7 +68,11 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va private destroy$ = new Subject(); private propagateChange = (v: any) => {}; - constructor(private fb: UntypedFormBuilder) { + @Input() + deviceId: DeviceId; + + constructor(private fb: UntypedFormBuilder, + private http: HttpClient) { this.lwm2mConfigFormGroup = this.initLwm2mConfigForm(); } @@ -101,6 +108,119 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va this.destroy$.complete(); } + /** + * AbstractRpcController -> rpcController + * - API + * "/api/plugins/rpc/twoway/${this.deviceId.id}" + * - DiscoveryAll + * requestBody = "{\"method\":\"DiscoverAll\"}"; + * - "Registration Update Trigger", + * requestBody = "{\"method\": \"Execute\", \"params\": {\"id\": \"/1_1.2/0/8\"}} + * - "Bootstrap-Request Trigger" + * requestBody = "{\"method\": \"Execute\", \"params\": {\"id\": \"/1_1.2/0/9\"}} + */ + public rebootDevice(isBootstrapServer: boolean): void { + const urlApi = `/api/plugins/rpc/twoway/${this.deviceId.id}`; + // DiscoveryAll} + this.http.post(urlApi, { method: "DiscoverAll" }) + .pipe( + timeout(10000), // 10 sec + catchError(err => { + console.error('DiscoverAll timeout or error', err); + return throwError(() => err); + }) + ) + .subscribe({ + next: (response: any) => { + console.log('success: Discovery'); + console.log(response); + // result = 'CONTENT' + if (response.result && response.result.toUpperCase() === 'CONTENT') { + const verId = this.getVerId(response.value); + console.log("ObjectId=1 ver:", verId); + const resourceId = isBootstrapServer ? 9 : 8; + const resourcePath = `/1_${verId}/0/${resourceId}`; + + // first rebootTrigger + this.rebootTrigger(resourcePath, urlApi).subscribe(first => { + if (first.result === 'CHANGED') { + console.log('Reboot success first'); + } + else if (first.result === 'BAD_REQUEST' && first.newVersionId && first.newVersionId !== verId) { + // Retry with new version + const correctedPath = `/1_${first.newVersionId}/0/${resourceId}`; + console.log(`Retrying with version ${first.newVersionId}`); + + this.rebootTrigger(correctedPath, urlApi).subscribe(second => { + if (second.result === 'CHANGED') { + console.log('Success reboot after retry'); + } else { + console.error(`error1: Reboot second failed: ${second.toString()}`); + } + }); + } else { + console.error(`error2: Reboot first failed: ${first.toString()}`); + } + }); + } + + else { + console.error(`error3: Bad registration device with id = ${this.deviceId.id} ❗ RPC result is not CONTENT`); + } + }, + error: (e) => { + console.error(`error4: Bad registration device with id = ${this.deviceId.id} ${e.toString()}`); + return throwError(() => e); + }, + complete: () => { + console.log('Discovery stream complete'); } + }); + } + + private getVerId(value: string): string { + const verDef = '1.1'; + try { + const arr = JSON.parse(value); + if (!Array.isArray(arr)) return verDef; + const obj1 = arr.find((s: string) => s.startsWith(' { + console.log(`Sending reboot command to ${resourcePath}`); + + return this.http.post(urlApi, { + method: 'Execute', + params: { id: resourcePath } + }).pipe( + timeout(10000), + map(res => { + console.log(`Reboot for ${resourcePath}`); + console.log(res); + if (res?.result?.toUpperCase() === 'CHANGED') { + return { result: 'CHANGED' }; + } + + if (res?.result?.toUpperCase() === 'BAD_REQUEST' && res?.error) { + const match = (res.error as string).match(/version[:=]\s*([\d.]+)/i); + const newVersionId = match ? match[1] : null; + console.warn(`BAD_REQUEST: suggested version ${newVersionId ?? 'unknown'}`); + return { result: 'BAD_REQUEST', newVersionId }; + } + + return { result: 'ERROR' }; + }), + catchError(err => { + console.error(`Execute error5 for ${resourcePath}:`, err); + return of({ result: 'ERROR' }); + }) + ); + } + private initClientSecurityConfig(config: Lwm2mSecurityConfigModels): void { this.lwm2mConfigFormGroup.patchValue(config, {emitEvent: false}); this.securityConfigClientUpdateValidators(config.client.securityConfigClientMode); diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html index 34c446f759..144f2059c3 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html @@ -81,7 +81,8 @@ - + diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts index 012bdf9c9c..9aa2c2fa2d 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts @@ -36,6 +36,7 @@ import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { generateSecret, isDefinedAndNotNull } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; +import {DeviceId} from "@shared/models/id/device-id"; @Component({ selector: 'tb-device-credentials', @@ -88,6 +89,8 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, credentialTypeNamesMap = credentialTypeNames; + deviceId: DeviceId; + private propagateChange = null; private propagateChangePending = false; @@ -126,6 +129,7 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, writeValue(value: DeviceCredentials | null): void { if (isDefinedAndNotNull(value)) { + this.deviceId = value.deviceId; const credentialsType = this.credentialsTypes.includes(value.credentialsType) ? value.credentialsType : this.credentialsTypes[0]; this.deviceCredentialsFormGroup.patchValue({ credentialsType, diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html index 65a2400e16..a6ef4c0847 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html @@ -24,7 +24,7 @@ 'device-profile.lwm2m.bootstrap-server' : 'device-profile.lwm2m.lwm2m-server') | translate }}

{{ ('device-profile.lwm2m.short-id' | translate) + ': ' }} - {{ serverFormGroup.get('shortServerId').value }} + {{ serverFormGroup.get('shortServerId').value ? serverFormGroup.get('shortServerId').value : '' }}
{{ ('device-profile.lwm2m.mode' | translate) + ': ' }} {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[serverFormGroup.get('securityMode').value]) }} @@ -54,7 +54,7 @@ - + {{ 'device-profile.lwm2m.short-id' | translate }} help diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts index 71f4264f94..48be7ff3f4 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts @@ -76,7 +76,6 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc readonly shortServerIdMin = 1; readonly shortServerIdMax = 65534; - readonly shortServerIdBs = 0; @Input() @coerceBoolean() @@ -101,9 +100,8 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc securityMode: [Lwm2mSecurityType.NO_SEC], serverPublicKey: [''], clientHoldOffTime: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - shortServerId: ['', this.isBootstrap - ? [Validators.required, Validators.pattern('^(' + this.shortServerIdBs + ')$' )] - : [Validators.required, Validators.pattern('[0-9]*'), Validators.min(this.shortServerIdMin), Validators.max(this.shortServerIdMax)] + shortServerId: ['', this.isBootstrap ? + [] : [Validators.required, Validators.pattern('[0-9]*'), Validators.min(this.shortServerIdMin), Validators.max(this.shortServerIdMax)] ], bootstrapServerAccountTimeout: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], binding: [''], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 912eeedc5f..a3fcc843a1 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1801,6 +1801,8 @@ "bootstrap-tab": "Bootstrap Client", "bootstrap-server": "Bootstrap Server", "lwm2m-server": "LwM2M Server", + "client-reboot": "Registration Update Trigger", + "bootstrap-reboot": "Bootstrap-Request Trigger", "client-publicKey-or-id": "Client Public Key or Id", "client-publicKey-or-id-required": "Client Public Key or Id is required.", "client-publicKey-or-id-tooltip-psk": "The PSK identifier is an arbitrary PSK identifier up to 128 bytes, as described in the standard [RFC7925].\nThe PSK identifier MUST first be converted to a character string and then encoded into octets using UTF-8.", @@ -2302,7 +2304,7 @@ "short-id-required": "Short server ID is required.", "short-id-range": "Short server ID should be in a range from {{ min }} to {{ max }}.", "short-id-pattern": "Short server ID must be a positive integer.", - "short-id-pattern-bs": "Short server ID must be only 0", + "short-id-pattern-bs": "Short server ID must be only null", "lifetime": "Client registration lifetime", "lifetime-required": "Client registration lifetime is required.", "lifetime-pattern": "Client registration lifetime must be a positive integer.", From 5e8225413b2ebc18cc40340c3ba0996a50116f1a Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Fri, 17 Oct 2025 20:34:27 +0300 Subject: [PATCH 13/16] lwm2m: bootstrap new: add reboot device and reboot device bootstrap - 2 --- .../lwm2m/client/LwM2MTestClient.java | 2 +- ...LwM2MBootstrapConfigStoreTaskProvider.java | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) 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 40fb6d3ff0..993e416ded 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 @@ -522,7 +522,7 @@ public class LwM2MTestClient { log.info("[forceNullSecurityId] Set id=null for {}", security); } catch (NoSuchFieldException e) { try { - // Якщо поле в батьківському класі (наприклад SecurityObjectInstance) + //(SecurityObjectInstance) Field field = security.getClass().getSuperclass().getDeclaredField("id"); field.setAccessible(true); field.set(security, null); 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 67f4aa32f6..ab69632956 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 @@ -188,7 +188,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask /** Map => LwM2MBootstrapClientInstanceIds * 1) Both - * - Short Server ID == null bs) + * - (Short) Server ID == null bs) * SECURITY = 0; InstanceId = 0 * - Short Server ID == 1 - 65534 lwm2m) * SECURITY = 0; InstanceId = 1 @@ -218,7 +218,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask // delete old bootstrap Security String path = "/" + SECURITY + "/" + bootstrapSecurityInstanceId; pathsDelete.add(path); - // add new bootstrap Security + security.serverId = null; requestsWrite.put(path, toWriteRequest(bootstrapSecurityInstanceId, security, contentFormat)); } } @@ -251,26 +251,31 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfigNew.security).values()) { if (!security.bootstrapServer) { // Security - boolean isUpdate = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(security.serverId); - Integer secureInstanceId; - Integer serverInstanceId; - if (isUpdate) { - secureInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(security.serverId); - serverInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(security.serverId); + Integer secureInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(security.serverId); + if (secureInstanceId != null) { pathsDelete.add("/" + SECURITY + "/" + secureInstanceId); - pathsDelete.add("/" + SERVER + "/" + serverInstanceId); + requestsWrite.put("/" + SECURITY + "/" + secureInstanceId, toWriteRequest(secureInstanceId, security, contentFormat)); } else { secureInstanceId = ++lwm2mSecurityInstanceIdMax; + if (bootstrapSecurityInstanceId.equals(secureInstanceId)) { + secureInstanceId = ++lwm2mSecurityInstanceIdMax; + } + requestsWrite.put("/" + SECURITY + "/" + secureInstanceId, toWriteRequest(secureInstanceId, security, contentFormat)); + } + Integer serverInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(security.serverId); + if (serverInstanceId != null) { + pathsDelete.add("/" + SERVER + "/" + serverInstanceId); + } else { serverInstanceId = ++lwm2mServerInstanceIdMax; } - requestsWrite.put("/" + SECURITY + "/" + secureInstanceId, toWriteRequest(secureInstanceId, security, contentFormat)); + Integer finalServerInstanceId = serverInstanceId; new TreeMap<>(bootstrapConfigNew.servers).values().stream() .filter(server -> server.shortId == security.serverId) .findFirst() .ifPresent(server -> requestsWrite.put( - "/" + SERVER + "/" + serverInstanceId, - toWriteRequest(serverInstanceId, server, contentFormat) + "/" + SERVER + "/" + finalServerInstanceId, + toWriteRequest(finalServerInstanceId, server, contentFormat) ) ); } @@ -290,7 +295,6 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask return (requests); } - private void initSupportedObjectsDefault() { this.supportedObjects = new HashMap<>(); this.supportedObjects.put(SECURITY, "1.1"); From d02582b13bc2ee3d264497d544ac3bd00a045359 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Sun, 19 Oct 2025 18:00:45 +0300 Subject: [PATCH 14/16] lwm2m: bootstrap new: add reboot device and reboot device bootstrap - 3 --- .../lwm2m/server/client/LwM2mClient.java | 6 + .../lwm2m/utils/LwM2MTransportUtil.java | 2 + ui-ngx/src/app/core/http/device.service.ts | 70 ++++++++++- .../device-credentials-lwm2m.component.ts | 114 ++---------------- 4 files changed, 85 insertions(+), 107 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 64597df9c5..884ba7e033 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -66,7 +66,9 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.BOOTSTRAP_TRIGGER_PARAMS_ID; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LWM2M_OBJECT_VERSION_DEFAULT; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.REGISTRATION_TRIGGER_PARAMS_ID; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.equalsResourceTypeGetSimpleName; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; @@ -342,6 +344,10 @@ public class LwM2mClient { public String isValidObjectVersion(String path) { LwM2mPath pathIds = getLwM2mPathFromString(path); + if (pathIds.isResource() && (pathIds.toString().equals(REGISTRATION_TRIGGER_PARAMS_ID ) || + pathIds.toString().equals(BOOTSTRAP_TRIGGER_PARAMS_ID))) { + return ""; + } LwM2m.Version verSupportedObject = this.getSupportedObjectVersion(pathIds.getObjectId()); if (verSupportedObject == null) { return String.format("Specified object id %s absent in the list supported objects of the client or is security object!", pathIds.getObjectId()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index d5ae8febe1..c1fbbcb006 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -81,6 +81,8 @@ public class LwM2MTransportUtil { public static final String LOG_LWM2M_INFO = "info"; public static final String LOG_LWM2M_ERROR = "error"; public static final String LOG_LWM2M_WARN = "warn"; + public static final String REGISTRATION_TRIGGER_PARAMS_ID = "/1/0/8"; + public static final String BOOTSTRAP_TRIGGER_PARAMS_ID = "/1/0/9";; public static LwM2mOtaConvert convertOtaUpdateValueToString(String pathIdVer, Object value, ResourceModel.Type currentType) { String path = fromVersionedIdToObjectId(pathIdVer); diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index 1e3a304774..88e5f8f7bf 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable, ReplaySubject } from 'rxjs'; +import {catchError, Observable, of, ReplaySubject, throwError, timeout} from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; @@ -35,6 +35,7 @@ import { AuthService } from '@core/auth/auth.service'; import { BulkImportRequest, BulkImportResult } from '@shared/import-export/import-export.models'; import { PersistentRpc, RpcStatus } from '@shared/models/rpc.models'; import { ResourcesService } from '@core/services/resources.service'; +import {map} from "rxjs/operators"; @Injectable({ providedIn: 'root' @@ -219,4 +220,71 @@ export class DeviceService { public downloadGatewayDockerComposeFile(deviceId: string): Observable { return this.resourcesService.downloadResource(`/api/device-connectivity/gateway-launch/${deviceId}/docker-compose/download`); } + + public rebootDevice(deviceId: string = '', isBootstrapServer: boolean): void { + const urlApi = `/api/plugins/rpc/twoway/${deviceId}`; + // DiscoveryAll} + this.http.post(urlApi, { method: "DiscoverAll" }) + .pipe( + timeout(10000), // 10 sec + catchError(err => { + console.error('DiscoverAll timeout or error', err); + return throwError(() => err); + }) + ) + .subscribe({ + next: (response: any) => { + console.log('success: Discovery'); + console.log(response); + // result = 'CONTENT' + if (response.result && response.result.toUpperCase() === 'CONTENT') { + const resourceId = isBootstrapServer ? 9 : 8; + const rebutName = isBootstrapServer ? "Bootstrap-Request Trigger" : + "Registration Update Trigger"; + const resourcePath = `/1/0/${resourceId}`; + + // first rebootTrigger + this.rebootTrigger(resourcePath, urlApi).subscribe(responseReboot => { + if (responseReboot.result === 'CHANGED') { + console.info(`info: ${rebutName} success.`); + } else { + console.error(`error: ${rebutName} failed: ${responseReboot.toString()}`); + } + }); + } + else { + console.error(`error3: Bad registration device with id = ${deviceId} ❗ RPC result is not CONTENT`); + } + }, + error: (e) => { + console.error(`error4: Bad registration device with id = ${deviceId} ${e.toString()}`); + return throwError(() => new Error('Could not get JWT token from store.')); + // return throwError(() => e); + }, + complete: () => { + console.log('Discovery stream complete'); } + }); + } + + private rebootTrigger(resourcePath: string, urlApi: string): Observable<{ result: string;}> { + console.log(`Sending reboot command to ${resourcePath}`); + return this.http.post(urlApi, { + method: 'Execute', + params: { id: resourcePath } + }).pipe( + timeout(10000), + map(res => { + console.log(res); + if (res?.result?.toUpperCase() === 'CHANGED') { + return { result: 'CHANGED' }; + } else { + return {result: 'ERROR'} + }; + }), + catchError(err => { + console.error(`Execute error5 for ${resourcePath}:`, err); + return of({ result: 'ERROR' }); + }) + ); + } } diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts index ec9f8ff5fb..4ddc3a4852 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts @@ -32,12 +32,11 @@ import { Lwm2mSecurityType, Lwm2mSecurityTypeTranslationMap } from '@shared/models/lwm2m-security-config.models'; -import {Subject, throwError, timeout, catchError, of} from 'rxjs'; -import {map, takeUntil} from 'rxjs/operators'; +import {Subject} from 'rxjs'; +import {takeUntil} from 'rxjs/operators'; import { isDefinedAndNotNull } from '@core/utils'; -import { HttpClient } from '@angular/common/http'; import {DeviceId} from "@shared/models/id/device-id"; -import {Observable} from "rxjs/internal/Observable"; +import {DeviceService} from "@core/http/device.service"; @Component({ selector: 'tb-device-credentials-lwm2m', @@ -72,7 +71,7 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va deviceId: DeviceId; constructor(private fb: UntypedFormBuilder, - private http: HttpClient) { + private deviceService: DeviceService) { this.lwm2mConfigFormGroup = this.initLwm2mConfigForm(); } @@ -115,110 +114,13 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va * - DiscoveryAll * requestBody = "{\"method\":\"DiscoverAll\"}"; * - "Registration Update Trigger", - * requestBody = "{\"method\": \"Execute\", \"params\": {\"id\": \"/1_1.2/0/8\"}} + * requestBody = "{\"method\": \"Execute\", \"params\": {\"id\": \"/1/0/8\"}} * - "Bootstrap-Request Trigger" - * requestBody = "{\"method\": \"Execute\", \"params\": {\"id\": \"/1_1.2/0/9\"}} + * requestBody = "{\"method\": \"Execute\", \"params\": {\"id\": \"/1/0/9\"}} */ - public rebootDevice(isBootstrapServer: boolean): void { - const urlApi = `/api/plugins/rpc/twoway/${this.deviceId.id}`; - // DiscoveryAll} - this.http.post(urlApi, { method: "DiscoverAll" }) - .pipe( - timeout(10000), // 10 sec - catchError(err => { - console.error('DiscoverAll timeout or error', err); - return throwError(() => err); - }) - ) - .subscribe({ - next: (response: any) => { - console.log('success: Discovery'); - console.log(response); - // result = 'CONTENT' - if (response.result && response.result.toUpperCase() === 'CONTENT') { - const verId = this.getVerId(response.value); - console.log("ObjectId=1 ver:", verId); - const resourceId = isBootstrapServer ? 9 : 8; - const resourcePath = `/1_${verId}/0/${resourceId}`; - - // first rebootTrigger - this.rebootTrigger(resourcePath, urlApi).subscribe(first => { - if (first.result === 'CHANGED') { - console.log('Reboot success first'); - } - else if (first.result === 'BAD_REQUEST' && first.newVersionId && first.newVersionId !== verId) { - // Retry with new version - const correctedPath = `/1_${first.newVersionId}/0/${resourceId}`; - console.log(`Retrying with version ${first.newVersionId}`); - - this.rebootTrigger(correctedPath, urlApi).subscribe(second => { - if (second.result === 'CHANGED') { - console.log('Success reboot after retry'); - } else { - console.error(`error1: Reboot second failed: ${second.toString()}`); - } - }); - } else { - console.error(`error2: Reboot first failed: ${first.toString()}`); - } - }); - } - - else { - console.error(`error3: Bad registration device with id = ${this.deviceId.id} ❗ RPC result is not CONTENT`); - } - }, - error: (e) => { - console.error(`error4: Bad registration device with id = ${this.deviceId.id} ${e.toString()}`); - return throwError(() => e); - }, - complete: () => { - console.log('Discovery stream complete'); } - }); - } - - private getVerId(value: string): string { - const verDef = '1.1'; - try { - const arr = JSON.parse(value); - if (!Array.isArray(arr)) return verDef; - const obj1 = arr.find((s: string) => s.startsWith(' { - console.log(`Sending reboot command to ${resourcePath}`); - - return this.http.post(urlApi, { - method: 'Execute', - params: { id: resourcePath } - }).pipe( - timeout(10000), - map(res => { - console.log(`Reboot for ${resourcePath}`); - console.log(res); - if (res?.result?.toUpperCase() === 'CHANGED') { - return { result: 'CHANGED' }; - } - - if (res?.result?.toUpperCase() === 'BAD_REQUEST' && res?.error) { - const match = (res.error as string).match(/version[:=]\s*([\d.]+)/i); - const newVersionId = match ? match[1] : null; - console.warn(`BAD_REQUEST: suggested version ${newVersionId ?? 'unknown'}`); - return { result: 'BAD_REQUEST', newVersionId }; - } - return { result: 'ERROR' }; - }), - catchError(err => { - console.error(`Execute error5 for ${resourcePath}:`, err); - return of({ result: 'ERROR' }); - }) - ); + public rebootDevice(isBootstrapServer: boolean): void { + this.deviceService.rebootDevice(this.deviceId.id, isBootstrapServer); } private initClientSecurityConfig(config: Lwm2mSecurityConfigModels): void { From 44dcbb4359a5b857351e6b0edece410b0d889aaf Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Tue, 21 Oct 2025 16:20:36 +0300 Subject: [PATCH 15/16] lwm2m: bootstrap new: add toast --- ui-ngx/src/app/core/http/device.service.ts | 94 +++++++++---------- .../device-credentials-lwm2m.component.ts | 27 +++++- 2 files changed, 72 insertions(+), 49 deletions(-) diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index 88e5f8f7bf..251870ab64 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -35,7 +35,7 @@ import { AuthService } from '@core/auth/auth.service'; import { BulkImportRequest, BulkImportResult } from '@shared/import-export/import-export.models'; import { PersistentRpc, RpcStatus } from '@shared/models/rpc.models'; import { ResourcesService } from '@core/services/resources.service'; -import {map} from "rxjs/operators"; +import {map, switchMap} from "rxjs/operators"; @Injectable({ providedIn: 'root' @@ -221,53 +221,51 @@ export class DeviceService { return this.resourcesService.downloadResource(`/api/device-connectivity/gateway-launch/${deviceId}/docker-compose/download`); } - public rebootDevice(deviceId: string = '', isBootstrapServer: boolean): void { + public rebootDevice(deviceId: string = '', isBootstrapServer: boolean): Observable<{ result: string, msg: string }> { const urlApi = `/api/plugins/rpc/twoway/${deviceId}`; - // DiscoveryAll} - this.http.post(urlApi, { method: "DiscoverAll" }) - .pipe( - timeout(10000), // 10 sec - catchError(err => { - console.error('DiscoverAll timeout or error', err); - return throwError(() => err); - }) - ) - .subscribe({ - next: (response: any) => { - console.log('success: Discovery'); - console.log(response); - // result = 'CONTENT' - if (response.result && response.result.toUpperCase() === 'CONTENT') { - const resourceId = isBootstrapServer ? 9 : 8; - const rebutName = isBootstrapServer ? "Bootstrap-Request Trigger" : - "Registration Update Trigger"; - const resourcePath = `/1/0/${resourceId}`; - - // first rebootTrigger - this.rebootTrigger(resourcePath, urlApi).subscribe(responseReboot => { + const rebootName = isBootstrapServer ? 'Bootstrap-Request Trigger' : 'Registration Update Trigger'; + return this.http.post(urlApi, { method: 'DiscoverAll' }).pipe( + timeout(10000), + switchMap((response: any) => { + if (response.result && response.result.toUpperCase() === 'CONTENT') { + const resourceId = isBootstrapServer ? 9 : 8; + const resourcePath = `/1/0/${resourceId}`; + return this.rebootTrigger(resourcePath, urlApi).pipe( + map((responseReboot: any) => { if (responseReboot.result === 'CHANGED') { - console.info(`info: ${rebutName} success.`); + return { + result: 'SUCCESS', + msg: `\"${rebootName}\" - Started Successfully.` }; } else { - console.error(`error: ${rebutName} failed: ${responseReboot.toString()}`); + return { + result: 'ERROR', + msg: `\"${rebootName}\" failed:
${JSON.stringify(responseReboot, null, 2)}
` + } } - }); - } - else { - console.error(`error3: Bad registration device with id = ${deviceId} ❗ RPC result is not CONTENT`); - } - }, - error: (e) => { - console.error(`error4: Bad registration device with id = ${deviceId} ${e.toString()}`); - return throwError(() => new Error('Could not get JWT token from store.')); - // return throwError(() => e); - }, - complete: () => { - console.log('Discovery stream complete'); } - }); - } - - private rebootTrigger(resourcePath: string, urlApi: string): Observable<{ result: string;}> { - console.log(`Sending reboot command to ${resourcePath}`); + }), + catchError(err => + of({ + result: 'ERROR', + msg: `\"${rebootName}\" failed.
Error: ${err.message || err}` }) + ) + ); + } else { + return of({ + result: 'ERROR', + msg: `\"${rebootName}\" failed.
Bad registration device with id = ${deviceId}.
\"DiscoverAll\" - RPC result is not \"CONTENT\"` + }); + } + }), + catchError(err => + of({ + result: 'ERROR', + msg: `\"${rebootName}\" failed.
Bad registration device with id = ${deviceId}.
Error: ${err.message || err}` + }) + ) + ); + } + + private rebootTrigger(resourcePath: string, urlApi: string): Observable<{ result: string, msg?: string }> { return this.http.post(urlApi, { method: 'Execute', params: { id: resourcePath } @@ -278,12 +276,14 @@ export class DeviceService { if (res?.result?.toUpperCase() === 'CHANGED') { return { result: 'CHANGED' }; } else { - return {result: 'ERROR'} + return { + result:`${res?.result}`, + msg: `${res?.error} ` + } }; }), catchError(err => { - console.error(`Execute error5 for ${resourcePath}:`, err); - return of({ result: 'ERROR' }); + return throwError(() => err); }) ); } diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts index 4ddc3a4852..7ace8c349a 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts @@ -37,6 +37,9 @@ import {takeUntil} from 'rxjs/operators'; import { isDefinedAndNotNull } from '@core/utils'; import {DeviceId} from "@shared/models/id/device-id"; import {DeviceService} from "@core/http/device.service"; +import {ActionNotificationShow} from "@core/notification/notification.actions"; +import {Store} from "@ngrx/store"; +import {AppState} from "@core/core.state"; @Component({ selector: 'tb-device-credentials-lwm2m', @@ -70,7 +73,8 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va @Input() deviceId: DeviceId; - constructor(private fb: UntypedFormBuilder, + constructor(protected store: Store, + private fb: UntypedFormBuilder, private deviceService: DeviceService) { this.lwm2mConfigFormGroup = this.initLwm2mConfigForm(); } @@ -120,7 +124,26 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va */ public rebootDevice(isBootstrapServer: boolean): void { - this.deviceService.rebootDevice(this.deviceId.id, isBootstrapServer); + this.deviceService.rebootDevice(this.deviceId.id, isBootstrapServer).subscribe(responseReboot => { + if (responseReboot.result === 'SUCCESS') { + this.store.dispatch(new ActionNotificationShow( + { + message: responseReboot.msg, + type: 'success', + duration: 1500, + verticalPosition: 'top', + horizontalPosition: 'left' + })); + } else { + this.store.dispatch(new ActionNotificationShow( + { + message: responseReboot.msg, + type: 'error', + verticalPosition: 'top', + horizontalPosition: 'left' + })); + } + }); } private initClientSecurityConfig(config: Lwm2mSecurityConfigModels): void { From f3b85f395b466ebd14369543b77e75a73f0a385f Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Wed, 22 Oct 2025 10:03:47 +0300 Subject: [PATCH 16/16] lwm2m: bootstrap new: test short serverBsId = 0 --- .../validator/DeviceProfileDataValidatorTest.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java index 4c427544d4..f9d6c035dc 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java @@ -135,8 +135,13 @@ class DeviceProfileDataValidatorTest { } @Test - void testValidateDeviceProfile_Lwm2mShortServerId_Ok_BootstrapShortServerId_0_Error() { - verifyValidationError(123, 0, msgErrorBsRange); + void testValidateDeviceProfile_Lwm2mShortServerId_Ok_BootstrapShortServerId_validate_0_to_null_Ok() { + Integer shortServerId = 123; + Integer shortServerIdBs = 0; + DeviceProfile deviceProfile = getDeviceProfile(shortServerId, shortServerIdBs); + + validator.validateDataImpl(tenantId, deviceProfile); + verify(validator).validateString("Device profile name", deviceProfile.getName()); } @Test