Browse Source

Merge pull request #5583 from thingsboard/develop/3.3.3

Develop 3.3.3 to Master
pull/5801/head
Andrew Shvayka 5 years ago
committed by GitHub
parent
commit
283ec2aa1a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 200
      application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql
  2. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  3. 1
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  4. 27
      application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
  5. 6
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  6. 6
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  7. 6
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  8. 70
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java
  9. 24
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java
  10. 81
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java
  11. 7
      application/src/main/resources/thingsboard.yml
  12. 2
      application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java
  13. 2
      application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java
  14. 2
      application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java
  15. 386
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  16. 70
      application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java
  17. 41
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MLocationParams.java
  18. 71
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  19. 231
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java
  20. 150
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java
  21. 131
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java
  22. 10
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  23. 1
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java
  24. 76
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java
  25. 225
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java
  26. 206
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java
  27. 133
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java
  28. 96
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDeleteTest.java
  29. 165
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java
  30. 186
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java
  31. 190
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java
  32. 226
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java
  33. 63
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java
  34. 335
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java
  35. 176
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java
  36. 35
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java
  37. 17
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java
  38. 21
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java
  39. 29
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java
  40. 42
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java
  41. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelperTest.java
  42. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java
  43. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java
  44. 1
      application/src/test/resources/application-test.properties
  45. 144
      application/src/test/resources/lwm2m/19.xml
  46. 103
      application/src/test/resources/lwm2m/3303.xml
  47. 143
      application/src/test/resources/lwm2m/6.xml
  48. 8
      common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java
  49. 6
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MBootstrapClientCredentialWithKeys.java
  50. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredential.java
  51. 16
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientSecurityCredential.java
  52. 10
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapClientCredential.java
  53. 6
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapClientCredentials.java
  54. 10
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredential.java
  55. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MDeviceCredentials.java
  56. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecBootstrapClientCredential.java
  57. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredential.java
  58. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKBootstrapClientCredential.java
  59. 40
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredential.java
  60. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKBootstrapClientCredential.java
  61. 13
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredential.java
  62. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509BootstrapClientCredential.java
  63. 13
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredential.java
  64. 11
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java
  65. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java
  66. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java
  67. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java
  68. 24
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/AbstractLwM2MBootstrapServerCredential.java
  69. 37
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServerCredential.java
  70. 27
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServersConfiguration.java
  71. 62
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java
  72. 29
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java
  73. 25
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java
  74. 25
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKLwM2MBootstrapServerCredential.java
  75. 25
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKLwM2MBootstrapServerCredential.java
  76. 25
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509LwM2MBootstrapServerCredential.java
  77. 44
      common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java
  78. 1
      common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java
  79. 23
      common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java
  80. 26
      common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mBootstrapTransportComponent.java
  81. 2
      common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java
  82. 1
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
  83. 31
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  84. 135
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java
  85. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java
  86. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java
  87. 190
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java
  88. 284
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java
  89. 128
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java
  90. 83
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java
  91. 40
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java
  92. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java
  93. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  94. 105
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java
  95. 22
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java
  96. 61
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java
  97. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java
  98. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentials.java
  99. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java
  100. 14
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java

200
application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql

@ -0,0 +1,200 @@
--
-- Copyright © 2016-2021 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.
--
CREATE OR REPLACE PROCEDURE update_profile_bootstrap()
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE device_profile
SET profile_data = jsonb_set(
profile_data,
'{transportConfiguration}',
get_bootstrap(
profile_data::jsonb #> '{transportConfiguration}',
subquery.publickey_bs,
subquery.publickey_lw),
true)
FROM (
SELECT id,
encode(
decode(profile_data::json #> '{transportConfiguration,bootstrap,bootstrapServer}' ->>
'serverPublicKey', 'hex')::bytea, 'base64') AS publickey_bs,
encode(
decode(profile_data::json #> '{transportConfiguration,bootstrap,lwm2mServer}' ->>
'serverPublicKey', 'hex')::bytea, 'base64') AS publickey_lw
FROM device_profile
WHERE transport_type = 'LWM2M'
) AS subquery
WHERE device_profile.id = subquery.id
AND subquery.publickey_bs IS NOT NULL
AND subquery.publickey_lw IS NOT NULL;
END;
$$;
CREATE OR REPLACE FUNCTION get_bootstrap(transport_configuration_in jsonb, publickey_bs text,
publickey_lw text) RETURNS jsonb AS
$$
DECLARE
bootstrap_new jsonb;
bootstrap_in jsonb;
BEGIN
bootstrap_in := transport_configuration_in::jsonb #> '{bootstrap}';
bootstrap_new := json_build_array(
json_build_object('shortServerId', bootstrap_in::json #> '{bootstrapServer}' -> 'serverId',
'securityMode', bootstrap_in::json #> '{bootstrapServer}' ->> 'securityMode',
'binding', bootstrap_in::json #> '{servers}' ->> 'binding',
'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime',
'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled',
'defaultMinPeriod', bootstrap_in::json #> '{servers}' -> 'defaultMinPeriod',
'host', bootstrap_in::json #> '{bootstrapServer}' ->> 'host',
'port', bootstrap_in::json #> '{bootstrapServer}' -> 'port',
'serverPublicKey', publickey_bs,
'bootstrapServerIs', true,
'clientHoldOffTime', bootstrap_in::json #> '{bootstrapServer}' -> 'clientHoldOffTime',
'bootstrapServerAccountTimeout',
bootstrap_in::json #> '{bootstrapServer}' -> 'bootstrapServerAccountTimeout'
),
json_build_object('shortServerId', bootstrap_in::json #> '{lwm2mServer}' -> 'serverId',
'securityMode', bootstrap_in::json #> '{lwm2mServer}' ->> 'securityMode',
'binding', bootstrap_in::json #> '{servers}' ->> 'binding',
'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime',
'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled',
'defaultMinPeriod', bootstrap_in::json #> '{servers}' -> 'defaultMinPeriod',
'host', bootstrap_in::json #> '{lwm2mServer}' ->> 'host',
'port', bootstrap_in::json #> '{lwm2mServer}' -> 'port',
'serverPublicKey', publickey_lw,
'bootstrapServerIs', false,
'clientHoldOffTime', bootstrap_in::json #> '{lwm2mServer}' -> 'clientHoldOffTime',
'bootstrapServerAccountTimeout',
bootstrap_in::json #> '{lwm2mServer}' -> 'bootstrapServerAccountTimeout'
)
);
RETURN jsonb_set(
transport_configuration_in,
'{bootstrap}',
bootstrap_new,
true) || '{"bootstrapServerUpdateEnable": true}';
END ;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE update_device_credentials_to_base64_and_bootstrap()
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE device_credentials
SET credentials_value = get_device_and_bootstrap(credentials_value::text)
WHERE credentials_type = 'LWM2M_CREDENTIALS';
END;
$$;
CREATE OR REPLACE FUNCTION get_device_and_bootstrap(IN credentials_value text, OUT credentials_value_new text)
LANGUAGE plpgsql AS
$$
DECLARE
client_secret_key text;
client_public_key_or_id text;
client_key_value_object jsonb;
client_bootstrap_server_value_object jsonb;
client_bootstrap_server_object jsonb;
client_bootstrap_object jsonb;
BEGIN
credentials_value_new := credentials_value;
IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'RPK' AND
NULLIF((credentials_value::jsonb #> '{client}' ->> 'key' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN
client_public_key_or_id := encode(decode(credentials_value::jsonb #> '{client}' ->> 'key', 'hex')::bytea, 'base64');
client_key_value_object := json_build_object(
'endpoint', credentials_value::jsonb #> '{client}' ->> 'endpoint',
'securityConfigClientMode', credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode',
'key', client_public_key_or_id);
credentials_value_new :=
credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb;
END IF;
IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'X509' AND
NULLIF((credentials_value::jsonb #> '{client}' ->> 'cert' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN
client_public_key_or_id :=
encode(decode(credentials_value::jsonb #> '{client}' ->> 'cert', 'hex')::bytea, 'base64');
client_key_value_object := json_build_object(
'endpoint', credentials_value::jsonb #> '{client}' ->> 'endpoint',
'securityConfigClientMode', credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode',
'cert', client_public_key_or_id);
credentials_value_new :=
credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb;
END IF;
IF credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'RPK' OR
credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'X509' THEN
IF NULLIF((credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text,
'false') = 'true' AND
NULLIF(
(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientPublicKeyOrId' ~ '^[0-9a-fA-F]+$')::text,
'false') = 'true' THEN
client_secret_key :=
encode(decode(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey', 'hex')::bytea,
'base64');
client_public_key_or_id := encode(
decode(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientPublicKeyOrId', 'hex')::bytea,
'base64');
client_bootstrap_server_value_object := jsonb_build_object(
'securityMode', credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode',
'clientPublicKeyOrId', client_public_key_or_id,
'clientSecretKey', client_secret_key
);
client_bootstrap_server_object := jsonb_build_object('lwm2mServer', client_bootstrap_server_value_object::jsonb);
client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb;
credentials_value_new :=
jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb;
END IF;
END IF;
IF credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'RPK' OR
credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'X509' THEN
IF NULLIF(
(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text,
'false') = 'true' AND
NULLIF(
(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientPublicKeyOrId' ~ '^[0-9a-fA-F]+$')::text,
'false') = 'true' THEN
client_secret_key :=
encode(
decode(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientSecretKey', 'hex')::bytea,
'base64');
client_public_key_or_id := encode(
decode(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientPublicKeyOrId', 'hex')::bytea,
'base64');
client_bootstrap_server_value_object := jsonb_build_object(
'securityMode', credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode',
'clientPublicKeyOrId', client_public_key_or_id,
'clientSecretKey', client_secret_key
);
client_bootstrap_server_object :=
jsonb_build_object('bootstrapServer', client_bootstrap_server_value_object::jsonb);
client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb;
credentials_value_new :=
jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb;
END IF;
END IF;
END;
$$;

4
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -126,7 +126,6 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.EdgeLicenseService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.resource.TbResourceService;
@ -261,9 +260,6 @@ public abstract class BaseController {
@Autowired
protected TbDeviceProfileCache deviceProfileCache;
@Autowired
protected LwM2MServerSecurityInfoRepository lwM2MServerSecurityInfoRepository;
@Autowired(required = false)
protected EdgeService edgeService;

1
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -219,7 +219,6 @@ public class DeviceProfileController extends BaseController {
isSoftwareChanged = true;
}
}
DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);

27
application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java

@ -20,6 +20,7 @@ import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@ -29,10 +30,10 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.lwm2m.LwM2MService;
import java.util.Map;
@ -41,15 +42,17 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CU
@Slf4j
@RestController
@TbCoreComponent
@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'")
@RequestMapping("/api")
public class Lwm2mController extends BaseController {
@Autowired
private DeviceController deviceController;
public static final String IS_BOOTSTRAP_SERVER = "isBootstrapServer";
@Autowired
private LwM2MService lwM2MService;
public static final String IS_BOOTSTRAP_SERVER = "isBootstrapServer";
@ApiOperation(value = "Get Lwm2m Bootstrap SecurityInfo (getLwm2mBootstrapSecurityInfo)",
notes = "Get the Lwm2m Bootstrap SecurityInfo object (of the current server) based on the provided isBootstrapServer parameter. If isBootstrapServer == true, get the parameters of the current Bootstrap Server. If isBootstrapServer == false, get the parameters of the current Lwm2m Server. Used for client settings when starting the client in Bootstrap mode. " +
@ -58,14 +61,14 @@ public class Lwm2mController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{isBootstrapServer}", method = RequestMethod.GET)
@ResponseBody
public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(
@ApiParam(value = IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION)
@PathVariable(IS_BOOTSTRAP_SERVER) boolean bootstrapServer) throws ThingsboardException {
try {
return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(bootstrapServer);
} catch (Exception e) {
throw handleException(e);
}
public LwM2MServerSecurityConfigDefault getLwm2mBootstrapSecurityInfo(
@ApiParam(value = IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION)
@PathVariable(IS_BOOTSTRAP_SERVER) boolean bootstrapServer) throws ThingsboardException {
try {
return lwM2MService.getServerSecurityInfo(bootstrapServer);
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(hidden = true, value = "Save device with credentials (Deprecated)")

6
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
@ -185,9 +185,9 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
setValues(client, fields, Set.of(BulkImportColumnType.LWM2M_CLIENT_SECURITY_CONFIG_MODE,
BulkImportColumnType.LWM2M_CLIENT_ENDPOINT, BulkImportColumnType.LWM2M_CLIENT_IDENTITY,
BulkImportColumnType.LWM2M_CLIENT_KEY, BulkImportColumnType.LWM2M_CLIENT_CERT));
LwM2MClientCredentials lwM2MClientCredentials = JacksonUtil.treeToValue(client, LwM2MClientCredentials.class);
LwM2MClientCredential lwM2MClientCredential = JacksonUtil.treeToValue(client, LwM2MClientCredential.class);
// so that only fields needed for specific type of lwM2MClientCredentials were saved in json
lwm2mCredentials.set("client", JacksonUtil.valueToTree(lwM2MClientCredentials));
lwm2mCredentials.set("client", JacksonUtil.valueToTree(lwM2MClientCredential));
ObjectNode bootstrapServer = JacksonUtil.newObjectNode();
setValues(bootstrapServer, fields, Set.of(BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE,

6
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -475,6 +475,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Updating server`s public key from HexDec to Base64 in profile for LWM2M...");
conn.createStatement().execute("call update_profile_bootstrap();");
log.info("Server`s public key from HexDec to Base64 in profile for LWM2M updated.");
log.info("Updating client`s public key and secret key from HexDec to Base64 for LWM2M...");
conn.createStatement().execute("call update_device_credentials_to_base64_and_bootstrap();");
log.info("Client`s public key and secret key from HexDec to Base64 for LWM2M updated.");
log.info("Updating schema settings...");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003003;");
log.info("Schema updated.");

6
application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java

@ -57,6 +57,12 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
clearCacheByName("tenantProfiles");
clearCacheByName("relations");
break;
case "3.3.2":
log.info("Clear cache to upgrade from version 3.3.2 to 3.3.3 ...");
clearCacheByName("devices");
clearCacheByName("deviceProfiles");
clearCacheByName("tenantProfiles");
break;
default:
//Do nothing, since cache cleanup is optional.
}

70
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java

@ -1,70 +0,0 @@
/**
* Copyright © 2016-2021 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.service.lwm2m;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class LwM2MServerSecurityInfoRepository {
private final LwM2MTransportServerConfig serverConfig;
private final LwM2MTransportBootstrapConfig bootstrapConfig;
public ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer) {
ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig);
result.setBootstrapServerIs(bootstrapServer);
return result;
}
private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig) {
ServerSecurityConfig bsServ = new ServerSecurityConfig();
bsServ.setServerId(serverConfig.getId());
bsServ.setHost(serverConfig.getHost());
bsServ.setPort(serverConfig.getPort());
bsServ.setSecurityHost(serverConfig.getSecureHost());
bsServ.setSecurityPort(serverConfig.getSecurePort());
bsServ.setServerPublicKey(getPublicKey(serverConfig));
return bsServ;
}
private String getPublicKey(LwM2MSecureServerConfig config) {
try {
SslCredentials sslCredentials = config.getSslCredentials();
if (sslCredentials != null) {
return Hex.encodeHexString(sslCredentials.getPublicKey().getEncoded());
}
} catch (Exception e) {
log.trace("Failed to fetch public key from key store!", e);
}
return "";
}
}

24
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2021 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.service.lwm2m;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault;
public interface LwM2MService {
LwM2MServerSecurityConfigDefault getServerSecurityInfo(boolean bootstrapServer);
}

81
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java

@ -0,0 +1,81 @@
/**
* Copyright © 2016-2021 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.service.lwm2m;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault;
import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import java.util.Optional;
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'")
public class LwM2MServiceImpl implements LwM2MService {
private final LwM2MTransportServerConfig serverConfig;
private final Optional<LwM2MTransportBootstrapConfig> bootstrapConfig;
@Override
public LwM2MServerSecurityConfigDefault getServerSecurityInfo(boolean bootstrapServer) {
LwM2MSecureServerConfig bsServerConfig = bootstrapServer ? bootstrapConfig.orElse(null) : serverConfig;
if (bsServerConfig!= null) {
LwM2MServerSecurityConfigDefault result = getServerSecurityConfig(bsServerConfig);
result.setBootstrapServerIs(bootstrapServer);
return result;
}
else {
return null;
}
}
private LwM2MServerSecurityConfigDefault getServerSecurityConfig(LwM2MSecureServerConfig bsServerConfig) {
LwM2MServerSecurityConfigDefault bsServ = new LwM2MServerSecurityConfigDefault();
bsServ.setShortServerId(bsServerConfig.getId());
bsServ.setHost(bsServerConfig.getHost());
bsServ.setPort(bsServerConfig.getPort());
bsServ.setSecurityHost(bsServerConfig.getSecureHost());
bsServ.setSecurityPort(bsServerConfig.getSecurePort());
byte[] publicKeyBase64 = getPublicKey(bsServerConfig);
if (publicKeyBase64 == null) {
bsServ.setServerPublicKey("");
} else {
bsServ.setServerPublicKey(Base64.encodeBase64String(publicKeyBase64));
}
return bsServ;
}
private byte[] getPublicKey(LwM2MSecureServerConfig config) {
try {
SslCredentials sslCredentials = config.getSslCredentials();
if (sslCredentials != null) {
return sslCredentials.getPublicKey().getEncoded();
}
} catch (Exception e) {
log.trace("Failed to fetch public key from key store!", e);
}
return null;
}
}

7
application/src/main/resources/thingsboard.yml

@ -165,7 +165,7 @@ ui:
# Help parameters
help:
# Base url for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.2}"
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.3}"
database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
@ -759,7 +759,7 @@ transport:
# Only Certificate_x509:
skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
bootstrap:
enable: "${LWM2M_ENABLED_BS:true}"
enabled: "${LWM2M_ENABLED_BS:true}"
id: "${LWM2M_SERVER_ID_BS:111}"
bind_address: "${LWM2M_BS_BIND_ADDRESS:0.0.0.0}"
bind_port: "${LWM2M_BS_BIND_PORT:5687}"
@ -818,6 +818,9 @@ transport:
downlink_pool_size: "${LWM2M_DOWNLINK_POOL_SIZE:10}"
ota_pool_size: "${LWM2M_OTA_POOL_SIZE:10}"
clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}"
log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}"
psm_activity_timer: "${LWM2M_PSM_ACTIVITY_TIMER:10000}"
paging_transmission_window: "${LWM2M_PAGING_TRANSMISSION_WINDOW:10000}"
# Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}"
network_config: # In this section you can specify custom parameters for LwM2M network configuration and expose the env variables to configure outside

2
application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java

@ -30,7 +30,7 @@ import org.thingsboard.server.queue.memory.InMemoryStorage;
"org.thingsboard.server.transport.*.claim.sql.*Test",
"org.thingsboard.server.transport.*.provision.sql.*Test",
"org.thingsboard.server.transport.*.credentials.sql.*Test",
"org.thingsboard.server.transport.lwm2m.sql.*Test"
"org.thingsboard.server.transport.lwm2m.*.sql.*Test"
})
public class TransportSqlTestSuite {

2
application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java

@ -146,7 +146,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE");
String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue());
String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue());
String sha3Hash = EncryptionUtil.getSha3Hash(cert);
Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash);

2
application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java

@ -145,7 +145,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString());
Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE);
String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue());
String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue());
String sha3Hash = EncryptionUtil.getSha3Hash(cert);
Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash);

386
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -19,13 +19,10 @@ import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.io.IOUtils;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.SocketUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Device;
@ -33,13 +30,13 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecServerCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
@ -61,38 +58,18 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import static org.eclipse.leshan.client.object.Security.noSec;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
@DaoSqlTest
public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
protected final String TRANSPORT_CONFIGURATION = "{\n" +
protected String transportConfiguration = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {\n" +
@ -106,178 +83,70 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrap\": {\n" +
" \"servers\": {\n" +
" \"binding\": \"U\",\n" +
" \"shortId\": 123,\n" +
" \"lifetime\": 300,\n" +
" \"notifIfDisabled\": true,\n" +
" \"defaultMinPeriod\": 1\n" +
" \"bootstrapServerUpdateEnable\": true,\n" +
" \"bootstrap\": [\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5687,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 111,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" \"lwm2mServer\": {\n" +
" \"host\": \"localhost\",\n" +
" \"port\": 5686,\n" +
" \"serverId\": 123,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" \"bootstrapServer\": {\n" +
" \"host\": \"localhost\",\n" +
" \"port\": 5687,\n" +
" \"serverId\": 111,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"serverPublicKey\": \"\",\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5685,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 123,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" },\n" +
" ],\n" +
" \"clientLwM2mSettings\": {\n" +
" \"clientOnlyObserveAfterConnect\": 1,\n" +
" \"edrxCycle\": null,\n" +
" \"powerMode\": \"DRX\",\n" +
" \"fwUpdateResource\": null,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"swUpdateStrategy\": 1\n" +
" \"psmActivityTimer\": null,\n" +
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" +
" \"pagingTransmissionWindow\": null,\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" +
"}";
protected DeviceProfile deviceProfile;
protected ScheduledExecutorService executor;
protected TbTestWebSocketClient wsClient;
protected final PublicKey clientPublicKey; // client public key used for RPK
protected final PrivateKey clientPrivateKey; // client private key used for RPK
protected final PublicKey serverPublicKey; // server public key used for RPK
protected final PrivateKey serverPrivateKey; // server private key used for RPK
// client private key used for X509
protected final PrivateKey clientPrivateKeyFromCert;
// server private key used for X509
protected final PrivateKey serverPrivateKeyFromCert;
// client certificate signed by rootCA with a good CN (CN start by leshan_integration_test)
protected final X509Certificate clientX509Cert;
// client certificate signed by rootCA but with bad CN (CN does not start by leshan_integration_test)
protected final X509Certificate clientX509CertWithBadCN;
// client certificate self-signed with a good CN (CN start by leshan_integration_test)
protected final X509Certificate clientX509CertSelfSigned;
// client certificate signed by another CA (not rootCA) with a good CN (CN start by leshan_integration_test)
protected final X509Certificate clientX509CertNotTrusted;
// server certificate signed by rootCA
protected final X509Certificate serverX509Cert;
// self-signed server certificate
protected final X509Certificate serverX509CertSelfSigned;
// rootCA used by the server
protected final X509Certificate rootCAX509Cert;
// certificates trustedby the server (should contain rootCA)
protected final Certificate[] trustedCertificates = new Certificate[1];
protected static final int SECURE_PORT = 5686;
protected static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT));
protected static final String ENDPOINT = "deviceAEndpoint";
protected static final String SECURE_URI = "coaps://localhost:" + SECURE_PORT;
protected static final int PORT = 5685;
protected static final Security SECURITY = noSec("coap://localhost:" + PORT, 123);
protected static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT));
private final LwM2MBootstrapCredentials defaultBootstrapCredentials;
protected LwM2MTestClient client;
private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials;
private String[] resources;
protected String endpoint;
public AbstractLwM2MIntegrationTest() {
// create client credentials
try {
// Get point values
byte[] publicX = Hex
.decodeHex("89c048261979208666f2bfb188be1968fc9021c416ce12828c06f4e314c167b5".toCharArray());
byte[] publicY = Hex
.decodeHex("cbf1eb7587f08e01688d9ada4be859137ca49f79394bad9179326b3090967b68".toCharArray());
byte[] privateS = Hex
.decodeHex("e67b68d2aaeb6550f19d98cade3ad62b39532e02e6b422e1f7ea189dabaea5d2".toCharArray());
// Get Elliptic Curve Parameter spec for secp256r1
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
// Create key specs
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
// Get keys
clientPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
clientPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
// Get certificates from key store
char[] clientKeyStorePwd = "client".toCharArray();
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream clientKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/clientKeyStore.jks")) {
clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd);
}
clientPrivateKeyFromCert = (PrivateKey) clientKeyStore.getKey("client", clientKeyStorePwd);
clientX509Cert = (X509Certificate) clientKeyStore.getCertificate("client");
clientX509CertWithBadCN = (X509Certificate) clientKeyStore.getCertificate("client_bad_cn");
clientX509CertSelfSigned = (X509Certificate) clientKeyStore.getCertificate("client_self_signed");
clientX509CertNotTrusted = (X509Certificate) clientKeyStore.getCertificate("client_not_trusted");
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
// create server credentials
try {
// Get point values
byte[] publicX = Hex
.decodeHex("fcc28728c123b155be410fc1c0651da374fc6ebe7f96606e90d927d188894a73".toCharArray());
byte[] publicY = Hex
.decodeHex("d2ffaa73957d76984633fc1cc54d0b763ca0559a9dff9706e9f4557dacc3f52a".toCharArray());
byte[] privateS = Hex
.decodeHex("1dae121ba406802ef07c193c1ee4df91115aabd79c1ed7f4c0ef7ef6a5449400".toCharArray());
// Get Elliptic Curve Parameter spec for secp256r1
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
// Create key specs
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
// Get keys
serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
// Get certificates from key store
char[] serverKeyStorePwd = "server".toCharArray();
KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream serverKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/serverKeyStore.jks")) {
serverKeyStore.load(serverKeyStoreFile, serverKeyStorePwd);
}
serverPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("server", serverKeyStorePwd);
rootCAX509Cert = (X509Certificate) serverKeyStore.getCertificate("rootCA");
serverX509Cert = (X509Certificate) serverKeyStore.getCertificate("server");
serverX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("server_self_signed");
trustedCertificates[0] = rootCAX509Cert;
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
defaultBootstrapCredentials = new LwM2MBootstrapCredentials();
NoSecServerCredentials serverCredentials = new NoSecServerCredentials();
defaultBootstrapCredentials.setBootstrapServer(serverCredentials);
defaultBootstrapCredentials.setLwm2mServer(serverCredentials);
this.defaultBootstrapCredentials = new LwM2MBootstrapClientCredentials();
NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential();
this.defaultBootstrapCredentials.setBootstrapServer(serverCredentials);
this.defaultBootstrapCredentials.setLwm2mServer(serverCredentials);
}
@Before
public void beforeTest() throws Exception {
public void init () throws Exception{
executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-scheduled"));
loginTenantAdmin();
String[] resources = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"};
for (String resourceName : resources) {
for (String resourceName : this.resources) {
TbResource lwModel = new TbResource();
lwModel.setResourceType(ResourceType.LWM2M_MODEL);
lwModel.setTitle(resourceName);
@ -292,9 +161,57 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
wsClient = buildAndConnectWebSocketClient();
}
@Before
public void beforeTest() throws Exception {
this.init();
}
@After
public void after() {
wsClient.close();
clientDestroy();
executor.shutdownNow();
}
public void basicTestConnectionObserveTelemetry(Security security,
LwM2MClientCredential credentials,
NetworkConfig coapConfig,
String endpoint) throws Exception {
createDeviceProfile(transportConfiguration);
Device device = createDevice(credentials);
SingleEntityFilter sef = new SingleEntityFilter();
sef.setSingleEntity(device.getId());
LatestValueCmd latestCmd = new LatestValueCmd();
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel")));
EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
wsClient.waitForReply();
wsClient.registerWaitForUpdate();
this.endpoint = endpoint;
createNewClient(security, coapConfig, false);
String msg = wsClient.waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
}
protected void createDeviceProfile(String transportConfiguration) throws Exception {
deviceProfile = new DeviceProfile();
deviceProfile.setName("LwM2M");
deviceProfile.setType(DeviceProfileType.DEFAULT);
deviceProfile.setTenantId(tenantId);
@ -312,7 +229,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
Assert.assertNotNull(deviceProfile);
}
protected Device createDevice(LwM2MClientCredentials clientCredentials) throws Exception {
protected Device createDevice(LwM2MClientCredential clientCredentials) throws Exception {
Device device = new Device();
device.setName("Device A");
device.setDeviceProfileId(deviceProfile.getId());
@ -326,7 +243,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials();
credentials.setClient(clientCredentials);
credentials.setBootstrap(defaultBootstrapCredentials);
@ -335,94 +251,30 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
return device;
}
protected OtaPackageInfo createFirmware() throws Exception {
String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
OtaPackageInfo firmwareInfo = new OtaPackageInfo();
firmwareInfo.setDeviceProfileId(deviceProfile.getId());
firmwareInfo.setType(FIRMWARE);
firmwareInfo.setTitle("My firmware");
firmwareInfo.setVersion("v1.0");
OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class);
MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1});
return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256");
public NoSecClientCredential createNoSecClientCredentials(String endpoint) {
NoSecClientCredential clientCredentials = new NoSecClientCredential();
clientCredentials.setEndpoint(endpoint);
return clientCredentials;
}
protected OtaPackageInfo createSoftware() throws Exception {
String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
OtaPackageInfo swInfo = new OtaPackageInfo();
swInfo.setDeviceProfileId(deviceProfile.getId());
swInfo.setType(SOFTWARE);
swInfo.setTitle("My sw");
swInfo.setVersion("v1.0");
OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", swInfo, OtaPackageInfo.class);
MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1});
return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256");
public void setResources(String[] resources) {
this.resources = resources;
}
protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception {
MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params);
postRequest.file(content);
setJwtToken(postRequest);
return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class);
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
@After
public void after() {
executor.shutdownNow();
wsClient.close();
public void createNewClient(Security security, NetworkConfig coapConfig, boolean isRpc) throws Exception {
clientDestroy();
client = new LwM2MTestClient(this.executor, this.endpoint);
int clientPort = SocketUtils.findAvailableTcpPort();
client.init(security, coapConfig, clientPort, isRpc);
}
public void basicTestConnectionObserveTelemetry(Security security,
LwM2MClientCredentials credentials,
NetworkConfig coapConfig,
String endpoint) throws Exception {
LwM2MTestClient client = null;
try {
createDeviceProfile(TRANSPORT_CONFIGURATION);
Device device = createDevice(credentials);
SingleEntityFilter sef = new SingleEntityFilter();
sef.setSingleEntity(device.getId());
LatestValueCmd latestCmd = new LatestValueCmd();
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel")));
EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
wsClient.waitForReply();
wsClient.registerWaitForUpdate();
client = new LwM2MTestClient(executor, endpoint);
client.init(security, coapConfig);
String msg = wsClient.waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
} finally {
if (client != null) {
client.destroy();
}
private void clientDestroy() {
if (client != null) {
client.destroy();
}
}
}

70
application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java

@ -0,0 +1,70 @@
/**
* Copyright © 2016-2021 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;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.client.object.Security;
import static org.eclipse.leshan.client.object.Security.noSec;
public class Lwm2mTestHelper {
// Server
public static final int SECURE_PORT = 5686;
public static final int SECURE_PORT_BS = 5688;
public static final String HOST = "localhost";
public static final String HOST_BS = "localhost";
public static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT));
public static final String ENDPOINT_SECURITY = "deviceAEndpoint";
public static final String SECURE_URI = "coaps://localhost:" + SECURE_PORT;
public static final int PORT = 5685;
public static final int PORT_BS = 5687;
public static final int SHORT_SERVER_ID = 123;
public static final int SHORT_SERVER_ID_BS = 111;
public static final Security SECURITY = noSec("coap://localhost:" + PORT, SHORT_SERVER_ID);
public static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT));
// Models
public static final String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"};
public static final int BINARY_APP_DATA_CONTAINER = 19;
public static final int TEMPERATURE_SENSOR = 3303;
// Ids in Client
public static final int objectId_0 = 0;
public static final int objectInstanceId_0 = 0;
public static final int objectInstanceId_1 = 1;
public static final int objectInstanceId_2 = 2;
public static final int objectInstanceId_12 = 12;
public static final int resourceId_0 = 0;
public static final int resourceId_1 = 1;
public static final int resourceId_2 = 2;
public static final int resourceId_3 = 3;
public static final int resourceId_4 = 4;
public static final int resourceId_8 = 8;
public static final int resourceId_9 = 9;
public static final int resourceId_11 = 11;
public static final int resourceId_14 = 14;
public static final int resourceId_15= 15;
public static final int resourceInstanceId_2 = 2;
public static final String resourceIdName_3_9 = "batteryLevel";
public static final String resourceIdName_3_14 = "UtfOffset";
public static final String resourceIdName_19_0_0 = "dataRead";
public static final String resourceIdName_19_1_0 = "dataWrite";
}

41
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MLocationParams.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2021 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.client;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Data
public class LwM2MLocationParams {
private Float latitude;
private Float longitude;
private Float scaleFactor = 1.0F;
private final String locationPos = "50.4501:30.5234";
private final Float locationScaleFactor = 1.0F;
protected void getPos() {
this.latitude = null;
this.longitude = null;
int c = locationPos.indexOf(':');
this.latitude = Float.valueOf(locationPos.substring(0, c));
this.longitude = Float.valueOf(locationPos.substring(c + 1));
}
}

71
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java

@ -31,7 +31,6 @@ import org.eclipse.leshan.client.observer.LwM2mClientObserver;
import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
import org.eclipse.leshan.client.resource.ObjectsInitializer;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.LwM2mId;
import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.californium.EndpointFactory;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
@ -39,12 +38,13 @@ import org.eclipse.leshan.core.model.LwM2mModel;
import org.eclipse.leshan.core.model.ObjectLoader;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.StaticModel;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
import org.eclipse.leshan.core.request.BootstrapRequest;
import org.eclipse.leshan.core.request.DeregisterRequest;
import org.eclipse.leshan.core.request.RegisterRequest;
import org.eclipse.leshan.core.request.UpdateRequest;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import org.junit.Assert;
import java.io.IOException;
@ -53,11 +53,20 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
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.LOCATION;
import static org.eclipse.leshan.core.LwM2mId.SECURITY;
import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_12;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources;
@Slf4j
@Data
@ -66,24 +75,36 @@ public class LwM2MTestClient {
private final ScheduledExecutorService executor;
private final String endpoint;
private LeshanClient client;
private Server lwm2mServer;
private SimpleLwM2MDevice lwM2MDevice;
private FwLwM2MDevice fwLwM2MDevice;
private SwLwM2MDevice swLwM2MDevice;
private LwM2mBinaryAppDataContainer lwM2MBinaryAppDataContainer;
private LwM2MLocationParams locationParams;
private LwM2mTemperatureSensor lwM2MTemperatureSensor;
public void init(Security security, NetworkConfig coapConfig) throws InvalidDDFFileException, IOException {
public void init(Security security, NetworkConfig coapConfig, int port, boolean isRpc) throws InvalidDDFFileException, IOException {
Assert.assertNull("client already initialized", client);
String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "9.xml"};
List<ObjectModel> models = new ArrayList<>();
for (String resourceName : resources) {
models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName));
}
LwM2mModel model = new StaticModel(models);
ObjectsInitializer initializer = new ObjectsInitializer(model);
initializer.setInstancesForObject(SECURITY, security);
initializer.setInstancesForObject(SERVER, new Server(123, 300));
initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice());
initializer.setInstancesForObject(SERVER, lwm2mServer = new Server(123, 300));
initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice());
initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice());
initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice());
initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class);
initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class);
initializer.setInstancesForObject(BINARY_APP_DATA_CONTAINER, lwM2MBinaryAppDataContainer = new LwM2mBinaryAppDataContainer(executor, objectInstanceId_0),
new LwM2mBinaryAppDataContainer(executor, objectInstanceId_1));
locationParams = new LwM2MLocationParams();
locationParams.getPos();
initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, objectInstanceId_0));
initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2MTemperatureSensor = new LwM2mTemperatureSensor(executor, objectInstanceId_0), new LwM2mTemperatureSensor(executor, objectInstanceId_12));
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setRecommendedCipherSuitesOnly(true);
@ -127,15 +148,16 @@ public class LwM2MTestClient {
LeshanClientBuilder builder = new LeshanClientBuilder(endpoint);
builder.setLocalAddress("0.0.0.0", 11000);
builder.setLocalAddress("0.0.0.0", port);
builder.setObjects(initializer.createAll());
builder.setCoapConfig(coapConfig);
builder.setDtlsConfig(dtlsConfig);
builder.setRegistrationEngineFactory(engineFactory);
builder.setEndpointFactory(endpointFactory);
builder.setSharedExecutor(executor);
builder.setDecoder(new DefaultLwM2mNodeDecoder(true));
builder.setEncoder(new DefaultLwM2mNodeEncoder(true));
builder.setDecoder(new DefaultLwM2mDecoder(false));
builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false));
client = builder.build();
LwM2mClientObserver observer = new LwM2mClientObserver() {
@ -227,18 +249,39 @@ public class LwM2MTestClient {
}
};
this.client.addObserver(observer);
client.start();
if (!isRpc) {
client.start();
}
}
public void destroy() {
client.destroy(true);
if (client != null) {
client.destroy(true);
}
if (lwm2mServer != null) {
lwm2mServer = null;
}
if (lwM2MDevice != null) {
lwM2MDevice.destroy();
}
if (fwLwM2MDevice != null) {
fwLwM2MDevice.destroy();
}
if (swLwM2MDevice != null) {
swLwM2MDevice.destroy();
}
if (lwM2MBinaryAppDataContainer != null) {
lwM2MBinaryAppDataContainer.destroy();
}
if (lwM2MTemperatureSensor != null) {
lwM2MTemperatureSensor.destroy();
}
}
public void start() {
if (client != null) {
client.start();
}
}
}

231
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java

@ -0,0 +1,231 @@
/**
* Copyright © 2016-2021 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.client;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.WriteResponse;
import javax.security.auth.Destroyable;
import java.sql.Time;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements Destroyable {
/**
* id = 0
* Multiple
* base64
*/
/**
* Example1:
* InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFka
* W5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo=
* "serviceId":"Meter",
* "serviceData":{
* "currentReading":"46.3",
* "signalStrength":16,
* "dailyActivityTime":5706
*/
/**
* Example2:
* InNlcnZpY2VJZCI6IldhdGVyTWV0ZXIiLA0KImNtZCI6IlNFVF9URU1QRVJBVFVSRV9SRUFEX
* 1BFUklPRCIsDQoicGFyYXMiOnsNCiJ2YWx1ZSI6NA0KICAgIH0sDQoNCg0K
* "serviceId":"WaterMeter",
* "cmd":"SET_TEMPERATURE_READ_PERIOD",
* "paras":{
* "value":4
* },
*/
// private String data = "InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFkaW5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo=";
// private byte[] data;
Map<Integer, byte[]> data;
private Integer priority = 0;
private Time timestamp;
private String description;
private String dataFormat;
private Integer appID = -1;
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 4, 5);
public LwM2mBinaryAppDataContainer() {
}
public LwM2mBinaryAppDataContainer(ScheduledExecutorService executorService, Integer id) {
try {
if (id != null) this.setId(id);
executorService.scheduleWithFixedDelay(() ->
// fireResourcesChange(0, 2), 5000, 5000, TimeUnit.MILLISECONDS);
fireResourcesChange(0, 2), 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
}
}
@Override
public ReadResponse read(ServerIdentity identity, int resourceId) {
// log.warn("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
try {
switch (resourceId) {
case 0:
// log.warn("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
ReadResponse response = ReadResponse.success(resourceId, getData(), ResourceModel.Type.OPAQUE);
// log.warn("Response [{}]", response);
return response;
case 1:
return ReadResponse.success(resourceId, getPriority());
case 2:
return ReadResponse.success(resourceId, getTimestamp());
case 3:
return ReadResponse.success(resourceId, getDescription());
case 4:
return ReadResponse.success(resourceId, getDataFormat());
case 5:
return ReadResponse.success(resourceId, getAppID());
default:
return super.read(identity, resourceId);
}
} catch (Exception e) {
return ReadResponse.badRequest(e.getMessage());
}
}
@Override
public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) {
log.info("Write on Device resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
switch (resourceId) {
case 0:
if (setData(value, replace)) {
return WriteResponse.success();
} else {
WriteResponse.badRequest("Invalidate value ...");
}
case 1:
setPriority((Integer) (value.getValue() instanceof Long ? ((Long) value.getValue()).intValue() : value.getValue()));
fireResourcesChange(resourceId);
return WriteResponse.success();
case 2:
setTimestamp(((Date) value.getValue()).getTime());
fireResourcesChange(resourceId);
return WriteResponse.success();
case 3:
setDescription((String) value.getValue());
fireResourcesChange(resourceId);
return WriteResponse.success();
case 4:
setDataFormat((String) value.getValue());
fireResourcesChange(resourceId);
return WriteResponse.success();
case 5:
setAppID((Integer) value.getValue());
fireResourcesChange(resourceId);
return WriteResponse.success();
default:
return super.write(identity, replace, resourceId, value);
}
}
private Integer getAppID() {
return this.appID;
}
private void setAppID(Integer appId) {
this.appID = appId;
}
private void setDataFormat(String value) {
this.dataFormat = value;
}
private String getDataFormat() {
// return this.dataFormat == null ? "base64" : this.dataFormat;
return this.dataFormat == null ? "OPAQUE" : this.dataFormat;
}
private void setDescription(String value) {
this.description = value;
}
private String getDescription() {
return this.description == null ? "meter reading" : this.description;
}
private void setTimestamp(long time) {
this.timestamp = new Time(time);
}
private Time getTimestamp() {
return this.timestamp != null ? this.timestamp : new Time(new Date().getTime());
}
// fireResourcesChange(resourceId);
private boolean setData(LwM2mResource value, boolean replace) {
try {
if (value instanceof LwM2mMultipleResource) {
if (replace || this.data == null) {
this.data = new HashMap<>();
}
value.getInstances().values().forEach(v -> {
this.data.put(v.getId(), (byte[]) v.getValue());
});
return true;
} else {
return false;
}
} catch (Exception e) {
return false;
}
}
private Map<Integer, byte[]> getData() {
// this.data.put(23, new byte[]{0,0, 2,3});
return data;
}
@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}
@Override
public void destroy() {
}
private int getPriority() {
return this.priority;
}
private void setPriority(int value) {
this.priority = value;
}
}

150
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java

@ -0,0 +1,150 @@
/**
* Copyright © 2016-2021 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.client;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.response.ReadResponse;
import javax.security.auth.Destroyable;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
public class LwM2mLocation extends BaseInstanceEnabler implements Destroyable {
private float latitude;
private float longitude;
private float scaleFactor;
private Date timestamp;
protected static final Random RANDOM = new Random();
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 5);
public LwM2mLocation() {
this(null, null, 1.0f);
}
public LwM2mLocation(Float latitude, Float longitude, float scaleFactor) {
if (latitude != null) {
this.latitude = latitude + 90f;
} else {
this.latitude = RANDOM.nextInt(180);
}
if (longitude != null) {
this.longitude = longitude + 180f;
} else {
this.longitude = RANDOM.nextInt(360);
}
this.scaleFactor = scaleFactor;
timestamp = new Date();
}
public LwM2mLocation(Float latitude, Float longitude, float scaleFactor, ScheduledExecutorService executorService, Integer id) {
try {
if (id != null) this.setId(id);
if (latitude != null) {
this.latitude = latitude + 90f;
} else {
this.latitude = RANDOM.nextInt(180);
}
if (longitude != null) {
this.longitude = longitude + 180f;
} else {
this.longitude = RANDOM.nextInt(360);
}
this.scaleFactor = scaleFactor;
timestamp = new Date();
executorService.scheduleWithFixedDelay(() ->
fireResourcesChange(0, 1), 10000, 10000, TimeUnit.MILLISECONDS);
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
}
}
@Override
public ReadResponse read(ServerIdentity identity, int resourceId) {
log.info("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
switch (resourceId) {
case 0:
return ReadResponse.success(resourceId, getLatitude());
case 1:
return ReadResponse.success(resourceId, getLongitude());
case 5:
return ReadResponse.success(resourceId, getTimestamp());
default:
return super.read(identity, resourceId);
}
}
public void moveLocation(String nextMove) {
switch (nextMove.charAt(0)) {
case 'w':
moveLatitude(1.0f);
// log.info("Move to North [{}]/[{}]", getLatitude(), getLongitude());
break;
case 'a':
moveLongitude(-1.0f);
// log.info("Move to East [{}]/[{}]", getLatitude(), getLongitude());
break;
case 's':
moveLatitude(-1.0f);
// log.info("Move to South [{}]/[{}]", getLatitude(), getLongitude());
break;
case 'd':
moveLongitude(1.0f);
// log.info("Move to West [{}]/[{}]", getLatitude(), getLongitude());
break;
}
}
private void moveLatitude(float delta) {
latitude = latitude + delta * scaleFactor;
timestamp = new Date();
fireResourcesChange(0, 5);
}
private void moveLongitude(float delta) {
longitude = longitude + delta * scaleFactor;
timestamp = new Date();
fireResourcesChange(1, 5);
}
public float getLatitude() {
return latitude - 90.0f;
}
public float getLongitude() {
return longitude - 180.f;
}
public Date getTimestamp() {
return timestamp;
}
@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}
}

131
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java

@ -0,0 +1,131 @@
/**
* Copyright © 2016-2021 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.client;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.ReadResponse;
import javax.security.auth.Destroyable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destroyable {
private static final String UNIT_CELSIUS = "cel";
private double currentTemp = 20d;
private double minMeasuredValue = currentTemp;
private double maxMeasuredValue = currentTemp;
protected static final Random RANDOM = new Random();
private static final List<Integer> supportedResources = Arrays.asList(5601, 5602, 5700, 5701);
public LwM2mTemperatureSensor() {
}
public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) {
try {
if (id != null) this.setId(id);
executorService.scheduleWithFixedDelay(this::adjustTemperature, 2000, 2000, TimeUnit.MILLISECONDS);
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
}
}
@Override
public synchronized ReadResponse read(ServerIdentity identity, int resourceId) {
log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
switch (resourceId) {
case 5601:
return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue));
case 5602:
return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue));
case 5700:
return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp));
case 5701:
return ReadResponse.success(resourceId, UNIT_CELSIUS);
default:
return super.read(identity, resourceId);
}
}
@Override
public synchronized ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) {
log.info("Execute on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
switch (resourceId) {
case 5605:
resetMinMaxMeasuredValues();
return ExecuteResponse.success();
default:
return super.execute(identity, resourceId, params);
}
}
private double getTwoDigitValue(double value) {
BigDecimal toBeTruncated = BigDecimal.valueOf(value);
return toBeTruncated.setScale(2, RoundingMode.HALF_UP).doubleValue();
}
private void adjustTemperature() {
float delta = (RANDOM.nextInt(20) - 10) / 10f;
currentTemp += delta;
Integer changedResource = adjustMinMaxMeasuredValue(currentTemp);
if (changedResource != null) {
fireResourcesChange(5700, changedResource);
} else {
fireResourcesChange(5700);
}
}
private synchronized Integer adjustMinMaxMeasuredValue(double newTemperature) {
if (newTemperature > maxMeasuredValue) {
maxMeasuredValue = newTemperature;
return 5602;
} else if (newTemperature < minMeasuredValue) {
minMeasuredValue = newTemperature;
return 5601;
} else {
return null;
}
}
private void resetMinMaxMeasuredValues() {
minMeasuredValue = currentTemp;
maxMeasuredValue = currentTemp;
}
@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}
@Override
public void destroy() {
}
}

10
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java

@ -40,9 +40,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
private static final Random RANDOM = new Random();
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3
// , 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21
);
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21);
@Override
public ReadResponse read(ServerIdentity identity, int resourceId) {
@ -117,7 +115,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
}
private String getManufacturer() {
return "Leshan Demo Device";
return "Thingsboard Demo Lwm2mDevice";
}
private String getModelNumber() {
@ -125,11 +123,11 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
}
private String getSerialNumber() {
return "LT-500-000-0001";
return "Thingsboard-500-000-0001";
}
private String getFirmwareVersion() {
return "1.0.0";
return "1.0.2";
}
private long getErrorCode() {

1
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.transport.lwm2m.client;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity;

76
application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java

@ -0,0 +1,76 @@
/**
* Copyright © 2016-2021 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.ota;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
@DaoSqlTest
public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
private final String[] resources = new String[]{"3.xml", "5.xml", "9.xml"};
public AbstractOtaLwM2MIntegrationTest() {
setResources(this.resources);
}
protected OtaPackageInfo createFirmware() throws Exception {
String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
OtaPackageInfo firmwareInfo = new OtaPackageInfo();
firmwareInfo.setDeviceProfileId(deviceProfile.getId());
firmwareInfo.setType(FIRMWARE);
firmwareInfo.setTitle("My firmware");
firmwareInfo.setVersion("v1.0");
OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class);
MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1});
return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256");
}
protected OtaPackageInfo createSoftware() throws Exception {
String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
OtaPackageInfo swInfo = new OtaPackageInfo();
swInfo.setDeviceProfileId(deviceProfile.getId());
swInfo.setType(SOFTWARE);
swInfo.setTitle("My sw");
swInfo.setVersion("v1.0");
OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", swInfo, OtaPackageInfo.class);
MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1});
return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256");
}
protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception {
MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params);
postRequest.file(content);
setJwtToken(postRequest);
return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class);
}
}

225
application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/NoSecLwM2MIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java

@ -13,20 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.sql;
package org.thingsboard.server.transport.lwm2m.ota.sql;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegrationTest;
import java.util.Arrays;
import java.util.Collections;
@ -48,12 +46,15 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEU
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY;
@Slf4j
public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
public static final int TIMEOUT = 30;
private final String OTA_TRANSPORT_CONFIGURATION = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {\n" +
" \"/5_1.0/0/3\": \"state\",\n" +
@ -91,126 +92,109 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrap\": {\n" +
" \"servers\": {\n" +
" \"binding\": \"UQ\",\n" +
" \"shortId\": 123,\n" +
" \"lifetime\": 300,\n" +
" \"notifIfDisabled\": true,\n" +
" \"defaultMinPeriod\": 1\n" +
" \"bootstrapServerUpdateEnable\": true,\n" +
" \"bootstrap\": [\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5687,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 111,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" \"lwm2mServer\": {\n" +
" \"host\": \"localhost\",\n" +
" \"port\": 5685,\n" +
" \"serverId\": 123,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"serverPublicKey\": \"\",\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" \"bootstrapServer\": {\n" +
" \"host\": \"localhost\",\n" +
" \"port\": 5687,\n" +
" \"serverId\": 111,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"serverPublicKey\": \"\",\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5685,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 123,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" },\n" +
" ],\n" +
" \"clientLwM2mSettings\": {\n" +
" \"edrxCycle\": null,\n" +
" \"powerMode\": \"DRX\",\n" +
" \"fwUpdateResource\": null,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"psmActivityTimer\": null,\n" +
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" +
" \"clientOnlyObserveAfterConnect\": 1,\n" +
" \"powerMode\": \"PSM\",\n" +
" \"fwUpdateResource\": \"\",\n" +
" \"swUpdateResource\": \"\",\n" +
" \"compositeOperationsSupport\": false\n" +
" },\n" +
" \"type\": \"LWM2M\"\n" +
" \"pagingTransmissionWindow\": null,\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" +
"}";
LwM2MTestClient client = null;
@After
public void tearDown() {
if (client != null) {
client.destroy();
}
}
@Test
public void testConnectAndObserveTelemetry() throws Exception {
NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint(ENDPOINT);
super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, ENDPOINT);
}
@Test
public void testFirmwareUpdateWithClientWithoutFirmwareInfo() throws Exception {
LwM2MTestClient client = null;
try {
createDeviceProfile(TRANSPORT_CONFIGURATION);
NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint(ENDPOINT);
Device device = createDevice(clientCredentials);
public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception {
String endpoint = "WithoutFirmwareInfoDevice";
setEndpoint(endpoint);
createDeviceProfile(transportConfiguration);
NoSecClientCredential credentials = createNoSecClientCredentials(endpoint);
final Device device = createDevice(credentials);
createNewClient(SECURITY, COAP_CONFIG, false);
client = new LwM2MTestClient(executor, ENDPOINT);
client.init(SECURITY, COAP_CONFIG);
Thread.sleep(1000);
Thread.sleep(1000);
device.setFirmwareId(createFirmware().getId());
final Device savedDevice = doPost("/api/device", device, Device.class);
device.setFirmwareId(createFirmware().getId());
device = doPost("/api/device", device, Device.class);
Thread.sleep(1000);
Thread.sleep(5000);
List<TsKvEntry> ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?keys=fw_state", new TypeReference<>() {
}));
List<OtaPackageUpdateStatus> statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList());
assertThat(savedDevice).as("saved device").isNotNull();
assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice);
List<OtaPackageUpdateStatus> expectedStatuses = Collections.singletonList(FAILED);
List<TsKvEntry> ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" +
savedDevice.getId().getId() + "/values/timeseries?keys=fw_state", new TypeReference<>() {}));
List<OtaPackageUpdateStatus> statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList());
List<OtaPackageUpdateStatus> expectedStatuses = Collections.singletonList(FAILED);
Assert.assertEquals(expectedStatuses, statuses);
} finally {
if (client != null) {
client.destroy();
}
}
Assert.assertEquals(expectedStatuses, statuses);
}
@Test
public void testFirmwareUpdateByObject5() throws Exception {
LwM2MTestClient client = null;
try {
createDeviceProfile(OTA_TRANSPORT_CONFIGURATION);
NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint("OTA_" + ENDPOINT);
Device device = createDevice(clientCredentials);
device.setFirmwareId(createFirmware().getId());
device = doPost("/api/device", device, Device.class);
Thread.sleep(1000);
client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT);
client.init(SECURITY, COAP_CONFIG);
String endpoint = "Ota5_Device";
setEndpoint(endpoint);
createDeviceProfile(OTA_TRANSPORT_CONFIGURATION);
NoSecClientCredential credentials = createNoSecClientCredentials(endpoint);
final Device device = createDevice(credentials);
createNewClient(SECURITY, COAP_CONFIG, false);
Thread.sleep(3000);
Thread.sleep(1000);
List<TsKvEntry> ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() {
}));
device.setFirmwareId(createFirmware().getId());
final Device savedDevice = doPost("/api/device", device, Device.class);
List<OtaPackageUpdateStatus> statuses = ts.stream().sorted(Comparator.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList());
Thread.sleep(1000);
List<OtaPackageUpdateStatus> expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED);
assertThat(savedDevice).as("saved device").isNotNull();
assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice);
Assert.assertEquals(expectedStatuses, statuses);
} finally {
if (client != null) {
client.destroy();
}
}
final List<OtaPackageUpdateStatus> expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED);
List<TsKvEntry> ts = await("await on timeseries")
.atMost(30, TimeUnit.SECONDS)
.until(() -> toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" +
savedDevice.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" +
System.currentTimeMillis(), new TypeReference<>() {
})), hasSize(expectedStatuses.size()));
List<OtaPackageUpdateStatus> statuses = ts.stream().sorted(Comparator
.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString)
.map(OtaPackageUpdateStatus::valueOf)
.collect(Collectors.toList());
Assert.assertEquals(expectedStatuses, statuses);
}
/**
@ -220,25 +204,25 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
* */
@Test
public void testSoftwareUpdateByObject9() throws Exception {
//given
final List<OtaPackageUpdateStatus> expectedStatuses = List.of(
QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED);
String endpoint = "Ota9_Device";
setEndpoint(endpoint);
createDeviceProfile(OTA_TRANSPORT_CONFIGURATION);
NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint("OTA_" + ENDPOINT);
final Device device = createDevice(clientCredentials);
device.setSoftwareId(createSoftware().getId());
NoSecClientCredential credentials = createNoSecClientCredentials(endpoint);
final Device device = createDevice(credentials);
createNewClient(SECURITY, COAP_CONFIG, false);
Thread.sleep(1000);
device.setSoftwareId(createSoftware().getId());
final Device savedDevice = doPost("/api/device", device, Device.class); //sync call
Thread.sleep(1000);
assertThat(savedDevice).as("saved device").isNotNull();
assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice);
//when
log.warn("Init the client...");
client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT);
client.init(SECURITY, COAP_CONFIG);
final List<OtaPackageUpdateStatus> expectedStatuses = List.of(
QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED);
log.warn("AWAIT atMost {} SECONDS on timeseries List<TsKvEntry> by API with list size {}...", TIMEOUT, expectedStatuses.size());
List<TsKvEntry> ts = await("await on timeseries")
.atMost(30, TimeUnit.SECONDS)
@ -247,9 +231,10 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
ts.sort(Comparator.comparingLong(TsKvEntry::getTs));
log.warn("Ts ordered: {}", ts);
ts.forEach((x) -> log.warn("ts: {} ", x));
List<OtaPackageUpdateStatus> statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList());
ts.forEach((x) -> log.warn("ts: { Thread.sleep(1000);} ", x));
List<OtaPackageUpdateStatus> statuses = ts.stream().map(KvEntry::getValueAsString)
.map(OtaPackageUpdateStatus::valueOf)
.collect(Collectors.toList());
log.warn("Converted ts to statuses: {}", statuses);
assertThat(statuses).isEqualTo(expectedStatuses);
@ -257,7 +242,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
private Device getDeviceFromAPI(UUID deviceId) throws Exception {
final Device device = doGet("/api/device/" + deviceId, Device.class);
log.warn("Fetched device by API for deviceId {}, device is {}", deviceId, device);
log.trace("Fetched device by API for deviceId {}, device is {}", deviceId, device);
return device;
}

206
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java

@ -0,0 +1,206 @@
/**
* Copyright © 2016-2021 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.rpc;
import org.junit.Before;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential;
import org.thingsboard.server.controller.TbTestWebSocketClient;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
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.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.COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_0_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_1_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources;
@DaoSqlTest
public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
protected String RPC_TRANSPORT_CONFIGURATION;
protected ScheduledExecutorService executor;
protected TbTestWebSocketClient wsClient;
protected String deviceId;
public Set expectedObjects;
public Set expectedObjectIdVers;
public Set expectedInstances;
public Set expectedObjectIdVerInstances;
protected String objectInstanceIdVer_1;
protected String objectIdVer_0;
protected String objectIdVer_2;
private static final Predicate predicate_3 = path -> (!((String) path).contains("/" + TEMPERATURE_SENSOR) && ((String) path).contains("/" + DEVICE));
protected String objectIdVer_3;
protected String objectInstanceIdVer_3;
protected String objectInstanceIdVer_5;
protected String objectInstanceIdVer_9;
protected String objectIdVer_19;
protected String objectIdVer_50 = "/50";
protected String objectIdVer_3303;
protected static AtomicInteger endpointSequence = new AtomicInteger();
public AbstractRpcLwM2MIntegrationTest(){
setResources(resources);
}
@Before
public void beforeTest() throws Exception {
setEndpoint("deviceEndpointRpc" + endpointSequence.incrementAndGet());
init();
createNewClient (SECURITY, COAP_CONFIG, true);
expectedObjects = ConcurrentHashMap.newKeySet();
expectedObjectIdVers = ConcurrentHashMap.newKeySet();
expectedInstances = ConcurrentHashMap.newKeySet();
expectedObjectIdVerInstances = ConcurrentHashMap.newKeySet();
client.getClient().getObjectTree().getObjectEnablers().forEach((key, val) -> {
if (key > 0) {
String objectVerId = "/" + key;
if (!val.getObjectModel().version.equals("1.0")) {
objectVerId += ("_" + val.getObjectModel().version);
}
expectedObjects.add("/" + key);
expectedObjectIdVers.add(objectVerId);
String finalObjectVerId = objectVerId;
val.getAvailableInstanceIds().forEach(inststanceId -> {
expectedInstances.add("/" + key + "/" + inststanceId);
expectedObjectIdVerInstances.add(finalObjectVerId + "/" + inststanceId);
});
}
});
String ver_Id_0 = client.getClient().getObjectTree().getModel().getObjectModel(objectId_0).version;
if ("1.0".equals(ver_Id_0)) {
objectIdVer_0 = "/" + objectId_0;
}
else {
objectIdVer_0 = "/" + objectId_0 + "_" + ver_Id_0;
}
objectIdVer_2 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + ACCESS_CONTROL)).findFirst().get();
objectIdVer_3 = (String) expectedObjects.stream().filter(predicate_3).findFirst().get();
objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get();
objectIdVer_3303 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + TEMPERATURE_SENSOR)).findFirst().get();
objectInstanceIdVer_1 = (String) expectedObjectIdVerInstances.stream().filter(path -> (!((String) path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).contains("/" + SERVER))).findFirst().get();
objectInstanceIdVer_3 = (String) expectedObjectIdVerInstances.stream().filter(predicate_3).findFirst().get();
objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + FIRMWARE)).findFirst().get();
objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + SOFTWARE_MANAGEMENT)).findFirst().get();
RPC_TRANSPORT_CONFIGURATION = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {\n" +
" \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\": \"" + resourceIdName_3_9 + "\",\n" +
" \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_14 + "\": \"" + resourceIdName_3_14 + "\",\n" +
" \"" + objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\": \"" + resourceIdName_19_0_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "\": \"" + resourceIdName_19_1_0 + "\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\",\n" +
" \"" + objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\"\n" +
" ],\n" +
" \"attribute\": [\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\",\n" +
" \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_14 + "\",\n" +
" \"" + objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrapServerUpdateEnable\": true,\n" +
" \"bootstrap\": [\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5687,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 111,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5685,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 123,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" ],\n" +
" \"clientLwM2mSettings\": {\n" +
" \"edrxCycle\": null,\n" +
" \"powerMode\": \"DRX\",\n" +
" \"fwUpdateResource\": null,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"psmActivityTimer\": null,\n" +
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" +
" \"pagingTransmissionWindow\": null,\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" +
"}";
createDeviceProfile(RPC_TRANSPORT_CONFIGURATION);
NoSecClientCredential credentials = createNoSecClientCredentials(endpoint);
final Device device = createDevice(credentials);
deviceId = device.getId().getId().toString();
client.start();
}
protected String pathIdVerToObjectId(String pathIdVer) {
if (pathIdVer.contains("_")){
String [] objVer = pathIdVer.split("/");
objVer[1] = objVer[1].split("_")[0];
return String.join("/", objVer);
}
return pathIdVer;
}
}

133
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java

@ -0,0 +1,133 @@
/**
* Copyright © 2016-2021 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.rpc.sql;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_12;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0;
public class RpcLwm2mIntegrationCreateTest extends AbstractRpcLwM2MIntegrationTest {
/**
* Create {"id":"/19_1.1","value":{"0":{"0":"00AC"}, "1":1}}
*
* create_2_instances_in_object
* new ObjectInstance if Object is Multiple & Resource Single
* Create {"id":"/19_1.1/12","value":{"0":{"0":"00AC", "1":1}}}
* {"{"result":"CREATED"}"}
*/
@Test
public void testCreateObjectInstanceWithInstanceIdByIdKey_Result_CREATED() throws Exception {
String expectedPath = objectIdVer_19 + "/" + objectInstanceId_12;
String expectedValue = "{\"" + resourceId_0 + "\":{\"0\":\"00AC\"}, \"1\":1}";
String actualResult = sendRPCreateById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CREATED.getName(), rpcActualResult.get("result").asText());
}
/**
* Create {"id":"/19_1.1","value":{"0":{"0":"00AC"}, "1":1}}
*
* create_2_instances_in_object
* new ObjectInstance if Object is Multiple & Resource Single
* Create {"id":"/19_1.1/0","value":{"0":{"0":"00AC", "1":1}}}
* {"result":"BAD_REQUEST","error":"instance 0 already exists"}
*/
@Test
public void testCreateObjectInstanceWithInstanceIdAlreadyExistsById_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectIdVer_19 + "/" + objectInstanceId_0;
String expectedValue = "{\"" + resourceId_0 + "\":{\"0\":\"00AC\"}, \"1\":1}";
String actualResult = sendRPCreateById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "instance " + objectInstanceId_0 + " already exists";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
/**
* failed: cannot_create_mandatory_single_object
* Create {"id":"/3/1,"value":{"0":"00AC"}}
* {"result":"BAD_REQUEST","error":"Path /3/1. Object must be Multiple !"}
*/
@Test
public void testCreateObjectInstanceWithInstanceIdMandatorySingleObjectById_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectIdVer_3 + "/" + objectInstanceId_1;
String expectedValue = "{\"" + resourceId_0 + "\":{\"0\":\"00AC\"}}";
String actualResult = sendRPCreateById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Path " + expectedPath + ". Object must be Multiple !";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
/**
* failed: cannot_create_instance_of_security_object
* Create {"id":"/0/2","value":{"2":4}}
* {"result":"BAD_REQUEST","error":"Specified object id 0 absent in the list supported objects of the client or is security object!"}
*/
@Test
public void testCreateObjectInstanceWithInstanceIdSecurityObjectById_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectIdVer_0 + "/" + objectInstanceId_1;
String expectedValue = "{\"" + resourceId_0 + "\":{\"2\":4}}";
String actualResult = sendRPCreateById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expectedObjectId = pathIdVerToObjectId((String) expectedPath);
LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId);
String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
/**
* failed: cannot_create_instance_of_absent_object
* Create {"id":"/50/1","value":{"0":"00AC"}}
* {"result":"BAD_REQUEST","error":"Specified object id 50 absent in the list supported objects of the client or is security object!"}
*/
@Test
public void testCreateObjectInstanceWithInstanceIdAbsentObjectById_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectIdVer_50+ "/" + objectInstanceId_1;
String expectedValue = "{\"" + resourceId_0 + "\":{\"0\":\"00AC\"}}";
String actualResult = sendRPCreateById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expectedObjectId = pathIdVerToObjectId((String) expectedPath);
LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId);
String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
private String sendRPCreateById(String path, String value) throws Exception {
String setRpcRequest = "{\"method\": \"Create\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
}

96
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDeleteTest.java

@ -0,0 +1,96 @@
/**
* Copyright © 2016-2021 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.rpc.sql;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.leshan.core.ResponseCode;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_12;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9;
public class RpcLwm2mIntegrationDeleteTest extends AbstractRpcLwM2MIntegrationTest {
/**
* if there is such an instance
* Delete {"id":"/3303/12"}
* {"result":"DELETE"}
*/
@Test
public void testDeleteObjectInstanceIsSuchByIdKey_Result_DELETED() throws Exception {
String expectedPath = objectIdVer_3303 + "/" + objectInstanceId_12;
String actualResult = sendRPCDeleteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.DELETED.getName(), rpcActualResult.get("result").asText());
}
/**
* if there is no such instance
* Delete {"id":"/19/12"}
* {"result":"NOT_FOUND"}
*/
@Test
public void testDeleteObjectInstanceIsNotSuchByIdKey_Result_NOT_FOUND() throws Exception {
String expectedPath = objectIdVer_19 + "/" + objectInstanceId_12;
String actualResult = sendRPCDeleteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText());
}
/**
* delete object
* Delete {"id":"/19_1.1"}
* {"result":"BAD_REQUEST","error":"Invalid path /19 : Only object instances can be delete"}
*/
@Test
public void testDeleteObjectByIdKey_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectIdVer_19;
String actualResult = sendRPCDeleteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Invalid path " + pathIdVerToObjectId((String) expectedPath) + " : Only object instances can be delete";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
/**
* delete resource
* Delete {"id":"/3/0/9"}
* {"result":"METHOD_NOT_ALLOWED"}
*/
@Test
public void testDeleteResourceByIdKey_Result_METHOD_NOT_ALLOWED() throws Exception {
String expectedPath = objectIdVer_3 + "/" + objectInstanceId_0 + resourceId_9;
String actualResult = sendRPCDeleteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText());
}
private String sendRPCDeleteById(String path) throws Exception {
String setRpcRequest = "{\"method\": \"Delete\", \"params\": {\"id\": \"" + path + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
}

165
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java

@ -0,0 +1,165 @@
/**
* Copyright © 2016-2021 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.rpc.sql;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2;
public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest {
/**
* DiscoverAll
*
* @throws Exception
*/
@Test
public void testDiscoverAll_Return_CONTENT_LinksAllObjectsAllInstancesOfClient() throws Exception {
String setRpcRequest = "{\"method\":\"DiscoverAll\"}";
String actualResult = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
JsonNode rpcActualValue = JacksonUtil.toJsonNode(rpcActualResult.get("value").asText());
Set actualObjects = ConcurrentHashMap.newKeySet();
Set actualInstances = ConcurrentHashMap.newKeySet();
rpcActualValue.forEach(node -> {
if (!node.get("url").asText().equals("/")) {
LwM2mPath path = new LwM2mPath(node.get("url").asText());
actualObjects.add("/" + path.getObjectId());
if (path.isObjectInstance()) {
actualInstances.add("/" + path.getObjectId() + "/" + path.getObjectInstanceId());
}
}
});
assertEquals(expectedInstances, actualInstances);
assertEquals(expectedObjects, actualObjects);
}
/**
* Discover {"id":"/3"}
*
* @throws Exception
*/
@Test
public void testDiscoverObject_Return_CONTENT_LinksInstancesAndResourcesOnLyExpectedObject() {
expectedObjectIdVers.forEach(expected -> {
try {
String actualResult = sendDiscover((String) expected);
String expectedObjectId = pathIdVerToObjectId((String) expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String[] actualValues = rpcActualResult.get("value").asText().split(",");
assertTrue(actualValues.length > 0);
assertEquals(0, Arrays.stream(actualValues).filter(path -> !path.contains(expectedObjectId)).collect(Collectors.toList()).size());
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* Discover {"id":"3/0"}
* If WriteAttributes not implemented:
* {"result":"CONTENT","value":"</3/0>,</3/0/0>,</3/0/1>,</3/0/2>,</3/0/3>,</3/0/4>,</3/0/5>,</3/0/6>,</3/0/7>,</3/0/8>,</3/0/9>,</3/0/10>,</3/0/11>,</3/0/12>,</3/0/13>,</3/0/14>,</3/0/15>,</3/0/16>,</3/0/1
* 7>,</3/0/18>,</3/0/19>,</3/0/20>,</3/0/21>,</3/0/22>"}
* If WriteAttributes implemented and WriteAttributes saved
* Discover {"id":"19/0"}
* {"result":"CONTENT","value":"[</19/0>;dim=2;pmin=10;pmax=60;gt=50;lt=42.2,</19/0/0>;pmax=120, </19/0/1>, </19/0/2>, </19/0/3>, </19/0/4>, </19/0/5>;lt=45]"}
*/
@Test
public void testDiscoverInstance_Return_CONTENT_LinksResourcesOnLyExpectedInstance() throws Exception {
String expected = (String) expectedObjectIdVerInstances.stream().findAny().get();
String actualResult = sendDiscover(expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expectedObjectInstanceId = pathIdVerToObjectId(expected);
String[] actualValues = rpcActualResult.get("value").asText().split(",");
assertTrue(actualValues.length > 0);
assertEquals(0, Arrays.stream(actualValues).filter(path -> !path.contains(expectedObjectInstanceId)).collect(Collectors.toList()).size());
}
/**
* Discover {"id":"3/0/14"}
* If WriteAttributes implemented:
* {"result":"CONTENT","value":"</3/0/14>;pmax=100, "pmin":10, "ver"=1.0"}
* If WriteAttributes not implemented:
* {"result":"CONTENT","value":"</3/0/14>"}
* Discover {"id":"19_1.1/0/0"}
* If WriteAttributes implemented:
* {"result":"CONTENT","value":"</19/0/0>;pmax=100, "pmin":10, "ver"=1.1"}
* If WriteAttributes not implemented:
* {"result":"CONTENT","value":"</19/0/0>"}
*/
@Test
public void testDiscoverResource_Return_CONTENT_LinksResourceOnLyExpectedResource() throws Exception {
String expectedInstance = (String) expectedInstances.stream().findFirst().get();
String expectedObjectInstanceId = pathIdVerToObjectId(expectedInstance);
LwM2mPath expectedPath = new LwM2mPath(expectedObjectInstanceId);
int expectedResource = client.getClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
String expected = expectedInstance + "/" + expectedResource;
String actualResult = sendDiscover(expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expectedResourceId = "<" + expectedObjectInstanceId + "/" + expectedResource + ">";
String actualValue = rpcActualResult.get("value").asText();
assertEquals(expectedResourceId, actualValue );
}
/**
* Discover {"id":"2/0"}
*{"result":"NOT_FOUND"}
*/
@Test
public void testDiscoverObjectInstanceAbsentInObject_Return_NOT_FOUND() throws Exception {
String expected = objectIdVer_2 + "/" + objectInstanceId_0;
String actualResult = sendDiscover(expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText());
}
/**
* Discover {"id":"2/0/2"}
* {"result":"NOT_FOUND"}
*/
@Test
public void testDiscoverResourceAbsentInObject_Return_NOT_FOUND() throws Exception {
String expected = objectIdVer_2 + "/" + objectInstanceId_0 + "/" + resourceId_2;
String actualResult = sendDiscover(expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText());
}
private String sendDiscover(String path) throws Exception {
String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
}

186
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java

@ -0,0 +1,186 @@
/**
* Copyright © 2016-2021 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.rpc.sql;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_4;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_8;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9;
public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationTest {
/**
* Update FW
* Execute {"id":"5/0/2"}
* {"result":"CHANGED"}
*/
@Test
public void testExecuteUpdateFWById_Result_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_5 + "/" + resourceId_2;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
}
/**
* Update SW
* Execute {"id":"9/0/4"}
* {"result":"CHANGED"}
*/
@Test
public void testExecuteUpdateSWById_Result_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_9 + "/" + resourceId_4;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
}
/**
* Reboot
* Execute {"id":"3/0/4"}
* {"result":"CHANGED"}
*/
@Test
public void testExecuteRebootById_Result_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + resourceId_4;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
}
/**
* Registration Update Trigger
* Execute {"id":"1/0/8"}
* {"result":"CHANGED"}
*/
@Test
public void testExecuteRegistrationUpdateTriggerById_Result_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_1 + "/" + resourceId_8;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
}
/**
* execute_resource_with_parameters (execute reboot after 60 seconds on device)
* Execute {"id":"3/0/4","value":60}
* {"result":"CHANGED"}
*/
@Test
public void testExecuteResourceWithParametersById_Result_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + resourceId_4;
Object expectedValue = 60;
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
}
/**
* Bootstrap-Request Trigger
* Execute {"id":"1/0/9"}
* {"result":"BAD_REQUEST","error":"probably no bootstrap server configured"}
*/
@Test
public void testExecuteBootstrapRequestTriggerById_Result_BAD_REQUEST_Error_NoBootstrapServerConfigured() throws Exception {
String expectedPath = objectInstanceIdVer_1 + "/" + resourceId_9;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "probably no bootstrap server configured";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
/**
* bad: resource operation not "E"
* Execute {"id":"5/0/3"}
* {"result":"BAD_REQUEST","error":"Resource with /5/0/3 is not executable."}
*/
@Test
public void testExecuteResourceWithOperationNotExecuteById_Result_METHOD_NOT_ALLOWED() throws Exception {
String expectedPath = objectInstanceIdVer_5 + "/" + resourceId_3;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expectedObjectId = pathIdVerToObjectId((String) expectedPath);
String expected = "Resource with " + expectedObjectId + " is not executable.";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
/**
* bad: execute_non_existing_resource_on_non_existing_object
* Execute {"id":"50/0/3"}
* {"result":"BAD_REQUEST","error":"Specified object id 50 absent in the list supported objects of the client or is security object!"}
*/
@Test
public void testExecuteNonExistingResourceOnNonExistingObjectById_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectIdVer_50 + "/" + objectInstanceId_0 + "/" + resourceId_3;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expectedObjectId = pathIdVerToObjectId((String) expectedPath);
LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId);
String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
/**
* bad: execute security object
* Execute {"id":"0/0/3"}
* {"result":"BAD_REQUEST","error":"Specified object id 0 absent in the list supported objects of the client or is security object!"}
*/
@Test
public void testExecuteSecurityObjectById_Result_NOT_FOUND() throws Exception {
String expectedPath = objectIdVer_0 + "/" + objectInstanceId_0 + "/" + resourceId_3;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expectedObjectId = pathIdVerToObjectId((String) expectedPath);
LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId);
String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
private String sendRPCExecuteById(String path) throws Exception {
String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendRPCExecuteWithValueById(String path, Object value) throws Exception {
String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
}

190
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java

@ -0,0 +1,190 @@
/**
* Copyright © 2016-2021 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.rpc.sql;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.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;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9;
public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest {
/**
* ObserveReadAll
* @throws Exception
*/
@Test
public void testObserveReadAllNothingObservation_Result_CONTENT_Value_Count_0() throws Exception {
String actualResult = sendObserve("ObserveCancelAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
actualResult = sendObserve("ObserveReadAll", null);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("[]", rpcActualResult.get("value").asText());
}
/**
* Observe {"id":"/3/0/9"}
* @throws Exception
*/
@Test
public void testObserveSingleResource_Result_CONTENT_Value_SingleResource() throws Exception {
String expectedIdVer = objectInstanceIdVer_3 + "/" + resourceId_9;
String actualResult = sendObserve("Observe", expectedIdVer);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
}
/**
* Observe {"id":"/3_1.1/0/13"}
* @throws Exception
*/
@Test
public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe1_0() throws Exception {
String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String)path).contains("_")).findFirst().get();
LwM2mPath expectedPath = new LwM2mPath(expectedInstance);
int expectedResource = client.getClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
String expectedId = "/" + expectedPath.getObjectId() + "_1.2" + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource;
String actualResult = sendObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Specified resource id " + expectedId +" is not valid version! Must be version: 1.0";
assertEquals(expected, rpcActualResult.get("error").asText());
}
/**
* Not implemented Instance
* Observe {"id":"/2/0"}
* @throws Exception
*/
@Test
public void testObserveNoImplementedInstanceOnDevice_Result_NotFound() throws Exception {
String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + ACCESS_CONTROL)).findFirst().get();
String expected = objectInstanceIdVer + "/" + objectInstanceId_0;
String actualResult = sendObserve("Observe", expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText());
}
/**
* Not implemented Resource
* Observe {"id":"/19_1.1/0/0"}
* @throws Exception
*/
@Test
public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception {
String objectIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get();
String expected = objectIdVer + "/" + objectInstanceId_0 + "/" + resourceId_0;
String actualResult = sendObserve("Observe", expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String expectedValue = "values MUST NOT be null";
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
assertEquals(expectedValue, rpcActualResult.get("error").asText());
}
/**
* Repeated request on Observe
* Observe {"id":"/5/0/0"}
* @throws Exception
*/
@Test
public void testObserveRSourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception {
String expectedId = objectInstanceIdVer_5 + "/" + resourceId_0;
sendObserve("Observe", expectedId);
String actualResult = sendObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText());
}
/**
* Repeated request on Observe
* Observe {"id":"/3/0/0"}
* @throws Exception
*/
@Test
public void testObserveRepeatedRequestObserveOnDevice_Result_BAD_REQUEST_ErrorMsg_AlreadyRegistered() throws Exception {
String expectedId = objectInstanceIdVer_3 + "/" + resourceId_0;
sendObserve("Observe", expectedId);
String actualResult = sendObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Observation is already registered!";
assertEquals(expected, rpcActualResult.get("error").asText());
}
/**
* ObserveReadAll
* @throws Exception
*/
@Test
public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveAll() throws Exception {
sendObserve("ObserveCancelAll", null);
String expectedId_0 = objectInstanceIdVer_3 + "/" + resourceId_0;
String expectedId_9 = objectInstanceIdVer_3 + "/" + resourceId_9;
sendObserve("Observe", expectedId_0);
sendObserve("Observe", expectedId_9);
String actualResult = sendObserve("ObserveReadAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(expectedId_0));
assertTrue(actualValues.contains(expectedId_9));
assertEquals(2, actualValues.split(",").length);
}
/**
* ObserveCancel {"id":"/3/0/3"}
* ObserveCancel {"id":"/5/0/3"}
*/
@Test
public void testObserveCancelOneResource_Result_CONTENT_Value_Count_1() throws Exception {
sendObserve("ObserveCancelAll", null);
String expectedId_0 = objectInstanceIdVer_3 + "/" + resourceId_0;
String expectedId_3 = objectInstanceIdVer_5 + "/" + resourceId_3;
sendObserve("Observe", expectedId_0);
sendObserve("Observe", expectedId_3);
String actualResult = sendObserve("ObserveCancel", expectedId_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("1", rpcActualResult.get("value").asText());
}
private String sendObserve(String method, String params) throws Exception {
String sendRpcRequest;
if (params == null) {
sendRpcRequest = "{\"method\": \"" + method + "\"}";
}
else {
sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}";
}
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk());
}
}

226
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java

@ -0,0 +1,226 @@
/**
* Copyright © 2016-2021 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.rpc.sql;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_0_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_1_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_11;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9;
public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest {
/**
* Read {"id":"/3"}
* Read {"id":"/6"}...
*/
@Test
public void testReadAllObjectsInClientById_Result_CONTENT_Value_IsLwM2mObject_IsInstances() throws Exception {
expectedObjectIdVers.forEach(expected -> {
try {
String actualResult = sendRPCById((String) expected);
String expectedObjectId = pathIdVerToObjectId((String) expected);
LwM2mPath expectedPath = new LwM2mPath(expectedObjectId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expectedObjectInstances = "LwM2mObject [id=" + expectedPath.getObjectId() + ", instances={0=LwM2mObjectInstance [id=0, resources=";
if (expectedPath.getObjectId() == 2) {
expectedObjectInstances = "LwM2mObject [id=2, instances={}]";
}
assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances));
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* Read {"id":"/5/0"}
*
* @throws Exception
*/
@Test
public void testReadAllInstancesInClientById_Result_CONTENT_Value_IsInstances_IsResources() throws Exception{
expectedObjectIdVerInstances.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 = "LwM2mObjectInstance [id=" + expectedPath.getObjectInstanceId() + ", resources={";
assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances));
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* Read {"id":"/19/1/0"}
*
* @throws Exception
*/
@Test
public void testReadMultipleResourceById_Result_CONTENT_Value_IsLwM2mMultipleResource() throws Exception {
String expectedIdVer = objectInstanceIdVer_3 +"/" + resourceId_11 ;
String actualResult = sendRPCById(expectedIdVer);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected = "LwM2mMultipleResource [id=" + resourceId_11 + ", values={";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
}
/**
* Read {"id":"/3/0/14"}
*/
@Test
public void testReadSingleResourceById_Result_CONTENT_Value_IsLwM2mSingleResource() throws Exception {
String expectedIdVer = objectInstanceIdVer_3 +"/" + resourceId_14 ;
String actualResult = sendRPCById(expectedIdVer);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
}
/**
* Read {"key":"UtfOffset"}
*/
@Test
public void testReadSingleResourceByKey_Result_CONTENT_Value_IsLwM2mSingleResource() throws Exception {
String expectedKey = resourceIdName_3_14 ;
String actualResult = sendRPCByKey(expectedKey);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=";
assertTrue(rpcActualResult.get("value").asText().contains(expected));
}
/**
* ReadComposite {"ids":["/1_1.2", "/3_1.0/0/1", "/3_1.0/0/11"]}
*/
@Test
public void testReadCompositeSingleResourceByIds_Result_CONTENT_Value_IsObjectIsLwM2mSingleResourceIsLwM2mMultipleResource() throws Exception {
String expectedIdVer_1 = (String) expectedObjectIdVers.stream().filter(path -> (!((String)path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String)path).contains("/" + SERVER))).findFirst().get();
String objectId_1 = pathIdVerToObjectId(expectedIdVer_1);
String expectedIdVer3_0_1 = objectInstanceIdVer_3 + "/" + resourceId_1;
String expectedIdVer3_0_11 = objectInstanceIdVer_3 + "/" + resourceId_11;
String objectInstanceId_3 = pathIdVerToObjectId(objectInstanceIdVer_3);
String expectedIds = "[\"" + expectedIdVer_1 + "\", \"" + expectedIdVer3_0_1 + "\", \"" + expectedIdVer3_0_11 + "\"]";
String actualResult = sendCompositeRPCByIds(expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected1 = objectId_1 + "=LwM2mObject [id=" + new LwM2mPath(objectId_1).getObjectId() + ", instances={";
String expected3_0_1 = objectInstanceId_3 + "/" + resourceId_1 + "=LwM2mSingleResource [id=" + resourceId_1 + ", value=";
String expected3_0_11 = objectInstanceId_3 + "/" + resourceId_11 + "=LwM2mMultipleResource [id=" + resourceId_11 + ", values={";
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(expected1));
assertTrue(actualValues.contains(expected3_0_1));
assertTrue(actualValues.contains(expected3_0_11));
}
/**
* ReadComposite {"ids":["/1_1.2/0/1", "/1_1.2/0/2", "/3_1.0/0"]}
*/
@Test
public void testReadCompositeSingleResourceByIds_Result_CONTENT_Value_IsObjectInstanceIsLwM2mSingleResource() throws Exception {
String expectedIdVer3_0 = objectInstanceIdVer_3;
String expectedIdVer1_0_1 = objectInstanceIdVer_1 + "/" + resourceId_1;
String expectedIdVer1_0_2 = objectInstanceIdVer_1 + "/" + resourceId_2;
String expectedIds = "[\"" + expectedIdVer1_0_1 + "\", \"" + expectedIdVer1_0_2 + "\", \"" + expectedIdVer3_0 + "\"]";
String actualResult = sendCompositeRPCByIds(expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String objectInstanceId_3 = pathIdVerToObjectId(objectInstanceIdVer_3);
LwM2mPath path = new LwM2mPath(objectInstanceId_3);
String expected3_0 = objectInstanceId_3 + "=LwM2mObjectInstance [id=" + path.getObjectInstanceId() + ", resources={";
String objectInstanceId_1 = pathIdVerToObjectId(objectInstanceIdVer_1);
String expected1_0_1 = objectInstanceId_1 + "/" + resourceId_1 + "=LwM2mSingleResource [id=" + resourceId_1 + ", value=";
String expected1_0_2 = objectInstanceId_1 + "/" + resourceId_2 + "=null";
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(expected3_0));
assertTrue(actualValues.contains(expected1_0_1));
assertTrue(actualValues.contains(expected1_0_2));
}
/**
* ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataRead", "dataWrite"]}
*/
@Test
public void testReadCompositeSingleResourceByKeys_Result_CONTENT_Value_3_0_IsLwM2mSingleResource_19_0_0_AND_19_0_1_Null() throws Exception {
String expectedKey3_0_9 = resourceIdName_3_9;
String expectedKey3_0_14 = resourceIdName_3_14;
String expectedKey19_0_0 = resourceIdName_19_0_0;
String expectedKey19_1_0 = resourceIdName_19_1_0;
String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]";
String actualResult = sendCompositeRPCByKeys(expectedKeys);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String objectInstanceId_3 = pathIdVerToObjectId(objectInstanceIdVer_3);
String objectId_19 = pathIdVerToObjectId(objectIdVer_19);
String expected3_0_9 = objectInstanceId_3 + "/" + resourceId_9 + "=LwM2mSingleResource [id=" + resourceId_9 + ", value=";
String expected3_0_14 = objectInstanceId_3 + "/" + resourceId_14 + "=LwM2mSingleResource [id=" + resourceId_14 + ", value=";
String expected19_0_0 = objectId_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "=null";
String expected19_1_0 = objectId_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "=null";
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(expected3_0_9));
assertTrue(actualValues.contains(expected3_0_14));
assertTrue(actualValues.contains(expected19_0_0));
assertTrue(actualValues.contains(expected19_1_0));
}
private String sendRPCById(String path) throws Exception {
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendRPCByKey(String key) throws Exception {
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"key\": \"" + key + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendCompositeRPCByIds(String paths) throws Exception {
String setRpcRequest = "{\"method\": \"ReadComposite\", \"params\": {\"ids\":" + paths + "}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendCompositeRPCByKeys(String keys) throws Exception {
String setRpcRequest = "{\"method\": \"ReadComposite\", \"params\": {\"keys\":" + keys + "}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
}

63
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java

@ -0,0 +1,63 @@
/**
* Copyright © 2016-2021 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.rpc.sql;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_4;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_8;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9;
public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest {
/**
* WriteAttributes {"id":"/3/0/14","attributes":{"pmax":100, "pmin":10}}
* if not implemented:
* {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"}
* if implemented:
* {"result":"CHANGED"}
*/
@Test
public void testWriteAttributesResourceWithParametersById_Result_INTERNAL_SERVER_ERROR() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + resourceId_14;
String expectedValue = "{\"pmax\":100, \"pmin\":10}";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText());
String expected = "not implemented";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
private String sendRPCExecuteWithValueById(String path, String value) throws Exception {
String setRpcRequest = "{\"method\": \"WriteAttributes\", \"params\": {\"id\": \"" + path + "\", \"attributes\": " + value + " }}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
}

335
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java

@ -0,0 +1,335 @@
/**
* Copyright © 2016-2021 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.rpc.sql;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_15;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceInstanceId_2;
public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTest {
/**
* update SingleResource:
* WriteReplace {"id":"3/0/14","value":"+12"}
* {"result":"CHANGED"}
*/
@Test
public void testWriteReplaceValueSingleResourceById_Result_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + resourceId_14;
String expectedValue = "+12";
String actualResult = sendRPCWriteStringById("WriteReplace", expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
actualResult = sendRPCReadById(expectedPath);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String actualValues = rpcActualResult.get("value").asText();
String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=" + expectedValue + ", type=STRING]";
assertTrue(actualValues.contains(expected));
}
/**
* key
* WriteReplace {"key":"timezone","value":"+10"}
* {"result":"CHANGED"}
*/
@Test
public void testWriteReplaceValueSingleResourceByKey_Result_CHANGED() throws Exception {
String expectedKey = resourceIdName_3_14;
String expectedValue = "+09";
String actualResult = sendRPCWriteByKey("WriteReplace", expectedKey, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
actualResult = sendRPCReadByKey(expectedKey);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String actualValues = rpcActualResult.get("value").asText();
String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=" + expectedValue + ", type=STRING]";
assertTrue(actualValues.contains(expected));
}
/**
* id
* WriteReplace {"id": "/19_1.1/0/0","value": {"0":"0000ad45675600", "15":"1525ad45675600cdef"}}
* {"result":"CHANGED"}
*/
@Test
public void testWriteReplaceValueMultipleResource_Result_CHANGED_Value_Multi_Instance_Resource_must_in_Json_format() throws Exception {
String expectedPath = objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0;
int resourceInstanceId0 = 0;
int resourceInstanceId15 = 15;
String expectedValue0 = "0000ad45675600";
String expectedValue15 = "1525ad45675600cdef";
String expectedValue = "{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId15 + "\":\"" + expectedValue15 + "\"}";
String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
String expectedPath0 = expectedPath + "/" + resourceInstanceId0;
String expectedPath15 = expectedPath + "/" + resourceInstanceId15;
actualResult = sendRPCReadById(expectedPath0);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String actualValues = rpcActualResult.get("value").asText();
String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]";
assertTrue(actualValues.contains(expected));
actualResult = sendRPCReadById(expectedPath15);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
actualValues = rpcActualResult.get("value").asText();
expected = "LwM2mResourceInstance [id=" + resourceInstanceId15 + ", value=" + expectedValue15.length()/2 + "Bytes, type=OPAQUE]";
assertTrue(actualValues.contains(expected));
}
/**
* bad: singleResource, operation="R" - only read
* WriteReplace {"id":"/3/0/9","value":90}
* {"result":"METHOD_NOT_ALLOWED"}
*/
@Test
public void testWriteReplaceValueSingleResourceR_ById_Result_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + resourceId_9;
Integer expectedValue = 90;
String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText());
}
/**
* ids
* WriteUpdate {"id":"/3/0","value":{"14":"+5","15":"Kiyv/Europe"}}
* {"result":"CHANGED"}
*/
@Test
public void testWriteUpdateValueSingleResourceById_Result_CHANGED() throws Exception {
String expectedPath = objectInstanceIdVer_3;
String expectedValue14 = "+5";
String expectedValue15 = "Kiyv/Europe";
String expectedValue = "{\"" + resourceId_14 + "\":\"" + expectedValue14 + "\",\"" + resourceId_15 + "\":\"" + expectedValue15 + "\"}";
String actualResult = sendRPCWriteObjectById("WriteUpdate", expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
String expectedPath14 = objectInstanceIdVer_3 + "/" + resourceId_14;
String expectedPath15 = objectInstanceIdVer_3 + "/" + resourceId_15;
actualResult = sendRPCReadById(expectedPath14);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String actualValues = rpcActualResult.get("value").asText();
String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=" + expectedValue14 + ", type=STRING]";
assertTrue(actualValues.contains(expected));
actualResult = sendRPCReadById(expectedPath15);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
actualValues = rpcActualResult.get("value").asText();
expected = "LwM2mSingleResource [id=" + resourceId_15 + ", value=" + expectedValue15 + ", type=STRING]";
assertTrue(actualValues.contains(expected));
}
/**
* id
* WriteUpdate {"id": "/19_1.1/0","value": {"0":{"0":"00ad456756", "25":"25ad456756"}}}
* {"result":"CHANGED"}
*/
@Test
public void testWriteUpdateValueMultipleResourceById_Result_CHANGED() throws Exception {
String expectedPath = objectIdVer_19 + "/" + objectInstanceId_0;
int resourceInstanceId0 = 0;
int resourceInstanceId25 = 25;
String expectedValue0 = "00ad45675600";
String expectedValue25 = "25ad45675600cdef";
String expectedValue = "{\"" + resourceId_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}";
String actualResult = sendRPCWriteObjectById("WriteUpdate", expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
String expectedPath0 = expectedPath + "/" + resourceId_0 + "/" + resourceInstanceId0;
String expectedPath25 =expectedPath + "/" + resourceId_0 + "/" + resourceInstanceId25;
actualResult = sendRPCReadById(expectedPath0);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String actualValues = rpcActualResult.get("value").asText();
String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]";
assertTrue(actualValues.contains(expected));
actualResult = sendRPCReadById(expectedPath25);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
actualValues = rpcActualResult.get("value").asText();
expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]";
assertTrue(actualValues.contains(expected));
}
/**
* ResourceInstance + KeySingleResource + IdSingleResource
* WriteComposite {"nodes":{"/19/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}}
* {"result":"CHANGED"}
*/
@Test
public void testWriteCompositeValueSingleResourceResourceInstanceByIdKey_Result_CHANGED() throws Exception {
int resourceInstanceId2 = 2;
String expectedPath19_1_0_2 = objectIdVer_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "/" + resourceInstanceId2;
String expectedValue19_1_0_2 = "00001234";
String expectedKey3_0_14 = resourceIdName_3_14;
String expectedValue3_0_14 = "+04";
String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + resourceId_15;
String expectedValue3_0_15 = "Kiyv/Europe";
String nodes = "{\"" + expectedPath19_1_0_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 +
"\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}";
String actualResult = sendCompositeRPC(nodes);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
actualResult = sendRPCReadById(expectedPath19_1_0_2);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String actualValues = rpcActualResult.get("value").asText();
String expected = "LwM2mResourceInstance [id=" + resourceInstanceId2 + ", value=" + expectedValue19_1_0_2.length()/2 + "Bytes, type=OPAQUE]";
assertTrue(actualValues.contains(expected));
actualResult = sendRPCReadByKey(expectedKey3_0_14);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
actualValues = rpcActualResult.get("value").asText();
expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=" + expectedValue3_0_14 + ", type=STRING]";
assertTrue(actualValues.contains(expected));
actualResult = sendRPCReadById(expectedPath3_0_15);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
actualValues = rpcActualResult.get("value").asText();
expected = "LwM2mSingleResource [id=" + resourceId_15 + ", value=" + expectedValue3_0_15 + ", type=STRING]";
assertTrue(actualValues.contains(expected));
}
/**
* multipleResource == error
* bad - cannot be used for value Json, only primitive: SingleResource, ResourceInstance (for Json: WriteUpdate, WriteReplace)
* WriteComposite {"nodes":{"/19/0/0":{"0":"abcd5678", "10":"abcd5678"}}}
*/
@Test
public void testWriteCompositeValueSingleMultipleResourceByIdKey_Result_BAD_REQUEST_WriteComposite_operation_for_SingleResources_or_and_ResourceInstance() throws Exception {
String nodes = "{\"/19/0/0\":{\"0\":\"abcd5678\", \"10\":\"abcd5678\"}}";
String actualResult = sendCompositeRPC(nodes);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("error").asText();
String expectedNodes = nodes.replaceAll("\"", "").replaceAll(":", "=");
String expected = String.format("nodes: %s is not validate value. " +
"The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", expectedNodes);
assertEquals(expected, actualValues);
}
/**
* update_resourceInstances&update_singleResource
* new ResourceInstance if Resource is Multiple & Resource Single
* - WriteReplace {"id":"/19_1.2/1/0","value":{"2":ddff12"}}
* - WriteReplace {"key":"UtfOffset","value":"+04"}
* - WriteReplace {"id":"/3/0/15","value":"Kiyv/Europe"}
* WriteComposite {"nodes":{"/19_1.1/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}}}
* {"result":"CHANGED"}
*/
@Test
public void testWriteCompositeCreateResourceInstanceUpdateSingleResourceByIdKey_Result_CHANGED() throws Exception {
String expectedPath19_1_0_2 = objectIdVer_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "/" + resourceInstanceId_2;
String expectedValue19_1_0_2 = "00001234";
String expectedKey3_0_14 = resourceIdName_3_14;
String expectedValue3_0_14 = "+04";
String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + resourceId_15;
String expectedValue3_0_15 = "Kiyv/Europe";
String nodes = "{\"" + expectedPath19_1_0_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 +
"\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}";
String actualResult = sendCompositeRPC(nodes);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
actualResult = sendRPCReadById(expectedPath19_1_0_2);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String actualValues = rpcActualResult.get("value").asText();
String expected = "LwM2mResourceInstance [id=" + resourceInstanceId_2 + ", value=" + expectedValue19_1_0_2.length()/2 + "Bytes, type=OPAQUE]";
assertTrue(actualValues.contains(expected));
actualResult = sendRPCReadByKey(expectedKey3_0_14);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
actualValues = rpcActualResult.get("value").asText();
expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=" + expectedValue3_0_14 + ", type=STRING]";
assertTrue(actualValues.contains(expected));
actualResult = sendRPCReadById(expectedPath3_0_15);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
actualValues = rpcActualResult.get("value").asText();
expected = "LwM2mSingleResource [id=" + resourceId_15 + ", value=" + expectedValue3_0_15 + ", type=STRING]";
assertTrue(actualValues.contains(expected));
}
/**
* composite_not created_new_instance...
* new ObjectInstance if Object is Multiple - bad
* - WriteReplace {"id":"/19_1.2/2/0","value":{"2":ddff12"}}
* - WriteReplace {"key":"UtfOffset","value":"+04"}
* - WriteReplace {"id":"/3/0/15","value":"Kiyv/Europe"}
* WriteComposite {"nodes":{"/19_1.1/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}}}
* {"result":"BAD_REQUEST","error":"object instance /19/2 not found"}
*/
@Test
public void testWriteCompositeCreateObjectInstanceUpdateSingleResourceByIdKey_Result_BAD_REQUEST() throws Exception {
String expectedPath19_1_2_2 = objectIdVer_19 + "/" + objectInstanceId_2 + "/" + resourceId_0 + "/" + resourceInstanceId_2;
String expectedValue19_1_0_2 = "00001234";
String expectedKey3_0_14 = resourceIdName_3_14;
String expectedValue3_0_14 = "+04";
String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + resourceId_15;
String expectedValue3_0_15 = "Kiyv/Europe";
String nodes = "{\"" + expectedPath19_1_2_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 +
"\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}";
String actualResult = sendCompositeRPC(nodes);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expectedObjectId = pathIdVerToObjectId((String) expectedPath19_1_2_2);
LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId);
String expected = "object instance " + "/" + expectedPathId.getObjectId() + "/" + expectedPathId.getObjectInstanceId() + " not found";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
private String sendRPCWriteStringById(String method, String path, String value) throws Exception {
String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + path + "\", \"value\": \"" + value + "\" }}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendRPCWriteObjectById(String method, String path, Object value) throws Exception {
String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendRPCReadById(String id) throws Exception {
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + id + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendRPCWriteByKey(String method, String key, String value) throws Exception {
String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"key\": \"" + key + "\", \"value\": \"" + value + "\" }}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendRPCReadByKey(String key) throws Exception {
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"key\": \"" + key + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
private String sendCompositeRPC(String nodes) throws Exception {
String setRpcRequest = "{\"method\": \"WriteComposite\", \"params\": {\"nodes\":" + nodes + "}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
}

176
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java

@ -0,0 +1,176 @@
/**
* Copyright © 2016-2021 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;
import org.eclipse.leshan.core.util.Hex;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.KeySpec;
@DaoSqlTest
public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
protected final String pskIdentity; // client public key or id used for PSK
protected final String pskKey; // client private/secret key used for PSK
protected final PublicKey clientPublicKey; // client public key used for RPK
protected final PrivateKey clientPrivateKey; // client private key used for RPK
protected final PublicKey serverPublicKey; // server public key used for RPK
protected final PrivateKey serverPrivateKey; // server private key used for RPK
// client private key used for X509
protected final PrivateKey clientPrivateKeyFromCert;
// server private key used for X509
protected final PrivateKey serverPrivateKeyFromCert;
// client certificate signed by rootCA with a good CN (CN start by leshan_integration_test)
protected final X509Certificate clientX509Cert;
// client certificate signed by rootCA but with bad CN (CN does not start by leshan_integration_test)
protected final X509Certificate clientX509CertWithBadCN;
// client certificate self-signed with a good CN (CN start by leshan_integration_test)
protected final X509Certificate clientX509CertSelfSigned;
// client certificate signed by another CA (not rootCA) with a good CN (CN start by leshan_integration_test)
protected final X509Certificate clientX509CertNotTrusted;
// server certificate signed by rootCA
protected final X509Certificate serverX509Cert;
// self-signed server certificate
protected final X509Certificate serverX509CertSelfSigned;
// rootCA used by the server
protected final X509Certificate rootCAX509Cert;
// certificates trustedby the server (should contain rootCA)
protected final Certificate[] trustedCertificates = new Certificate[1];
protected static final String ENDPOINT = "deviceAEndpoint";
protected LwM2MTestClient client;
private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials;
private final String[] resources = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"};
public AbstractSecurityLwM2MIntegrationTest() {
// create client credentials
setResources(this.resources);
setEndpoint(ENDPOINT);
try {
// Get keys PSK
this.pskIdentity = "SOME_PSK_ID";
this.pskKey = "73656372657450534b73656372657450";
// Get point values
byte[] publicX = Hex
.decodeHex("89c048261979208666f2bfb188be1968fc9021c416ce12828c06f4e314c167b5".toCharArray());
byte[] publicY = Hex
.decodeHex("cbf1eb7587f08e01688d9ada4be859137ca49f79394bad9179326b3090967b68".toCharArray());
byte[] privateS = Hex
.decodeHex("e67b68d2aaeb6550f19d98cade3ad62b39532e02e6b422e1f7ea189dabaea5d2".toCharArray());
// Get Elliptic Curve Parameter spec for secp256r1
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
// Create key specs
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
// Get keys RPK
clientPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
clientPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
// Get certificates from key store
char[] clientKeyStorePwd = "client".toCharArray();
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream clientKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/clientKeyStore.jks")) {
clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd);
}
clientPrivateKeyFromCert = (PrivateKey) clientKeyStore.getKey("client", clientKeyStorePwd);
clientX509Cert = (X509Certificate) clientKeyStore.getCertificate("client");
clientX509CertWithBadCN = (X509Certificate) clientKeyStore.getCertificate("client_bad_cn");
clientX509CertSelfSigned = (X509Certificate) clientKeyStore.getCertificate("client_self_signed");
clientX509CertNotTrusted = (X509Certificate) clientKeyStore.getCertificate("client_not_trusted");
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
// create server credentials
try {
// Get point values
byte[] publicX = Hex
.decodeHex("fcc28728c123b155be410fc1c0651da374fc6ebe7f96606e90d927d188894a73".toCharArray());
byte[] publicY = Hex
.decodeHex("d2ffaa73957d76984633fc1cc54d0b763ca0559a9dff9706e9f4557dacc3f52a".toCharArray());
byte[] privateS = Hex
.decodeHex("1dae121ba406802ef07c193c1ee4df91115aabd79c1ed7f4c0ef7ef6a5449400".toCharArray());
// Get Elliptic Curve Parameter spec for secp256r1
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
// Create key specs
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
// Get keys
serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
// Get certificates from key store
char[] serverKeyStorePwd = "server".toCharArray();
KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream serverKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/serverKeyStore.jks")) {
serverKeyStore.load(serverKeyStoreFile, serverKeyStorePwd);
}
serverPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("server", serverKeyStorePwd);
rootCAX509Cert = (X509Certificate) serverKeyStore.getCertificate("rootCA");
serverX509Cert = (X509Certificate) serverKeyStore.getCertificate("server");
serverX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("server_self_signed");
trustedCertificates[0] = serverX509Cert;
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
defaultBootstrapCredentials = new LwM2MBootstrapClientCredentials();
NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential();
defaultBootstrapCredentials.setBootstrapServer(serverCredentials);
defaultBootstrapCredentials.setLwm2mServer(serverCredentials);
}
}

35
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2021 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 lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY;
@Slf4j
public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
@Test
public void testConnectAndObserveTelemetry() throws Exception {
NoSecClientCredential clientCredentials = createNoSecClientCredentials(ENDPOINT);
super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, ENDPOINT);
}
}

17
application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/PskLwm2mIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java

@ -13,30 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.sql;
package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex;
import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import java.nio.charset.StandardCharsets;
import static org.eclipse.leshan.client.object.Security.psk;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID;
public class PskLwm2mIntegrationTest extends AbstractLwM2MIntegrationTest {
public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
@Test
public void testConnectWithPSKAndObserveTelemetry() throws Exception {
String pskIdentity = "SOME_PSK_ID";
String pskKey = "73656372657450534b73656372657450";
PSKClientCredentials clientCredentials = new PSKClientCredentials();
PSKClientCredential clientCredentials = new PSKClientCredential();
clientCredentials.setEndpoint(ENDPOINT);
clientCredentials.setKey(pskKey);
clientCredentials.setIdentity(pskIdentity);
Security security = psk(SECURE_URI,
123,
SHORT_SERVER_ID,
pskIdentity.getBytes(StandardCharsets.UTF_8),
Hex.decodeHex(pskKey.toCharArray()));
super.basicTestConnectionObserveTelemetry(security, clientCredentials, SECURE_COAP_CONFIG, ENDPOINT);

21
application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/RpkLwM2MIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java

@ -13,29 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.sql;
package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex;
import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import org.apache.commons.codec.binary.Base64;;
import static org.eclipse.leshan.client.object.Security.rpk;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID;
public class RpkLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
@Test
public void testConnectWithRPKAndObserveTelemetry() throws Exception {
RPKClientCredentials rpkClientCredentials = new RPKClientCredentials();
RPKClientCredential rpkClientCredentials = new RPKClientCredential();
rpkClientCredentials.setEndpoint(ENDPOINT);
rpkClientCredentials.setKey(Hex.encodeHexString(clientPublicKey.getEncoded()));
rpkClientCredentials.setKey(new String(Base64.encodeBase64(clientPublicKey.getEncoded())));
Security security = rpk(SECURE_URI,
123,
SHORT_SERVER_ID,
clientPublicKey.getEncoded(),
clientPrivateKey.getEncoded(),
serverX509Cert.getPublicKey().getEncoded());
super.basicTestConnectionObserveTelemetry(security, rpkClientCredentials, SECURE_COAP_CONFIG, ENDPOINT);
}
}

29
application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/X509LwM2MIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java

@ -13,39 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.sql;
package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential;
import org.thingsboard.server.common.transport.util.SslUtil;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import static org.eclipse.leshan.client.object.Security.x509;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID;
public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
@Test
public void testConnectAndObserveTelemetry() throws Exception {
X509ClientCredentials credentials = new X509ClientCredentials();
credentials.setEndpoint(ENDPOINT);
Security security = x509(SECURE_URI,
123,
clientX509Cert.getEncoded(),
clientPrivateKeyFromCert.getEncoded(),
serverX509Cert.getEncoded());
super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT);
}
@Ignore //See LwM2mClientContextImpl.unregister
@Test
public void testConnectWithCertAndObserveTelemetry() throws Exception {
X509ClientCredentials credentials = new X509ClientCredentials();
X509ClientCredential credentials = new X509ClientCredential();
credentials.setEndpoint(ENDPOINT);
credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted));
Security security = x509(SECURE_URI,
123,
SHORT_SERVER_ID,
clientX509CertNotTrusted.getEncoded(),
clientPrivateKeyFromCert.getEncoded(),
serverX509Cert.getEncoded());

42
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2021 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.eclipse.leshan.client.object.Security;
import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import static org.eclipse.leshan.client.object.Security.x509;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID;
public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
@Test
public void testConnectAndObserveTelemetry() throws Exception {
X509ClientCredential credentials = new X509ClientCredential();
credentials.setEndpoint(ENDPOINT);
Security security = x509(SECURE_URI,
SHORT_SERVER_ID,
clientX509Cert.getEncoded(),
clientPrivateKeyFromCert.getEncoded(),
serverX509Cert.getEncoded());
super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT);
}
}

2
application/src/test/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelperTest.java

@ -37,7 +37,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_TELEMETRY;
class LwM2mTransportServerHelperTest {

2
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java

@ -152,7 +152,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE");
String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue());
String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue());
String sha3Hash = EncryptionUtil.getSha3Hash(cert);
Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash);

2
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java

@ -154,7 +154,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString());
Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE);
String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue());
String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue());
String sha3Hash = EncryptionUtil.getSha3Hash(cert);
Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash);

1
application/src/test/resources/application-test.properties

@ -4,6 +4,7 @@ transport.lwm2m.server.security.credentials.keystore.store_file=lwm2m/credential
transport.lwm2m.server.security.credentials.keystore.store_password=server
transport.lwm2m.server.security.credentials.keystore.key_alias=server
transport.lwm2m.server.security.credentials.keystore.key_password=server
transport.lwm2m.bootstrap.enabled=false
transport.lwm2m.bootstrap.security.credentials.enabled=true
transport.lwm2m.bootstrap.security.credentials.type=KEYSTORE
transport.lwm2m.bootstrap.security.credentials.keystore.store_file=lwm2m/credentials/serverKeyStore.jks

144
application/src/test/resources/lwm2m/19.xml

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
FILE INFORMATION
OMA Permanent Document
File: OMA-SUP-LwM2M_BinaryAppDataContainer-V1_0_1-20190221-A
Type: xml
Public Reachable Information
Path: http://www.openmobilealliance.org/tech/profiles
Name: LwM2M_BinaryAppDataContainer-v1_0_1.xml
NORMATIVE INFORMATION
Information about this file can be found in the latest revision of
OMA-TS-LWM2M_BinaryAppDataContainer-V1_0_1
This is available at http://www.openmobilealliance.org/
Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues
CHANGE HISTORY
15062018 Status changed to Approved by DM, Doc Ref # OMA-DM&SE-2018-0061-INP_LWM2M_APPDATA_V1_0_ERP_for_final_Approval
21022019 Status changed to Approved by IPSO, Doc Ref # OMA-IPSO-2019-0025-INP_LwM2M_Object_App_Data_Container_1_0_1_for_Final_Approval
LEGAL DISCLAIMER
Copyright 2019 Open Mobile Alliance.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
The above license is used as a license under copyright only. Please
reference the OMA IPR Policy for patent licensing terms:
https://www.omaspecworks.org/about/intellectual-property-rights/
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd">
<Object ObjectType="MODefinition">
<Name>BinaryAppDataContainer</Name>
<Description1><![CDATA[This LwM2M Objects provides the application service data related to a LwM2M Server, eg. Water meter data.
There are several methods to create instance to indicate the message direction based on the negotiation between Application and LwM2M. The Client and Server should negotiate the instance(s) used to exchange the data. For example:
- Using a single instance for both directions communication, from Client to Server and from Server to Client.
- Using an instance for communication from Client to Server and another one for communication from Server to Client
- Using several instances
]]></Description1>
<ObjectID>19</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:19:1.1</ObjectURN>
<LWM2MVersion>1.1</LWM2MVersion>
<ObjectVersion>1.1</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="0"><Name>Data</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration />
<Units />
<Description><![CDATA[Indicates the application data content.]]></Description>
</Item>
<Item ID="1"><Name>Data Priority</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>1 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the Application data priority:
0:Immediate
1:BestEffort
2:Latest
3-100: Reserved for future use.
101-254: Proprietary mode.]]></Description>
</Item>
<Item ID="2"><Name>Data Creation Time</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Time</Type>
<RangeEnumeration />
<Units />
<Description><![CDATA[Indicates the Data instance creation timestamp.]]></Description>
</Item>
<Item ID="3"><Name>Data Description</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration>32 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the data description.
e.g. "meter reading".]]></Description>
</Item>
<Item ID="4"><Name>Data Format</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration>32 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the format of the Application Data.
e.g. YG-Meter-Water-Reading
UTF8-string
]]></Description>
</Item>
<Item ID="5"><Name>App ID</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>2 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the destination Application ID.]]></Description>
</Item></Resources>
<Description2><![CDATA[]]></Description2>
</Object>
</LWM2M>

103
application/src/test/resources/lwm2m/3303.xml

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright © 2016-2018 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.
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://openmobilealliance.org/tech/profiles/LWM2M.xsd">
<Object ObjectType="MODefinition">
<Name>Temperature</Name>
<Description1>This IPSO object should be used with a temperature sensor to report a temperature measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the temperature sensor. An example measurement unit is degrees Celsius.</Description1>
<ObjectID>3303</ObjectID>
<ObjectURN>urn:oma:lwm2m:ext:3303</ObjectURN>
<LWM2MVersion>1.0</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="5700">
<Name>Sensor Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>Last or Current Measured Value from the Sensor</Description>
</Item>
<Item ID="5601">
<Name>Min Measured Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The minimum value measured by the sensor since power ON or reset</Description>
</Item>
<Item ID="5602">
<Name>Max Measured Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The maximum value measured by the sensor since power ON or reset</Description>
</Item>
<Item ID="5603">
<Name>Min Range Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The minimum value that can be measured by the sensor</Description>
</Item>
<Item ID="5604">
<Name>Max Range Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The maximum value that can be measured by the sensor</Description>
</Item>
<Item ID="5701">
<Name>Sensor Units</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>Measurement Units Definition.</Description>
</Item>
<Item ID="5605">
<Name>Reset Min and Max Measured Values</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>Reset the Min and Max Measured Values to Current Value</Description>
</Item>
</Resources>
<Description2></Description2>
</Object>
</LWM2M>

143
application/src/test/resources/lwm2m/6.xml

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
FILE INFORMATION
OMA Permanent Document
File: OMA-SUP-XML_6-V1_0-20201110-A.xml
http://www.openmobilealliance.org/release/ObjLwM2M_Location/
OMNA LwM2M Registry
Path: https://github.com/OpenMobileAlliance/lwm2m-registry
Name: 6.xml
NORMATIVE INFORMATION
Information about this file can be found in the latest revision of
OMA-TS-LightweightM2M-V1_0_2
OMA-TS-LightweightM2M_Core-V1_1_1
This is available at http://www.openmobilealliance.org/release/LightweightM2M/
Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues
LEGAL DISCLAIMER
Copyright 2020 Open Mobile Alliance.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
The above license is used as a license under copyright only. Please
reference the OMA IPR Policy for patent licensing terms:
https://www.omaspecworks.org/about/intellectual-property-rights/
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M.xsd">
<Object ObjectType="MODefinition">
<Name>Location</Name>
<Description1><![CDATA[This LwM2M Object provides a range of location telemetry related information which can be queried by the LwM2M Server.]]></Description1>
<ObjectID>6</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:6</ObjectURN>
<LWM2MVersion>1.0</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="0">
<Name>Latitude</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units>lat</Units>
<Description><![CDATA[The decimal notation of latitude, e.g. -43.5723 [World Geodetic System 1984].]]></Description>
</Item>
<Item ID="1">
<Name>Longitude</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units>lon</Units>
<Description><![CDATA[The decimal notation of longitude, e.g. 153.21760 [World Geodetic System 1984].]]></Description>
</Item>
<Item ID="2">
<Name>Altitude</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units>m</Units>
<Description><![CDATA[The decimal notation of altitude in meters above sea level.]]></Description>
</Item>
<Item ID="3">
<Name>Radius</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units>m</Units>
<Description><![CDATA[The value in this resource indicates the radius of a circular area in meters. The circular area is used to describe uncertainty about a point for coordinates in a two-dimensional coordinate reference systems (CRS). The center point of a circular area is specified by using the Latitude and the Longitude Resources.]]></Description>
</Item>
<Item ID="4">
<Name>Velocity</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[The velocity of the LwM2M Client, as defined in [3GPP-TS_23.032].]]></Description>
</Item>
<Item ID="5">
<Name>Timestamp</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Time</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[The timestamp of when the location measurement was performed.]]></Description>
</Item>
<Item ID="6"><Name>Speed</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units>m/s</Units>
<Description><![CDATA[Speed is the time rate of change in position of a LwM2M Client without regard for direction: the scalar component of velocity.]]></Description>
</Item></Resources>
<Description2></Description2>
</Object>
</LWM2M>

8
common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java

@ -21,7 +21,15 @@ public class StringUtils {
return source == null || source.isEmpty();
}
public static boolean isBlank(String source) {
return source == null || source.isEmpty() || source.trim().isEmpty();
}
public static boolean isNotEmpty(String source) {
return source != null && !source.isEmpty();
}
public static boolean isNotBlank(String source) {
return source != null && !source.isEmpty() && !source.trim().isEmpty();
}
}

6
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MServerCredentialsWithKeys.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MBootstrapClientCredentialWithKeys.java

@ -19,11 +19,11 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.binary.Base64;
@Getter
@Setter
public abstract class AbstractLwM2MServerCredentialsWithKeys implements LwM2MServerCredentials {
public abstract class AbstractLwM2MBootstrapClientCredentialWithKeys implements LwM2MBootstrapClientCredential {
private String clientPublicKeyOrId;
private String clientSecretKey;
@ -40,6 +40,6 @@ public abstract class AbstractLwM2MServerCredentialsWithKeys implements LwM2MSer
@SneakyThrows
private static byte[] getDecoded(String key) {
return Hex.decodeHex(key.toLowerCase().toCharArray());
return Base64.decodeBase64(key.getBytes());
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredential.java

@ -22,6 +22,6 @@ import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public abstract class AbstractLwM2MClientCredentials implements LwM2MClientCredentials {
public abstract class AbstractLwM2MClientCredential implements LwM2MClientCredential {
private String endpoint;
}

16
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientSecurityCredential.java

@ -17,14 +17,14 @@ package org.thingsboard.server.common.data.device.credentials.lwm2m;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.codec.DecoderException;
@Getter
@Setter
public class PSKClientCredentials extends AbstractLwM2MClientCredentialsWithKey {
private String identity;
public abstract class AbstractLwM2MClientSecurityCredential extends AbstractLwM2MClientCredential {
@Getter
@Setter
protected String key;
@Override
public LwM2MSecurityMode getSecurityConfigClientMode() {
return LwM2MSecurityMode.PSK;
}
protected byte[] securityInBytes;
public abstract byte[] getDecoded() throws IllegalArgumentException, DecoderException;
}

10
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MServerCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapClientCredential.java

@ -24,13 +24,13 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
use = JsonTypeInfo.Id.NAME,
property = "securityMode")
@JsonSubTypes({
@JsonSubTypes.Type(value = NoSecServerCredentials.class, name = "NO_SEC"),
@JsonSubTypes.Type(value = PSKServerCredentials.class, name = "PSK"),
@JsonSubTypes.Type(value = RPKServerCredentials.class, name = "RPK"),
@JsonSubTypes.Type(value = X509ServerCredentials.class, name = "X509")
@JsonSubTypes.Type(value = NoSecBootstrapClientCredential.class, name = "NO_SEC"),
@JsonSubTypes.Type(value = PSKBootstrapClientCredential.class, name = "PSK"),
@JsonSubTypes.Type(value = RPKBootstrapClientCredential.class, name = "RPK"),
@JsonSubTypes.Type(value = X509BootstrapClientCredential.class, name = "X509")
})
@JsonIgnoreProperties(ignoreUnknown = true)
public interface LwM2MServerCredentials {
public interface LwM2MBootstrapClientCredential {
@JsonIgnore
LwM2MSecurityMode getSecurityMode();

6
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapClientCredentials.java

@ -20,7 +20,7 @@ import lombok.Setter;
@Getter
@Setter
public class LwM2MBootstrapCredentials {
private LwM2MServerCredentials bootstrapServer;
private LwM2MServerCredentials lwm2mServer;
public class LwM2MBootstrapClientCredentials {
private LwM2MBootstrapClientCredential bootstrapServer;
private LwM2MBootstrapClientCredential lwm2mServer;
}

10
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredential.java

@ -24,13 +24,13 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
use = JsonTypeInfo.Id.NAME,
property = "securityConfigClientMode")
@JsonSubTypes({
@JsonSubTypes.Type(value = NoSecClientCredentials.class, name = "NO_SEC"),
@JsonSubTypes.Type(value = PSKClientCredentials.class, name = "PSK"),
@JsonSubTypes.Type(value = RPKClientCredentials.class, name = "RPK"),
@JsonSubTypes.Type(value = X509ClientCredentials.class, name = "X509")
@JsonSubTypes.Type(value = NoSecClientCredential.class, name = "NO_SEC"),
@JsonSubTypes.Type(value = PSKClientCredential.class, name = "PSK"),
@JsonSubTypes.Type(value = RPKClientCredential.class, name = "RPK"),
@JsonSubTypes.Type(value = X509ClientCredential.class, name = "X509")
})
@JsonIgnoreProperties(ignoreUnknown = true)
public interface LwM2MClientCredentials {
public interface LwM2MClientCredential {
@JsonIgnore
LwM2MSecurityMode getSecurityConfigClientMode();

4
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MDeviceCredentials.java

@ -21,6 +21,6 @@ import lombok.Setter;
@Getter
@Setter
public class LwM2MDeviceCredentials {
private LwM2MClientCredentials client;
private LwM2MBootstrapCredentials bootstrap;
private LwM2MClientCredential client;
private LwM2MBootstrapClientCredentials bootstrap;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecServerCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecBootstrapClientCredential.java

@ -15,7 +15,7 @@
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
public class NoSecServerCredentials implements LwM2MServerCredentials {
public class NoSecBootstrapClientCredential implements LwM2MBootstrapClientCredential {
@Override
public LwM2MSecurityMode getSecurityMode() {

2
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredential.java

@ -15,7 +15,7 @@
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
public class NoSecClientCredentials extends AbstractLwM2MClientCredentials {
public class NoSecClientCredential extends AbstractLwM2MClientCredential {
@Override
public LwM2MSecurityMode getSecurityConfigClientMode() {

2
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKServerCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKBootstrapClientCredential.java

@ -15,7 +15,7 @@
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
public class PSKServerCredentials extends AbstractLwM2MServerCredentialsWithKeys {
public class PSKBootstrapClientCredential extends AbstractLwM2MBootstrapClientCredentialWithKeys {
@Override
public LwM2MSecurityMode getSecurityMode() {

40
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredential.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2021 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;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
@Getter
@Setter
public class PSKClientCredential extends AbstractLwM2MClientSecurityCredential {
private String identity;
@Override
public LwM2MSecurityMode getSecurityConfigClientMode() {
return LwM2MSecurityMode.PSK;
}
@Override
public byte[] getDecoded() throws IllegalArgumentException, DecoderException {
if (securityInBytes == null) {
securityInBytes = Hex.decodeHex(key.toLowerCase().toCharArray());
}
return securityInBytes;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKServerCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKBootstrapClientCredential.java

@ -15,7 +15,7 @@
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
public class RPKServerCredentials extends AbstractLwM2MServerCredentialsWithKeys {
public class RPKBootstrapClientCredential extends AbstractLwM2MBootstrapClientCredentialWithKeys {
@Override
public LwM2MSecurityMode getSecurityMode() {

13
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredential.java

@ -15,10 +15,21 @@
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
public class RPKClientCredentials extends AbstractLwM2MClientCredentialsWithKey {
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
public class RPKClientCredential extends AbstractLwM2MClientSecurityCredential {
@Override
public LwM2MSecurityMode getSecurityConfigClientMode() {
return LwM2MSecurityMode.RPK;
}
@Override
public byte[] getDecoded() throws IllegalArgumentException, DecoderException {
if (securityInBytes == null) {
securityInBytes = Base64.decodeBase64(key.getBytes());
}
return securityInBytes;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ServerCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509BootstrapClientCredential.java

@ -15,7 +15,7 @@
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
public class X509ServerCredentials extends AbstractLwM2MServerCredentialsWithKeys {
public class X509BootstrapClientCredential extends AbstractLwM2MBootstrapClientCredentialWithKeys {
@Override
public LwM2MSecurityMode getSecurityMode() {

13
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java → common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredential.java

@ -17,14 +17,25 @@ package org.thingsboard.server.common.data.device.credentials.lwm2m;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
@Getter
@Setter
public class X509ClientCredentials extends AbstractLwM2MClientCredentials {
public class X509ClientCredential extends AbstractLwM2MClientSecurityCredential {
private String cert;
@Override
public LwM2MSecurityMode getSecurityConfigClientMode() {
return LwM2MSecurityMode.X509;
}
@Override
public byte[] getDecoded() throws IllegalArgumentException, DecoderException {
if (securityInBytes == null && cert != null) {
securityInBytes = Base64.decodeBase64(cert.getBytes());
}
return securityInBytes;
}
}

11
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java

@ -17,9 +17,11 @@ package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration;
import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration;
import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration;
import java.util.List;
@Data
public class Lwm2mDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
@ -27,7 +29,8 @@ public class Lwm2mDeviceProfileTransportConfiguration implements DeviceProfileTr
private static final long serialVersionUID = 6257277825459600068L;
private TelemetryMappingConfiguration observeAttr;
private BootstrapConfiguration bootstrap;
private boolean bootstrapServerUpdateEnable;
private List<LwM2MBootstrapServerCredential> bootstrap;
private OtherConfiguration clientLwM2mSettings;
@Override

2
common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java → common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.data.lwm2m;
package org.thingsboard.server.common.data.device.profile.lwm2m;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

4
common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java → common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.data.lwm2m;
package org.thingsboard.server.common.data.device.profile.lwm2m;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@ -33,6 +33,4 @@ public class OtherConfiguration extends PowerSavingConfiguration {
private Long pagingTransmissionWindow;
private String fwUpdateResource;
private String swUpdateResource;
private boolean compositeOperationsSupport;
}

3
common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/TelemetryMappingConfiguration.java → common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.data.lwm2m;
package org.thingsboard.server.common.data.device.profile.lwm2m;
import lombok.Data;
import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes;
import java.util.Map;
import java.util.Set;

24
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentialsWithKey.java → common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/AbstractLwM2MBootstrapServerCredential.java

@ -13,27 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.binary.Base64;
public abstract class AbstractLwM2MClientCredentialsWithKey extends AbstractLwM2MClientCredentials {
@Getter
@Setter
private String key;
@Getter
@Setter
public abstract class AbstractLwM2MBootstrapServerCredential extends LwM2MServerSecurityConfig implements LwM2MBootstrapServerCredential {
private byte[] keyInBytes;
@JsonIgnore
public byte[] getDecodedCServerPublicKey() {
return getDecoded(serverPublicKey);
}
@SneakyThrows
@JsonIgnore
public byte[] getDecodedKey() {
if (keyInBytes == null) {
keyInBytes = Hex.decodeHex(key.toLowerCase().toCharArray());
}
return keyInBytes;
private static byte[] getDecoded(String key) {
return Base64.decodeBase64(key.getBytes());
}
}

37
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServerCredential.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2021 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.profile.lwm2m.bootstrap;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "securityMode")
@JsonSubTypes({
@JsonSubTypes.Type(value = NoSecLwM2MBootstrapServerCredential.class, name = "NO_SEC"),
@JsonSubTypes.Type(value = PSKLwM2MBootstrapServerCredential.class, name = "PSK"),
@JsonSubTypes.Type(value = RPKLwM2MBootstrapServerCredential.class, name = "RPK"),
@JsonSubTypes.Type(value = X509LwM2MBootstrapServerCredential.class, name = "X509")
})
@JsonIgnoreProperties(ignoreUnknown = true)
public interface LwM2MBootstrapServerCredential {
@JsonIgnore
LwM2MSecurityMode getSecurityMode();
}

27
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServersConfiguration.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2021 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.profile.lwm2m.bootstrap;
import lombok.Data;
import java.util.List;
@Data
public class LwM2MBootstrapServersConfiguration {
List<LwM2MBootstrapServerCredential> bootstrap;
}

62
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java

@ -0,0 +1,62 @@
/**
* Copyright © 2016-2021 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.profile.lwm2m.bootstrap;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel
@Data
public class LwM2MServerSecurityConfig {
@ApiModelProperty(position = 1, value = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " +
"This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. " +
"The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", readOnly = true)
protected Integer shortServerId = 123;
/** Security -> ObjectId = 0 'LWM2M Security' */
@ApiModelProperty(position = 2, value = "Is Bootstrap Server or Lwm2m Server. " +
"The LwM2M Client MAY be configured to use one or more LwM2M Server Account(s). " +
"The LwM2M Client MUST have at most one LwM2M Bootstrap-Server Account. " +
"(*) The LwM2M client MUST have at least one LwM2M server account after completing the boot sequence specified.", example = "true or false", readOnly = true)
protected boolean bootstrapServerIs = false;
@ApiModelProperty(position = 3, value = "Host for 'No Security' mode", example = "0.0.0.0", readOnly = true)
protected String host;
@ApiModelProperty(position = 4, value = "Port for Lwm2m Server: 'No Security' mode: Lwm2m Server or Bootstrap Server", example = "'5685' or '5687'", readOnly = true)
protected Integer port;
@ApiModelProperty(position = 7, value = "Client Hold Off Time. The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode. (This information is relevant for use with a Bootstrap-Server only.)", example = "1", readOnly = true)
protected Integer clientHoldOffTime = 1;
@ApiModelProperty(position = 8, value = "Server Public Key for 'Security' mode (DTLS): RPK or X509. Format: base64 encoded", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" +
"+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", readOnly = true)
protected String serverPublicKey;
@ApiModelProperty(position = 9, value = "Bootstrap Server Account Timeout (If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.)", example = "0", readOnly = true)
Integer bootstrapServerAccountTimeout = 0;
/** Config -> ObjectId = 1 'LwM2M Server' */
@ApiModelProperty(position = 10, value = "Specify the lifetime of the registration in seconds.", example = "300", readOnly = true)
private Integer lifetime = 300;
@ApiModelProperty(position = 11, value = "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation. " +
"If this Resource doesn’t exist, the default value is 0.", example = "1", readOnly = true)
private Integer defaultMinPeriod = 1;
/** ResourceID=6 'Notification Storing When Disabled or Offline' */
@ApiModelProperty(position = 12, value = "If true, the LwM2M Client stores “Notify” operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored “Notify” operations to the Server. " +
"If false, the LwM2M Client discards all the “Notify” operations or temporarily disables the Observe function while the LwM2M Server is disabled or the LwM2M Client is offline. " +
"The default value is true.", example = "true", readOnly = true)
private boolean notifIfDisabled = true;
@ApiModelProperty(position = 14, value = "This Resource defines the transport binding configured for the LwM2M Client. " +
"If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.", example = "U", readOnly = true)
private String binding = "U";
}

29
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2021 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.profile.lwm2m.bootstrap;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel
@Data
public class LwM2MServerSecurityConfigDefault extends LwM2MServerSecurityConfig {
@ApiModelProperty(position = 5, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", readOnly = true)
protected String securityHost;
@ApiModelProperty(position = 6, value = "Port for 'Security' mode (DTLS): Lwm2m Server or Bootstrap Server", example = "5686 or 5688", readOnly = true)
protected Integer securityPort;
}

25
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2021 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.profile.lwm2m.bootstrap;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
public class NoSecLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential {
@Override
public LwM2MSecurityMode getSecurityMode() {
return LwM2MSecurityMode.NO_SEC;
}
}

25
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKLwM2MBootstrapServerCredential.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2021 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.profile.lwm2m.bootstrap;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
public class PSKLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential {
@Override
public LwM2MSecurityMode getSecurityMode() {
return LwM2MSecurityMode.PSK;
}
}

25
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKLwM2MBootstrapServerCredential.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2021 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.profile.lwm2m.bootstrap;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
public class RPKLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential {
@Override
public LwM2MSecurityMode getSecurityMode() {
return LwM2MSecurityMode.RPK;
}
}

25
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509LwM2MBootstrapServerCredential.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2021 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.profile.lwm2m.bootstrap;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
public class X509LwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential {
@Override
public LwM2MSecurityMode getSecurityMode() {
return LwM2MSecurityMode.X509;
}
}

44
common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java

@ -1,44 +0,0 @@
/**
* Copyright © 2016-2021 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.lwm2m;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel
@Data
public class ServerSecurityConfig {
@ApiModelProperty(position = 1, value = "Is Bootstrap Server", example = "true", readOnly = true)
boolean bootstrapServerIs = true;
@ApiModelProperty(position = 2, value = "Host for 'No Security' mode", example = "0.0.0.0", readOnly = true)
String host;
@ApiModelProperty(position = 3, value = "Port for 'No Security' mode", example = "5687", readOnly = true)
Integer port;
@ApiModelProperty(position = 4, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", readOnly = true)
String securityHost;
@ApiModelProperty(position = 5, value = "Port for 'Security' mode (DTLS)", example = "5688", readOnly = true)
Integer securityPort;
@ApiModelProperty(position = 5, value = "Server short Id", example = "111", readOnly = true)
Integer serverId = 111;
@ApiModelProperty(position = 7, value = "Client Hold Off Time", example = "1", readOnly = true)
Integer clientHoldOffTime = 1;
@ApiModelProperty(position = 8, value = "Server Public Key (base64 encoded)", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" +
"+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", readOnly = true)
String serverPublicKey;
@ApiModelProperty(position = 9, value = "Bootstrap Server Account Timeout", example = "0", readOnly = true)
Integer bootstrapServerAccountTimeout = 0;
}

1
common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java

@ -112,5 +112,4 @@ public class DeviceCredentials extends BaseData<DeviceCredentialsId> implements
+ credentialsId + ", credentialsValue=" + credentialsValue + ", createdTime=" + createdTime + ", id="
+ id + "]";
}
}

23
common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java

@ -28,15 +28,30 @@ public class EncryptionUtil {
private EncryptionUtil() {
}
public static String trimNewLines(String input) {
public static String certTrimNewLines(String input) {
return input.replaceAll("-----BEGIN CERTIFICATE-----", "")
.replaceAll("-----END CERTIFICATE-----", "")
.replaceAll("\n", "")
.replaceAll("\r", "");
.replaceAll("\r", "")
.replaceAll("-----END CERTIFICATE-----", "");
}
public static String pubkTrimNewLines(String input) {
return input.replaceAll("-----BEGIN PUBLIC KEY-----", "")
.replaceAll("\n", "")
.replaceAll("\r", "")
.replaceAll("-----END PUBLIC KEY-----", "");
}
public static String prikTrimNewLines(String input) {
return input.replaceAll("-----BEGIN EC PRIVATE KEY-----", "")
.replaceAll("\n", "")
.replaceAll("\r", "")
.replaceAll("-----END EC PRIVATE KEY-----", "");
}
public static String getSha3Hash(String data) {
String trimmedData = trimNewLines(data);
String trimmedData = certTrimNewLines(data);
byte[] dataBytes = trimmedData.getBytes();
SHA3Digest md = new SHA3Digest(256);
md.reset();

26
common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mBootstrapTransportComponent.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2021 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.queue.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.lwm2m.enabled}'=='true')) && '${transport.lwm2m.bootstrap.enabled:false}'=='true'")
public @interface TbLwM2mBootstrapTransportComponent {
}

2
common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java

@ -21,6 +21,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.lwm2m.enabled}'=='true')")
public @interface TbLwM2mTransportComponent {
}

1
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java

@ -52,7 +52,6 @@ import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

31
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java

@ -17,39 +17,30 @@ package org.thingsboard.server.transport.lwm2m.bootstrap;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.leshan.core.model.ObjectLoader;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.StaticModel;
import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigurationAdapter;
import org.thingsboard.server.queue.util.TbLwM2mBootstrapTransportComponent;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MInMemoryBootstrapConfigStore;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig;
@Slf4j
@Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')")
@TbLwM2mBootstrapTransportComponent
@RequiredArgsConstructor
public class LwM2MTransportBootstrapService {
private boolean pskMode = false;
@ -58,7 +49,7 @@ public class LwM2MTransportBootstrapService {
private final LwM2MTransportBootstrapConfig bootstrapConfig;
private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore;
private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore;
private final TransportService transportService;
private LeshanBootstrapServer server;
@PostConstruct
@ -84,15 +75,11 @@ public class LwM2MTransportBootstrapService {
/* Create CoAP Config */
builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig));
/* Define model provider (Create Models )*/
List<ObjectModel> models = ObjectLoader.loadDefault();
builder.setModel(new StaticModel(models));
/* Create credentials */
this.setServerWithCredentials(builder);
/* Set securityStore with new ConfigStore */
builder.setConfigStore(new LwM2MInMemoryBootstrapConfigurationAdapter(lwM2MInMemoryBootstrapConfigStore));
builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
/* SecurityStore */
builder.setSecurityStore(lwM2MBootstrapSecurityStore);
@ -107,7 +94,7 @@ public class LwM2MTransportBootstrapService {
/* Set DTLS Config */
builder.setDtlsConfig(dtlsConfig);
BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore);
BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore, lwM2MInMemoryBootstrapConfigStore, transportService);
builder.setSessionManager(sessionManager);
/* Create BootstrapServer */

135
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java

@ -15,32 +15,31 @@
*/
package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.request.BindingMode;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
import org.thingsboard.server.common.data.device.credentials.lwm2m.AbstractLwM2MBootstrapClientCredentialWithKeys;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Data
public class LwM2MBootstrapConfig implements Serializable {
/*
interface BootstrapSecurityConfig
servers: BootstrapServersSecurityConfig,
bootstrapServer: ServerSecurityConfig,
lwm2mServer: ServerSecurityConfig
}
*/
/** -servers
* shortId: number,
* lifetime: number,
* defaultMinPeriod: number,
* notifIfDisabled: boolean,
* binding: string
* */
LwM2MBootstrapServers servers;
List<LwM2MBootstrapServerCredential> serverConfiguration;
/** -bootstrapServer, lwm2mServer
* interface ServerSecurityConfig
@ -55,50 +54,90 @@ public class LwM2MBootstrapConfig implements Serializable {
* serverId?: number,
* bootstrapServerAccountTimeout: number
* */
LwM2MServerBootstrap bootstrapServer;
@Getter
@Setter
private LwM2MBootstrapClientCredential bootstrapServer;
@Getter
@Setter
private LwM2MBootstrapClientCredential lwm2mServer;
LwM2MServerBootstrap lwm2mServer;
public LwM2MBootstrapConfig(){};
public LwM2MBootstrapConfig(List<LwM2MBootstrapServerCredential> serverConfiguration, LwM2MBootstrapClientCredential bootstrapClientServer, LwM2MBootstrapClientCredential lwm2mClientServer) {
this.serverConfiguration = serverConfiguration;
this.bootstrapServer = bootstrapClientServer;
this.lwm2mServer = lwm2mClientServer;
}
@JsonIgnore
public BootstrapConfig getLwM2MBootstrapConfig() {
BootstrapConfig configBs = new BootstrapConfig();
/* Delete old security objects */
configBs.toDelete.add("/0");
configBs.toDelete.add("/1");
/* Server Configuration (object 1) as defined in LWM2M 1.0.x TS. */
BootstrapConfig.ServerConfig server0 = new BootstrapConfig.ServerConfig();
server0.shortId = servers.getShortId();
server0.lifetime = servers.getLifetime();
server0.defaultMinPeriod = servers.getDefaultMinPeriod();
server0.notifIfDisabled = servers.isNotifIfDisabled();
server0.binding = BindingMode.parse(servers.getBinding());
configBs.servers.put(0, server0);
/* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */
this.bootstrapServer.setBootstrapServerIs(true);
configBs.security.put(0, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.getSecurityHost(), this.lwm2mServer.getSecurityPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId()));
/* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Server instance = 1 */
configBs.security.put(1, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.getSecurityHost(), this.lwm2mServer.getSecurityPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId()));
configBs.autoIdForSecurityObject = true;
int id = 0;
for (LwM2MBootstrapServerCredential serverCredential : serverConfiguration) {
BootstrapConfig.ServerConfig serverConfig = setServerConfig((AbstractLwM2MBootstrapServerCredential) serverCredential);
configBs.servers.put(id, serverConfig);
BootstrapConfig.ServerSecurity serverSecurity = setServerSecurity((AbstractLwM2MBootstrapServerCredential) serverCredential, serverCredential.getSecurityMode());
configBs.security.put(id, serverSecurity);
id++;
}
/** in LwM2mDefaultBootstrapSessionManager -> initTasks
* Delete all security/config objects if update bootstrap server and lwm2m server
* if other: del or update only instances */
return configBs;
}
private BootstrapConfig.ServerSecurity setServerSecurity(String host, Integer port, String securityHost, Integer securityPort, boolean bootstrapServer, SecurityMode securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) {
private BootstrapConfig.ServerSecurity setServerSecurity(AbstractLwM2MBootstrapServerCredential serverCredential, LwM2MSecurityMode securityMode) {
BootstrapConfig.ServerSecurity serverSecurity = new BootstrapConfig.ServerSecurity();
if (securityMode.equals(SecurityMode.NO_SEC)) {
serverSecurity.uri = "coap://" + host + ":" + Integer.toString(port);
} else {
serverSecurity.uri = "coaps://" + securityHost + ":" + Integer.toString(securityPort);
String serverUri = "coap://";
byte[] publicKeyOrId = new byte[]{};
byte[] secretKey = new byte[]{};
byte[] serverPublicKey = new byte[]{};
serverSecurity.serverId = serverCredential.getShortServerId();
log.info("serverId = [{}]", serverSecurity.serverId);
serverSecurity.securityMode = SecurityMode.valueOf(securityMode.name());
serverSecurity.bootstrapServer = serverCredential.isBootstrapServerIs();
if (!LwM2MSecurityMode.NO_SEC.equals(securityMode)) {
AbstractLwM2MBootstrapClientCredentialWithKeys server;
if (serverSecurity.bootstrapServer) {
server = (AbstractLwM2MBootstrapClientCredentialWithKeys) this.bootstrapServer;
} else {
server = (AbstractLwM2MBootstrapClientCredentialWithKeys) this.lwm2mServer;
}
serverUri = "coaps://";
if (LwM2MSecurityMode.PSK.equals(securityMode)) {
publicKeyOrId = server.getClientPublicKeyOrId().getBytes();
secretKey = Hex.decodeHex(server.getClientSecretKey().toCharArray());
log.info("publicKeyOrId [{}]", new String(publicKeyOrId, StandardCharsets.UTF_8));
} else {
publicKeyOrId = server.getDecodedClientPublicKeyOrId();
secretKey = server.getDecodedClientSecretKey();
log.info("publicKeyOrId [{}]", Hex.encodeHexString(publicKeyOrId));
}
serverPublicKey = serverCredential.getDecodedCServerPublicKey();
}
serverSecurity.bootstrapServer = bootstrapServer;
serverSecurity.securityMode = securityMode;
serverSecurity.publicKeyOrId = setPublicKeyOrId(clientPublicKey, securityMode);
serverSecurity.serverPublicKey = (serverPublicKey != null && !serverPublicKey.isEmpty()) ? Hex.decodeHex(serverPublicKey.toCharArray()) : new byte[]{};
serverSecurity.secretKey = (secretKey != null && !secretKey.isEmpty()) ? Hex.decodeHex(secretKey.toCharArray()) : new byte[]{};
serverSecurity.serverId = serverId;
log.info("secretKey [{}]", Hex.encodeHexString(secretKey));
serverUri += (((serverCredential.getHost().equals("0.0.0.0") ? "localhost" : serverCredential.getHost()) + ":" + serverCredential.getPort()));
log.info("serverSecurity.uri = [{}]", serverUri);
serverSecurity.uri = serverUri;
serverSecurity.publicKeyOrId = publicKeyOrId;
serverSecurity.secretKey = secretKey;
serverSecurity.serverPublicKey = serverPublicKey;
log.info("server [{}]", Hex.encodeHexString(serverSecurity.serverPublicKey));
return serverSecurity;
}
private byte[] setPublicKeyOrId(String publicKeyOrIdStr, SecurityMode securityMode) {
return (publicKeyOrIdStr == null || publicKeyOrIdStr.isEmpty()) ? new byte[]{} :
SecurityMode.PSK.equals(securityMode) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) :
Hex.decodeHex(publicKeyOrIdStr.toCharArray());
private BootstrapConfig.ServerConfig setServerConfig (AbstractLwM2MBootstrapServerCredential serverCredential) {
BootstrapConfig.ServerConfig serverConfig = new BootstrapConfig.ServerConfig();
serverConfig.shortId = serverCredential.getShortServerId();
serverConfig.lifetime = serverCredential.getLifetime();
serverConfig.defaultMinPeriod = serverCredential.getDefaultMinPeriod();
serverConfig.notifIfDisabled = serverCredential.isNotifIfDisabled();
serverConfig.binding = BindingMode.parse(serverCredential.getBinding());
return serverConfig;
}
}

27
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java

@ -1,27 +0,0 @@
/**
* Copyright © 2016-2021 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.bootstrap.secure;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter;
public class LwM2MInMemoryBootstrapConfigurationAdapter extends BootstrapConfigurationStoreAdapter {
public LwM2MInMemoryBootstrapConfigurationAdapter(BootstrapConfigStore store) {
super(store);
}
}

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java

@ -42,8 +42,6 @@ public class LwM2MServerBootstrap {
public LwM2MServerBootstrap() {
}
;
public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) {
this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId();
this.clientSecretKey = bootstrapFromCredential.getClientSecretKey();

190
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java

@ -16,23 +16,42 @@
package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.BootstrapDownlinkRequest;
import org.eclipse.leshan.core.request.BootstrapFinishRequest;
import org.eclipse.leshan.core.request.BootstrapRequest;
import org.eclipse.leshan.core.request.Identity;
import org.eclipse.leshan.core.response.LwM2mResponse;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
import org.eclipse.leshan.server.bootstrap.BootstrapFailureCause;
import org.eclipse.leshan.server.bootstrap.BootstrapSession;
import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider;
import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession;
import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager;
import org.eclipse.leshan.server.model.LwM2mBootstrapModelProvider;
import org.eclipse.leshan.server.model.StandardBootstrapModelProvider;
import org.eclipse.leshan.server.security.BootstrapSecurityStore;
import org.eclipse.leshan.server.security.SecurityChecker;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapConfigStoreTaskProvider;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_INFO;
@Slf4j
public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager {
private BootstrapSecurityStore bsSecurityStore;
private SecurityChecker securityChecker;
private BootstrapTaskProvider tasksProvider;
private LwM2mBootstrapModelProvider modelProvider;
private TransportService transportService;
/**
* Create a {@link DefaultBootstrapSessionManager} using a default {@link SecurityChecker} to accept or refuse new
@ -40,29 +59,170 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
*
* @param bsSecurityStore the {@link BootstrapSecurityStore} used by default {@link SecurityChecker}.
*/
public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore) {
this(bsSecurityStore, new SecurityChecker());
public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore, TransportService transportService) {
this(bsSecurityStore, new SecurityChecker(), new LwM2MBootstrapConfigStoreTaskProvider(configStore),
new StandardBootstrapModelProvider());
this.transportService = transportService;
}
public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker) {
super(bsSecurityStore);
/**
* Create a {@link DefaultBootstrapSessionManager}.
*
* @param bsSecurityStore the {@link BootstrapSecurityStore} used by {@link SecurityChecker}.
* @param securityChecker used to accept or refuse new {@link BootstrapSession}.
*/
public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker,
BootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) {
super(bsSecurityStore, securityChecker, tasksProvider, modelProvider);
this.bsSecurityStore = bsSecurityStore;
this.securityChecker = securityChecker;
this.tasksProvider = tasksProvider;
this.modelProvider = modelProvider;
}
@SuppressWarnings("deprecation")
@Override
public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
boolean authorized;
if (bsSecurityStore != null) {
Iterator<SecurityInfo> securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ?
Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())).iterator() : bsSecurityStore.getAllByEndpoint(request.getEndpointName());
log.info("Bootstrap session started securityInfos: [{}]", securityInfos);
authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos);
} else {
authorized = true;
boolean authorized = true;
Iterator<SecurityInfo> securityInfos;
try {
if (bsSecurityStore != null && securityChecker != null) {
if (clientIdentity.isSecure() && clientIdentity.isPSK()) {
securityInfos = bsSecurityStore.getAllByEndpoint(clientIdentity.getPskIdentity());
} else {
securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName());
}
authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos);
}
} catch (LwM2MAuthException e) {
authorized = false;
}
DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized);
log.info("Bootstrap session started : {}", session);
if (authorized) {
this.sendLogs (request.getEndpointName(),
String.format("%s: Bootstrap session started...", LOG_LWM2M_INFO, request.getEndpointName()));
}
return session;
}
@Override
public boolean hasConfigFor(BootstrapSession session) {
BootstrapTaskProvider.Tasks firstTasks = tasksProvider.getTasks(session, null);
if (firstTasks == null) {
return false;
}
initTasks(session, firstTasks);
return true;
}
protected void initTasks(BootstrapSession bssession, BootstrapTaskProvider.Tasks tasks) {
DefaultBootstrapSession session = (DefaultBootstrapSession) bssession;
// set models
if (tasks.supportedObjects != null)
session.setModel(modelProvider.getObjectModel(session, tasks.supportedObjects));
// set Requests to Send
log.info("tasks.requestsToSend = [{}]", tasks.requestsToSend);
session.setRequests(tasks.requestsToSend);
// prepare list where we will store Responses
session.setResponses(new ArrayList<LwM2mResponse>(tasks.requestsToSend.size()));
// is last Tasks ?
session.setMoreTasks(!tasks.last);
}
@Override
public BootstrapDownlinkRequest<? extends LwM2mResponse> getFirstRequest(BootstrapSession bsSession) {
return nextRequest(bsSession);
}
protected BootstrapDownlinkRequest<? extends LwM2mResponse> nextRequest(BootstrapSession bsSession) {
DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requestsToSend = session.getRequests();
if (!requestsToSend.isEmpty()) {
// get next requests
return requestsToSend.remove(0);
} else {
if (session.hasMoreTasks()) {
BootstrapTaskProvider.Tasks nextTasks = tasksProvider.getTasks(session, session.getResponses());
if (nextTasks == null) {
session.setMoreTasks(false);
return new BootstrapFinishRequest();
}
initTasks(session, nextTasks);
return nextRequest(bsSession);
} else {
return new BootstrapFinishRequest();
}
}
}
@Override
public BootstrapPolicy onResponseSuccess(BootstrapSession bsSession,
BootstrapDownlinkRequest<? extends LwM2mResponse> request, LwM2mResponse response) {
if (!(request instanceof BootstrapFinishRequest)) {
// store response
DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
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());
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));
return BootstrapPolicy.finished();
}
}
@Override
public BootstrapPolicy onResponseError(BootstrapSession bsSession,
BootstrapDownlinkRequest<? extends LwM2mResponse> request, LwM2mResponse response) {
if (!(request instanceof BootstrapFinishRequest)) {
// store response
DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
session.getResponses().add(response);
this.sendLogs (bsSession.getEndpoint(),
String.format("%s: %s %s receives error response %s ", LOG_LWM2M_INFO,
request.getClass().getSimpleName(),
request.getPath().toString(), response.toString()));
// on response error for NOT bootstrap finish request we continue any sending next request
return BootstrapPolicy.continueWith(nextRequest(bsSession));
} else {
// on response error for bootstrap finish request we stop the session
this.sendLogs (bsSession.getEndpoint(),
String.format("%s: error response for request bootstrap finish. Stop the session: %s", LOG_LWM2M_ERROR, bsSession.toString()));
return BootstrapPolicy.failed();
}
}
@Override
public BootstrapPolicy onRequestFailure(BootstrapSession bsSession,
BootstrapDownlinkRequest<? extends LwM2mResponse> request, Throwable cause) {
this.sendLogs(bsSession.getEndpoint(),
String.format("%s: %s %s failed because of %s", LOG_LWM2M_ERROR, request.getClass().getSimpleName(),
request.getPath().toString(), cause.toString()));
return BootstrapPolicy.failed();
}
@Override
public void end(BootstrapSession bsSession) {
this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session finished.", LOG_LWM2M_INFO));
}
@Override
public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) {
this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session failed because of %s", LOG_LWM2M_ERROR,
cause.toString()));
}
private void sendLogs(String endpointName, String logMsg) {
log.info(logMsg);
transportService.log(((LwM2MBootstrapSecurityStore) bsSecurityStore).getSessionByEndpoint(endpointName), logMsg);
}
}

284
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java

@ -0,0 +1,284 @@
/**
* Copyright © 2016-2021 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.bootstrap.store;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.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;
import org.eclipse.leshan.server.bootstrap.BootstrapSession;
import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider;
import org.eclipse.leshan.server.bootstrap.BootstrapUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest;
@Slf4j
public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvider {
private BootstrapConfigStore store;
private Map<Integer, String> supportedObjects;
/**
* Map<serverId, InstanceId>
*/
protected Map<Integer, Integer> securityInstances;
protected Map<Integer, Integer> serverInstances;
protected Integer bootstrapServerIdOld;
protected Integer bootstrapServerIdNew;
public LwM2MBootstrapConfigStoreTaskProvider(BootstrapConfigStore store) {
this.store = store;
}
@Override
public Tasks getTasks(BootstrapSession session, List<LwM2mResponse> previousResponse) {
BootstrapConfig config = store.get(session.getEndpoint(), session.getIdentity(), session);
if (config == null) {
return null;
}
if (previousResponse == null && shouldStartWithDiscover(config)) {
Tasks tasks = new Tasks();
tasks.requestsToSend = new ArrayList<>(1);
tasks.requestsToSend.add(new BootstrapDiscoverRequest());
tasks.last = false;
return tasks;
} else {
Tasks tasks = new Tasks();
if (this.supportedObjects == null) {
initSupportedObjectsDefault();
}
// add supportedObjects
tasks.supportedObjects = this.supportedObjects;
// handle bootstrap discover response
if (previousResponse != null) {
if (previousResponse.get(0) instanceof BootstrapDiscoverResponse) {
BootstrapDiscoverResponse discoverResponse = (BootstrapDiscoverResponse) previousResponse.get(0);
if (discoverResponse.isSuccess()) {
this.initAfterBootstrapDiscover(discoverResponse);
findSecurityInstanceId(discoverResponse.getObjectLinks());
} else {
log.warn(
"Bootstrap Discover return error {} : to continue bootstrap session without autoIdForSecurityObject mode. {}",
discoverResponse, session);
}
if (this.securityInstances.get(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);
if (readResponse.isSuccess()) {
findServerInstanceId(readResponse);
} 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());
} else {
// create requests from config
tasks.requestsToSend = BootstrapUtil.toRequests(config,
config.contentFormat != null ? config.contentFormat : session.getContentFormat());
}
return tasks;
}
}
protected boolean shouldStartWithDiscover(BootstrapConfig config) {
return config.autoIdForSecurityObject;
}
protected void findSecurityInstanceId(Link[] objectLinks) {
log.info("Object after discover: [{}]", objectLinks);
this.securityInstances = new HashMap<>();
for (Link link : objectLinks) {
if (link.getUrl().startsWith("/0/")) {
try {
LwM2mPath path = new LwM2mPath(link.getUrl());
if (path.isObjectInstance()) {
if (link.getAttributes().containsKey("ssid")) {
int serverId = Integer.parseInt(link.getAttributes().get("ssid"));
if (!this.securityInstances.containsKey(serverId)) {
this.securityInstances.put(serverId, path.getObjectInstanceId());
} else {
log.error("Invalid lwm2mSecurityInstance by [{}]", path.getObjectInstanceId());
}
this.securityInstances.put(Integer.valueOf(link.getAttributes().get("ssid")), path.getObjectInstanceId());
} else {
if (!this.securityInstances.containsKey(0)) {
this.securityInstances.put(0, path.getObjectInstanceId());
} else {
log.error("Invalid bootstrapSecurityInstance by [{}]", path.getObjectInstanceId());
}
}
}
} catch (Exception e) {
// ignore if this is not a LWM2M path
log.error("Invalid LwM2MPath starting by \"/0/\"");
}
}
}
}
protected void findServerInstanceId(BootstrapReadResponse readResponse) {
this.serverInstances = new HashMap<>();
((LwM2mObject) readResponse.getContent()).getInstances().values().forEach(instance -> {
serverInstances.put(((Long) instance.getResource(0).getValue()).intValue(), instance.getId());
});
if (this.securityInstances != null && this.securityInstances.size() > 0 && this.serverInstances != null && this.serverInstances.size() > 0) {
this.findBootstrapServerId();
}
}
protected void findBootstrapServerId() {
Map<Integer, Integer> filteredMap = this.serverInstances.entrySet()
.stream().filter(x -> !this.securityInstances.containsKey(x.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (filteredMap.size() > 0) {
this.bootstrapServerIdOld = filteredMap.keySet().stream().findFirst().get();
}
}
public BootstrapConfigStore getStore() {
return this.store;
}
private void initAfterBootstrapDiscover(BootstrapDiscoverResponse response) {
Link[] links = response.getObjectLinks();
Arrays.stream(links).forEach(link -> {
LwM2mPath path = new LwM2mPath(link.getUrl());
if (!path.isRoot() && path.getObjectId() < 3) {
if (path.isObject()) {
String ver = link.getAttributes().get("ver") != null ? link.getAttributes().get("ver") : "1.0";
this.supportedObjects.put(path.getObjectId(), ver);
}
}
});
}
public List<BootstrapDownlinkRequest<? extends LwM2mResponse>> toRequests(BootstrapConfig bootstrapConfig,
ContentFormat contentFormat) {
List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requests = new ArrayList<>();
Set<String> pathsDelete = new HashSet<>();
List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requestsWrite = new ArrayList<>();
boolean isBsServer = false;
boolean isLwServer = false;
/** Map<serverId, InstanceId> */
Map<Integer, Integer> instances = new HashMap<>();
// handle security
int id = 0;
for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfig.security).values()) {
if (security.bootstrapServer) {
requestsWrite.add(toWriteRequest(this.securityInstances.get(0), security, contentFormat));
isBsServer = true;
this.bootstrapServerIdNew = security.serverId;
instances.put(security.serverId, this.securityInstances.get(0));
} else {
if (id == this.securityInstances.get(0)) {
id++;
}
requestsWrite.add(toWriteRequest(id, security, contentFormat));
instances.put(security.serverId, id);
isLwServer = true;
if (!isBsServer && this.securityInstances.containsKey(security.serverId) && id != this.securityInstances.get(security.serverId)) {
pathsDelete.add("/0/" + this.securityInstances.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<Integer, Integer> entry : this.securityInstances.entrySet()) {
if (entry.getValue().equals(id)) {
serverIdOld = entry.getKey();
}
}
if (!isBsServer && serverIdOld != null && this.serverInstances.containsKey(serverIdOld)) {
pathsDelete.add("/1/" + this.serverInstances.get(serverIdOld));
}
id++;
}
}
// handle server
for (Map.Entry<Integer, BootstrapConfig.ServerConfig> 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 (this.bootstrapServerIdNew != null && server.getValue().shortId == this.bootstrapServerIdNew &&
(this.bootstrapServerIdNew != this.bootstrapServerIdOld || securityInstanceId != this.serverInstances.get(this.bootstrapServerIdOld))) {
pathsDelete.add("/1/" + this.serverInstances.get(this.bootstrapServerIdOld));
/** Delete instance if serverIdNew is present in serverInstances and securityInstanceIdOld by serverIdNew not equals serverInstanceIdOld */
} else if (this.serverInstances.containsKey(server.getValue().shortId) && securityInstanceId != this.serverInstances.get(server.getValue().shortId)) {
pathsDelete.add("/1/" + this.serverInstances.get(server.getValue().shortId));
}
}
}
// handle acl
for (Map.Entry<Integer, BootstrapConfig.ACLConfig> acl : bootstrapConfig.acls.entrySet()) {
requestsWrite.add(toWriteRequest(acl.getKey(), acl.getValue(), contentFormat));
}
// handle delete
if (isBsServer && isLwServer) {
requests.add(new BootstrapDeleteRequest("/0"));
requests.add(new BootstrapDeleteRequest("/1"));
} else {
pathsDelete.forEach(pathDelete -> requests.add(new BootstrapDeleteRequest(pathDelete)));
}
// handle write
if (requestsWrite.size() > 0) {
requests.addAll(requestsWrite);
}
return (requests);
}
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");
}
}

128
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java

@ -13,43 +13,42 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
package org.thingsboard.server.transport.lwm2m.bootstrap.store;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.core.util.SecurityUtil;
import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore;
import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
import org.eclipse.leshan.server.security.BootstrapSecurityStore;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbLwM2mBootstrapTransportComponent;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getBootstrapParametersFromThingsboard;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_TELEMETRY;
@Slf4j
@Service("LwM2MBootstrapSecurityStore")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true' && '${transport.lwm2m.bootstrap.enable}'=='true')")
@TbLwM2mBootstrapTransportComponent
public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
private final EditableBootstrapConfigStore bootstrapConfigStore;
@ -58,6 +57,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
private final LwM2mTransportContext context;
private final LwM2mTransportServerHelper helper;
private final Map<String /* endpoint */, TransportProtos.SessionInfoProto> bsSessions = new ConcurrentHashMap<>();
public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContext context, LwM2mTransportServerHelper helper) {
this.bootstrapConfigStore = bootstrapConfigStore;
@ -69,12 +69,25 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override
public Iterator<SecurityInfo> getAllByEndpoint(String endPoint) {
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, BOOTSTRAP);
if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
if (store.getBootstrapCredentialConfig() != null) {
/* add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store);
endPoint = store.getEndpoint();
BootstrapConfig bsConfigNew = store.getBootstrapConfig();
if (bsConfigNew != null) {
try {
boolean bootstrapServerUpdateEnable = ((Lwm2mDeviceProfileTransportConfiguration)store.getDeviceProfile().getProfileData().getTransportConfiguration()).isBootstrapServerUpdateEnable();
if (!bootstrapServerUpdateEnable) {
Optional<Map.Entry<Integer, BootstrapConfig.ServerSecurity>> securities = bsConfigNew.security.entrySet().stream().filter(sec -> sec.getValue().bootstrapServer).findAny();
if (securities.isPresent()) {
bsConfigNew.security.entrySet().remove(securities.get());
int serverSortId = securities.get().getValue().serverId;
Optional<Map.Entry<Integer, BootstrapConfig.ServerConfig>> serverConfigs = bsConfigNew.servers.entrySet().stream().filter(serv -> ((BootstrapConfig.ServerConfig)serv.getValue()).shortId==serverSortId).findAny();
if (serverConfigs.isPresent()) {
bsConfigNew.servers.entrySet().remove(serverConfigs.get());
}
}
}
for (String config : bootstrapConfigStore.getAll().keySet()) {
if (config.equals(endPoint)) {
bootstrapConfigStore.remove(config);
@ -82,7 +95,12 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
}
bootstrapConfigStore.add(endPoint, bsConfigNew);
} catch (InvalidConfigurationException e) {
log.error("", e);
if (e.getMessage().contains("Psk identity") && e.getMessage().contains("already used for this bootstrap server")) {
log.trace("Invalid Bootstrap Configuration", e);
}
else {
log.error("Invalid Bootstrap Configuration", e);
}
}
return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator();
}
@ -101,7 +119,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
try {
bootstrapConfigStore.add(store.getEndpoint(), bsConfig);
} catch (InvalidConfigurationException e) {
log.error("", e);
log.error("Invalid Bootstrap Configuration", e);
}
return store.getSecurityInfo();
}
@ -113,34 +131,6 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
/* BootstrapConfig */
LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store);
if (lwM2MBootstrapConfig != null) {
/* Security info */
switch (lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode()) {
/* Use RPK only */
case PSK:
store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndpoint(),
lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(),
Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientSecretKey().toCharArray())));
store.setSecurityMode(SecurityMode.PSK);
break;
case RPK:
try {
store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndpoint(),
SecurityUtil.publicKey.decode(Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId().toCharArray()))));
store.setSecurityMode(SecurityMode.RPK);
break;
} catch (IOException | GeneralSecurityException e) {
log.error("Unable to decode Client public key for [{}] [{}]", store.getEndpoint(), e.getMessage());
}
case X509:
store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndpoint()));
store.setSecurityMode(SecurityMode.X509);
break;
case NO_SEC:
store.setSecurityMode(SecurityMode.NO_SEC);
store.setSecurityInfo(null);
break;
default:
}
BootstrapConfig bootstrapConfig = lwM2MBootstrapConfig.getLwM2MBootstrapConfig();
store.setBootstrapConfig(bootstrapConfig);
}
@ -149,28 +139,20 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
private LwM2MBootstrapConfig getParametersBootstrap(TbLwM2MSecurityInfo store) {
LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig();
if (lwM2MBootstrapConfig != null) {
BootstrapConfiguration bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile());
lwM2MBootstrapConfig.servers = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getServers()), LwM2MBootstrapServers.class);
LwM2MServerBootstrap profileServerBootstrap = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getBootstrapServer()), LwM2MServerBootstrap.class);
LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class);
UUID sessionUUiD = UUID.randomUUID();
TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits());
bsSessions.put(store.getEndpoint(), sessionInfo);
context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, null, null, sessionInfo, context.getTransportService()));
if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer);
String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LWM2M_INFO, store.getEndpoint());
helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), sessionInfo);
if (this.getValidatedSecurityMode(lwM2MBootstrapConfig)) {
return lwM2MBootstrapConfig;
} else {
log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint());
log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR, store.getEndpoint());
String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR, store.getEndpoint());
String logMsg = String.format("%s: Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR);
helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), sessionInfo);
return null;
}
}
log.error("Unable to decode Json or Certificate for [{}]", store.getEndpoint());
return null;
}
@ -179,14 +161,34 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
* Bootstrap security have to sync between (bootstrapServer in credential and bootstrapServer in profile)
* and (lwm2mServer in credential and lwm2mServer in profile
*
* @param bootstrapFromCredential - Bootstrap -> Security of bootstrapServer in credential
* @param profileServerBootstrap - Bootstrap -> Security of bootstrapServer in profile
* @param lwm2mFromCredential - Bootstrap -> Security of lwm2mServer in credential
* @param profileLwm2mServer - Bootstrap -> Security of lwm2mServer in profile
* @return false if not sync between SecurityMode of Bootstrap credential and profile
*/
private boolean getValidatedSecurityMode(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap, LwM2MServerBootstrap lwm2mFromCredential, LwM2MServerBootstrap profileLwm2mServer) {
return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) &&
lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.getSecurityMode()));
// private boolean getValidatedSecurityMode(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap bootstrapServerProfile, LwM2MServerBootstrap lwm2mFromCredential, LwM2MServerBootstrap profileLwm2mServer) {
private boolean getValidatedSecurityMode(LwM2MBootstrapConfig lwM2MBootstrapConfig) {
LwM2MSecurityMode bootstrapServerSecurityMode = lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode();
LwM2MSecurityMode lwm2mServerSecurityMode = lwM2MBootstrapConfig.getLwm2mServer().getSecurityMode();
AtomicBoolean validBs = new AtomicBoolean(true);
AtomicBoolean validLw = new AtomicBoolean(true);
lwM2MBootstrapConfig.getServerConfiguration().forEach(serverCredential -> {
if (((AbstractLwM2MBootstrapServerCredential)serverCredential).isBootstrapServerIs()) {
if (!bootstrapServerSecurityMode.equals(serverCredential.getSecurityMode())) {
validBs.set(false);
}
}
else {
if (!lwm2mServerSecurityMode.equals(serverCredential.getSecurityMode())) {
validLw.set(false);
}
}
});
return validBs.get()&validLw.get();
}
public TransportProtos.SessionInfoProto getSessionByEndpoint(String endpoint) {
return bsSessions.get(endpoint);
}
public TransportProtos.SessionInfoProto removeSessionByEndpoint(String endpoint) {
return bsSessions.remove(endpoint);
}
}

83
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java

@ -0,0 +1,83 @@
/**
* Copyright © 2016-2021 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.bootstrap.store;
import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
import org.eclipse.leshan.server.bootstrap.ConfigurationChecker;
import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
import java.util.Map;
public class LwM2MConfigurationChecker extends ConfigurationChecker {
@Override
public void verify(BootstrapConfig config) throws InvalidConfigurationException {
// check security configurations
for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> e : config.security.entrySet()) {
BootstrapConfig.ServerSecurity sec = e.getValue();
// checks security config
switch (sec.securityMode) {
case NO_SEC:
checkNoSec(sec);
break;
case PSK:
checkPSK(sec);
break;
case RPK:
checkRPK(sec);
break;
case X509:
checkX509(sec);
break;
case EST:
throw new InvalidConfigurationException("EST is not currently supported.", e);
}
validateMandatoryField(sec);
}
// does each server have a corresponding security entry?
validateOneSecurityByServer(config);
}
protected void validateOneSecurityByServer(BootstrapConfig config) throws InvalidConfigurationException {
for (Map.Entry<Integer, BootstrapConfig.ServerConfig> e : config.servers.entrySet()) {
BootstrapConfig.ServerConfig srvCfg = e.getValue();
// shortId checks
if (srvCfg.shortId == 0) {
throw new InvalidConfigurationException("short ID must not be 0");
}
// look for security entry
BootstrapConfig.ServerSecurity security = getSecurityEntry(config, srvCfg.shortId);
if (security == null) {
throw new InvalidConfigurationException("no security entry for server instance: " + e.getKey());
}
}
}
protected static BootstrapConfig.ServerSecurity getSecurityEntry(BootstrapConfig config, int shortId) {
for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> es : config.security.entrySet()) {
if (es.getValue().serverId == shortId) {
return es.getValue();
}
}
return null;
}
}

40
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java

@ -13,14 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
package org.thingsboard.server.transport.lwm2m.bootstrap.store;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.Identity;
import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
import org.eclipse.leshan.server.bootstrap.BootstrapSession;
import org.eclipse.leshan.server.bootstrap.ConfigurationChecker;
import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore;
import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.server.queue.util.TbLwM2mBootstrapTransportComponent;
import java.util.Map;
import java.util.concurrent.locks.Lock;
@ -29,11 +32,17 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
@Component("LwM2MInMemoryBootstrapConfigStore")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true'&& '${transport.lwm2m.bootstrap.enable}'=='true')")
@TbLwM2mBootstrapTransportComponent
public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigStore {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
protected final ConfigurationChecker configChecker = new LwM2MConfigurationChecker();
@Override
public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
return bootstrapByEndpoint.get(endpoint);
}
@Override
public Map<String, BootstrapConfig> getAll() {
@ -56,16 +65,35 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt
}
@Override
public BootstrapConfig remove(String enpoint) {
public BootstrapConfig remove(String endpoint) {
writeLock.lock();
try {
return super.remove(enpoint);
return super.remove(endpoint);
} finally {
writeLock.unlock();
}
}
public void addToStore(String endpoint, BootstrapConfig config) throws InvalidConfigurationException {
super.add(endpoint, config);
configChecker.verify(config);
// Check PSK identity uniqueness for bootstrap server:
PskByServer pskToAdd = getBootstrapPskIdentity(config);
if (pskToAdd != null) {
BootstrapConfig existingConfig = bootstrapByPskId.get(pskToAdd);
if (existingConfig != null) {
// check if this config will be replace by the new one.
BootstrapConfig previousConfig = bootstrapByEndpoint.get(endpoint);
if (previousConfig != existingConfig) {
throw new InvalidConfigurationException(
"Psk identity [%s] already used for this bootstrap server [%s]", pskToAdd.identity,
pskToAdd.serverUrl);
}
}
}
bootstrapByEndpoint.put(endpoint, config);
if (pskToAdd != null) {
bootstrapByPskId.put(pskToAdd, config);
}
}
}

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java

@ -29,7 +29,7 @@ import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig;
@Slf4j
@Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enabled:false}'=='true'")
public class LwM2MTransportBootstrapConfig implements LwM2MSecureServerConfig {
@Getter

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java

@ -33,7 +33,7 @@ import java.util.List;
@Slf4j
@Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'")
@ConfigurationProperties(prefix = "transport.lwm2m")
public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {

105
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java

@ -17,21 +17,23 @@ package org.thingsboard.server.transport.lwm2m.secure;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.DecoderException;
import org.eclipse.leshan.core.util.SecurityUtil;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MClientCredentials;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer;
@ -47,6 +49,7 @@ import static org.eclipse.leshan.core.SecurityMode.PSK;
import static org.eclipse.leshan.core.SecurityMode.RPK;
import static org.eclipse.leshan.core.SecurityMode.X509;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
@Slf4j
@Component
@ -66,17 +69,16 @@ public class LwM2mCredentialsSecurityInfoValidator {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
log.trace("Validated credentials: [{}] [{}]", credentialsId, msg);
String credentialsBody = msg.getCredentials();
resultSecurityStore[0] = createSecurityInfo(credentialsId, credentialsBody, keyValue);
resultSecurityStore[0].setMsg(msg);
resultSecurityStore[0].setDeviceProfile(msg.getDeviceProfile());
resultSecurityStore[0] = createSecurityInfo(credentialsId, msg, keyValue);
latch.countDown();
}
@Override
public void onError(Throwable e) {
log.trace("[{}] [{}] Failed to process credentials ", credentialsId, e);
resultSecurityStore[0] = createSecurityInfo(credentialsId, null, null);
TbLwM2MSecurityInfo result = new TbLwM2MSecurityInfo();
result.setEndpoint(credentialsId);
resultSecurityStore[0] = result;
latch.countDown();
}
});
@ -87,51 +89,46 @@ public class LwM2mCredentialsSecurityInfoValidator {
}
TbLwM2MSecurityInfo securityInfo = resultSecurityStore[0];
if (securityInfo.getSecurityMode() == null) {
if ((CLIENT.equals(keyValue) && securityInfo.getSecurityMode() == null)) {
throw new LwM2MAuthException();
}
return securityInfo;
}
/**
* Create new SecurityInfo
* @param endpoint -
* @param jsonStr -
* @param keyValue -
*
* @return SecurityInfo
*/
private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTypeServer keyValue) {
private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, ValidateDeviceCredentialsResponse msg, LwM2mTypeServer keyValue) {
TbLwM2MSecurityInfo result = new TbLwM2MSecurityInfo();
LwM2MCredentials credentials = JacksonUtil.fromString(jsonStr, LwM2MCredentials.class);
LwM2MClientCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MClientCredentials.class);
if (credentials != null) {
result.setMsg(msg);
result.setDeviceProfile(msg.getDeviceProfile());
result.setEndpoint(credentials.getClient().getEndpoint());
// if ((keyValue.equals(CLIENT))) {
switch (credentials.getClient().getSecurityConfigClientMode()) {
case NO_SEC:
createClientSecurityInfoNoSec(result);
break;
case PSK:
createClientSecurityInfoPSK(result, endpoint, credentials.getClient());
break;
case RPK:
createClientSecurityInfoRPK(result, endpoint, credentials.getClient());
break;
case X509:
createClientSecurityInfoX509(result, endpoint, credentials.getClient());
break;
default:
break;
}
// } else
if (keyValue.equals(BOOTSTRAP)) {
result.setBootstrapCredentialConfig(credentials.getBootstrap());
if (LwM2MSecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) {
PSKClientCredentials pskClientConfig = (PSKClientCredentials) credentials.getClient();
endpoint = StringUtils.isNotEmpty(pskClientConfig.getEndpoint()) ? pskClientConfig.getEndpoint() : endpoint;
}
result.setEndpoint(endpoint);
result.setSecurityMode(credentials.getBootstrap().getBootstrapServer().getSecurityMode());
} else {
result.setEndpoint(credentials.getClient().getEndpoint());
switch (credentials.getClient().getSecurityConfigClientMode()) {
case NO_SEC:
createClientSecurityInfoNoSec(result);
break;
case PSK:
createClientSecurityInfoPSK(result, endpoint, credentials.getClient());
break;
case RPK:
createClientSecurityInfoRPK(result, endpoint, credentials.getClient());
break;
case X509:
createClientSecurityInfoX509(result, endpoint, credentials.getClient());
break;
default:
break;
}
LwM2MBootstrapConfig bootstrapCredentialConfig = new LwM2MBootstrapConfig(((Lwm2mDeviceProfileTransportConfiguration) msg.getDeviceProfile().getProfileData().getTransportConfiguration()).getBootstrap(),
credentials.getBootstrap().getBootstrapServer(), credentials.getBootstrap().getLwm2mServer());
result.setBootstrapCredentialConfig(bootstrapCredentialConfig);
}
}
return result;
@ -142,18 +139,18 @@ public class LwM2mCredentialsSecurityInfoValidator {
result.setSecurityMode(NO_SEC);
}
private void createClientSecurityInfoPSK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) {
PSKClientCredentials pskConfig = (PSKClientCredentials) clientCredentialsConfig;
private void createClientSecurityInfoPSK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredential clientCredentialsConfig) {
PSKClientCredential pskConfig = (PSKClientCredential) clientCredentialsConfig;
if (StringUtils.isNotEmpty(pskConfig.getIdentity())) {
try {
if (pskConfig.getDecodedKey() != null && pskConfig.getDecodedKey().length > 0) {
if (pskConfig.getDecoded() != null && pskConfig.getDecoded().length > 0) {
endpoint = StringUtils.isNotEmpty(pskConfig.getEndpoint()) ? pskConfig.getEndpoint() : endpoint;
if (endpoint != null && !endpoint.isEmpty()) {
result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getDecodedKey()));
result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getDecoded()));
result.setSecurityMode(PSK);
}
}
} catch (IllegalArgumentException e) {
} catch (IllegalArgumentException | DecoderException e) {
log.error("Missing PSK key: " + e.getMessage());
}
} else {
@ -161,22 +158,22 @@ public class LwM2mCredentialsSecurityInfoValidator {
}
}
private void createClientSecurityInfoRPK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) {
RPKClientCredentials rpkConfig = (RPKClientCredentials) clientCredentialsConfig;
private void createClientSecurityInfoRPK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredential clientCredentialsConfig) {
RPKClientCredential rpkConfig = (RPKClientCredential) clientCredentialsConfig;
try {
if (rpkConfig.getDecodedKey() != null) {
PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getDecodedKey());
if (rpkConfig.getDecoded() != null) {
PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getDecoded());
result.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(endpoint, key));
result.setSecurityMode(RPK);
} else {
log.error("Missing RPK key");
}
} catch (IllegalArgumentException | IOException | GeneralSecurityException e) {
} catch (IllegalArgumentException | IOException | GeneralSecurityException | DecoderException e) {
log.error("RPK: Invalid security info content: " + e.getMessage());
}
}
private void createClientSecurityInfoX509(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) {
private void createClientSecurityInfoX509(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredential clientCredentialsConfig) {
result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint));
result.setSecurityMode(X509);
}

22
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.secure;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.request.Identity;
import org.eclipse.leshan.core.request.UplinkRequest;
import org.eclipse.leshan.server.registration.Registration;
@ -30,6 +31,8 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore;
import java.util.Arrays;
@Component
@RequiredArgsConstructor
@TbLwM2mTransportComponent
@ -57,13 +60,18 @@ public class TbLwM2MAuthorizer implements Authorizer {
}
// If session info is not found, this may be the trusted certificate, so we still need to check all other options below.
}
SecurityInfo expectedSecurityInfo = null;
try {
expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint());
} catch (LwM2MAuthException e) {
log.info("Registration failed: FORBIDDEN, endpointId: [{}]", registration.getEndpoint());
return null;
}
SecurityInfo expectedSecurityInfo;
try {
expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint());
if (expectedSecurityInfo != null && expectedSecurityInfo.usePSK() && expectedSecurityInfo.getEndpoint().equals(SecurityMode.NO_SEC.toString())
&& expectedSecurityInfo.getIdentity().equals(SecurityMode.NO_SEC.toString())
&& Arrays.equals(SecurityMode.NO_SEC.toString().getBytes(), expectedSecurityInfo.getPreSharedKey())) {
expectedSecurityInfo = null;
}
} catch (LwM2MAuthException e) {
log.info("Registration failed: FORBIDDEN, endpointId: [{}]", registration.getEndpoint());
return null;
}
if (securityChecker.checkSecurityInfo(registration.getEndpoint(), senderIdentity, expectedSecurityInfo)) {
return registration;
} else {

61
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java

@ -29,19 +29,21 @@ import org.eclipse.californium.scandium.dtls.HandshakeResultHandler;
import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier;
import org.eclipse.californium.scandium.dtls.x509.StaticCertificateVerifier;
import org.eclipse.californium.scandium.util.ServerNames;
import org.eclipse.leshan.core.util.SecurityUtil;
import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential;
import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.util.SslUtil;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MClientCredentials;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore;
@ -114,24 +116,35 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
cert.checkValidity();
}
TbLwM2MSecurityInfo securityInfo = null;
// verify if trust
if (config.getTrustSslCredentials().getTrustedCertificates().length > 0) {
if (verifyIssuer(cert, config.getTrustSslCredentials().getTrustedCertificates()) != null) {
String endpoint = config.getTrustSslCredentials().getValueFromSubjectNameByKey(cert.getSubjectX500Principal().getName(), "CN");
securityInfo = StringUtils.isNotEmpty(endpoint) ? securityInfoValidator.getEndpointSecurityInfoByCredentialsId(endpoint, CLIENT) : null;
}
}
// if not trust or cert trust securityInfo == null
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
TbLwM2MSecurityInfo securityInfo;
try {
securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, CLIENT);
} catch (LwM2MAuthException e) {
securityInfo = null;
if (securityInfo == null) {
try {
securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, CLIENT);
} catch (LwM2MAuthException e) {
log.trace("Failed find security info: {}", sha3Hash, e);
}
}
ValidateDeviceCredentialsResponse msg = securityInfo != null ? securityInfo.getMsg() : null;
if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) {
LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class);
LwM2MClientCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MClientCredentials.class);
if (!credentials.getClient().getSecurityConfigClientMode().equals(LwM2MSecurityMode.X509)) {
continue;
}
X509ClientCredentials config = (X509ClientCredentials) credentials.getClient();
X509ClientCredential config = (X509ClientCredential) credentials.getClient();
String certBody = config.getCert();
String endpoint = config.getEndpoint();
if (strCert.equals(certBody)) {
if (StringUtils.isBlank(certBody) || strCert.equals(certBody)) {
x509CredentialsFound = true;
DeviceProfile deviceProfile = msg.getDeviceProfile();
if (msg.hasDeviceInfo() && deviceProfile != null) {
@ -179,4 +192,32 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
public void setResultHandler(HandshakeResultHandler resultHandler) {
}
private X509Certificate verifyIssuer(X509Certificate certificate, X509Certificate[] certificates) {
String issuerCN = config.getTrustSslCredentials().getValueFromSubjectNameByKey(certificate.getIssuerX500Principal().getName(), "CN");
if (!StringUtils.isBlank(issuerCN)) {
for (int index = 0; index < certificates.length; ++index) {
X509Certificate trust = certificates[index];
String trustCN = config.getTrustSslCredentials().getValueFromSubjectNameByKey(trust.getSubjectX500Principal().getName(), "CN");
if (!StringUtils.isBlank(trustCN) && issuerCN.length() >= trustCN.length() && issuerCN.substring(issuerCN.length()-trustCN.length()).equals(trustCN)) {
if (verifyCertificate(certificate)) {
return certificate;
}
}
}
}
return null;
}
private static boolean verifyCertificate(X509Certificate certificate) {
try {
// date
certificate.checkValidity();
// Validate X509.
SecurityUtil.certificate.decode(certificate.getEncoded());
return true;
} catch (Exception e) {
return false;
}
}
}

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java

@ -28,12 +28,13 @@ import java.io.Serializable;
@Data
public class TbLwM2MSecurityInfo implements Serializable {
private ValidateDeviceCredentialsResponse msg;
private DeviceProfile deviceProfile;
private String endpoint;
private SecurityInfo securityInfo;
private SecurityMode securityMode;
/** bootstrap */
private DeviceProfile deviceProfile;
private LwM2MBootstrapConfig bootstrapCredentialConfig;
private String endpoint;
private BootstrapConfig bootstrapConfig;
}

6
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentials.java

@ -16,11 +16,11 @@
package org.thingsboard.server.transport.lwm2m.secure.credentials;
import lombok.Data;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig;
@Data
public class LwM2MCredentials {
private LwM2MClientCredentials client;
public class LwM2MClientCredentials {
private LwM2MClientCredential client;
private LwM2MBootstrapConfig bootstrap;
}

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java

@ -16,11 +16,8 @@
package org.thingsboard.server.transport.lwm2m.server;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.server.resources.CoapExchange;
import org.eclipse.leshan.core.californium.LwM2mCoapResource;
import org.thingsboard.server.common.transport.TransportServiceCallback;
@Slf4j
public abstract class AbstractLwM2mTransportResource extends LwM2mCoapResource {

14
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java

@ -19,8 +19,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
@ -34,7 +34,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier;
import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PreDestroy;
@ -44,7 +44,7 @@ import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_UPDATE_COAP_RESOURCE;
@Slf4j
@ -59,7 +59,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
private final OtaPackageDataCache otaPackageDataCache;
private final DefaultLwM2MUplinkMsgHandler handler;
private final DefaultLwM2mUplinkMsgHandler handler;
private final CaliforniumRegistrationStore registrationStore;
private final TbSecurityStore securityStore;
private final TbLwM2MDtlsCertificateVerifier certificateVerifier;
@ -105,9 +105,9 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
LeshanServerBuilder builder = new LeshanServerBuilder();
builder.setLocalAddress(config.getHost(), config.getPort());
builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort());
builder.setDecoder(new DefaultLwM2mNodeDecoder());
builder.setDecoder(new DefaultLwM2mDecoder());
/* Use a magic converter to support bad type send by the UI. */
builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance()));
builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance()));
/* Create CoAP Config */
builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config));

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save