From 8a5a4f0995048ed1d7787bb91bccab135220a7c3 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 28 Apr 2021 12:26:35 +0300 Subject: [PATCH 01/40] Added ability to autogenerate checksum for firmware --- .../server/controller/FirmwareController.java | 13 +++-- .../server/dao/firmware/FirmwareService.java | 4 ++ .../transport/mqtt/MqttTransportHandler.java | 3 -- .../dao/firmware/BaseFirmwareService.java | 48 +++++++++++-------- ui-ngx/src/app/core/http/firmware.service.ts | 10 ++-- .../pages/firmware/firmwares.component.html | 7 +-- .../pages/firmware/firmwares.component.ts | 23 +-------- .../src/app/shared/models/firmware.models.ts | 4 +- 8 files changed, 46 insertions(+), 66 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index c8f10b3d78..9fcf5a7ec9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -115,7 +115,7 @@ public class FirmwareController extends BaseController { @ResponseBody public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId, @RequestParam(required = false) String checksum, - @RequestParam(required = false) String checksumAlgorithm, + @RequestParam() String checksumAlgorithm, @RequestBody MultipartFile file) throws ThingsboardException { checkParameter(FIRMWARE_ID, strFirmwareId); try { @@ -129,18 +129,17 @@ public class FirmwareController extends BaseController { firmware.setVersion(info.getVersion()); firmware.setAdditionalInfo(info.getAdditionalInfo()); - byte[] data = file.getBytes(); - if (StringUtils.isEmpty(checksumAlgorithm)) { - checksumAlgorithm = "sha256"; - checksum = Hashing.sha256().hashBytes(data).toString(); + ByteBuffer data = ByteBuffer.wrap(file.getBytes()); + if (StringUtils.isEmpty(checksum)) { + checksum = firmwareService.generateChecksum(checksumAlgorithm, data); } firmware.setChecksumAlgorithm(checksumAlgorithm); firmware.setChecksum(checksum); firmware.setFileName(file.getOriginalFilename()); firmware.setContentType(file.getContentType()); - firmware.setData(ByteBuffer.wrap(data)); - firmware.setDataSize((long) data.length); + firmware.setData(data); + firmware.setDataSize((long) data.array().length); return firmwareService.saveFirmware(firmware); } catch (Exception e) { throw handleException(e); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java index 0fd26fee6e..0ba34ae90f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java @@ -22,12 +22,16 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import java.nio.ByteBuffer; + public interface FirmwareService { FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo); Firmware saveFirmware(Firmware firmware); + String generateChecksum(String checksumAlgorithm, ByteBuffer data); + Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId); FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 73ca2cdf9d..2b990d6b6b 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -456,9 +456,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement deviceSessionCtx.getPayloadAdaptor() .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk) .ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); - if (firmwareChunk != null && chunkSize != firmwareChunk.length) { - scheduler.schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS); - } } catch (Exception e) { log.trace("[{}] Failed to send firmware response!", sessionId, e); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index 5a80fc75f6..c2b5394ae2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -101,6 +101,32 @@ public class BaseFirmwareService implements FirmwareService { } } + @Override + public String generateChecksum(String checksumAlgorithm, ByteBuffer data) { + + if (data == null || !data.hasArray() || data.array().length == 0) { + throw new DataValidationException("Firmware data should be specified!"); + } + + HashFunction hashFunction; + switch (checksumAlgorithm) { + case "sha256": + hashFunction = Hashing.sha256(); + break; + case "md5": + hashFunction = Hashing.md5(); + break; + case "crc32": + hashFunction = Hashing.crc32(); + break; + default: + throw new DataValidationException("Unknown checksum algorithm!"); + } + + return hashFunction.hashBytes(data.array()).toString(); + } + + @Override public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) { log.trace("Executing findFirmwareById [{}]", firmwareId); @@ -217,11 +243,6 @@ public class BaseFirmwareService implements FirmwareService { throw new DataValidationException("Firmware content type should be specified!"); } - ByteBuffer data = firmware.getData(); - if (data == null || !data.hasArray() || data.array().length == 0) { - throw new DataValidationException("Firmware data should be specified!"); - } - if (StringUtils.isEmpty(firmware.getChecksumAlgorithm())) { throw new DataValidationException("Firmware checksum algorithm should be specified!"); } @@ -229,22 +250,7 @@ public class BaseFirmwareService implements FirmwareService { throw new DataValidationException("Firmware checksum should be specified!"); } - HashFunction hashFunction; - switch (firmware.getChecksumAlgorithm()) { - case "sha256": - hashFunction = Hashing.sha256(); - break; - case "md5": - hashFunction = Hashing.md5(); - break; - case "crc32": - hashFunction = Hashing.crc32(); - break; - default: - throw new DataValidationException("Unknown checksum algorithm!"); - } - - String currentChecksum = hashFunction.hashBytes(data.array()).toString(); + String currentChecksum = generateChecksum(firmware.getChecksumAlgorithm(), firmware.getData()); if (!currentChecksum.equals(firmware.getChecksum())) { throw new DataValidationException("Wrong firmware file!"); diff --git a/ui-ngx/src/app/core/http/firmware.service.ts b/ui-ngx/src/app/core/http/firmware.service.ts index 7bf42fb49e..60d16d6bdf 100644 --- a/ui-ngx/src/app/core/http/firmware.service.ts +++ b/ui-ngx/src/app/core/http/firmware.service.ts @@ -20,7 +20,7 @@ import { PageLink } from '@shared/models/page/page-link'; import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils'; import { Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; -import { Firmware, FirmwareInfo } from '@shared/models/firmware.models'; +import { ChecksumAlgorithm, Firmware, FirmwareInfo } from '@shared/models/firmware.models'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { deepClone, isDefinedAndNotNull } from '@core/utils'; @@ -100,16 +100,16 @@ export class FirmwareService { return this.http.post('/api/firmware', firmware, defaultHttpOptionsFromConfig(config)); } - public uploadFirmwareFile(firmwareId: string, file: File, checksumAlgorithm?: string, + public uploadFirmwareFile(firmwareId: string, file: File, checksumAlgorithm: ChecksumAlgorithm, checksum?: string, config?: RequestConfig): Observable { if (!config) { config = {}; } const formData = new FormData(); formData.append('file', file); - let url = `/api/firmware/${firmwareId}`; - if (checksumAlgorithm && checksum) { - url += `?checksumAlgorithm=${checksumAlgorithm}&checksum=${checksum}`; + let url = `/api/firmware/${firmwareId}?checksumAlgorithm=${checksumAlgorithm}`; + if (checksum) { + url += `&checksum=${checksum}`; } return this.http.post(url, formData, defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest)); diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html index 2911802a75..21a5ad327e 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html @@ -72,7 +72,6 @@ firmware.checksum-algorithm - {{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }} @@ -80,11 +79,7 @@ firmware.checksum - - - {{ 'firmware.checksum-required' | translate }} - +
diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts index 8ffef239b4..5e3c26dfee 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts @@ -23,7 +23,6 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { EntityComponent } from '@home/components/entity/entity.component'; import { ChecksumAlgorithm, ChecksumAlgorithmTranslationMap, Firmware } from '@shared/models/firmware.models'; -import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; import { ActionNotificationShow } from '@core/notification/notification.actions'; @Component({ @@ -45,26 +44,6 @@ export class FirmwaresComponent extends EntityComponent implements OnI super(store, fb, entityValue, entitiesTableConfigValue); } - ngOnInit() { - super.ngOnInit(); - if (this.isAdd) { - this.entityForm.get('checksumAlgorithm').valueChanges.pipe( - map(algorithm => !!algorithm), - distinctUntilChanged(), - takeUntil(this.destroy$) - ).subscribe( - setAlgorithm => { - if (setAlgorithm) { - this.entityForm.get('checksum').setValidators([Validators.maxLength(1020), Validators.required]); - } else { - this.entityForm.get('checksum').clearValidators(); - } - this.entityForm.get('checksum').updateValueAndValidity({emitEvent: false}); - } - ); - } - } - ngOnDestroy() { super.ngOnDestroy(); this.destroy$.next(); @@ -83,7 +62,7 @@ export class FirmwaresComponent extends EntityComponent implements OnI const form = this.fb.group({ title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]], - checksumAlgorithm: [entity ? entity.checksumAlgorithm : null], + checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256], checksum: [entity ? entity.checksum : '', Validators.maxLength(1020)], additionalInfo: this.fb.group( { diff --git a/ui-ngx/src/app/shared/models/firmware.models.ts b/ui-ngx/src/app/shared/models/firmware.models.ts index 65dd1761bf..147901a0cc 100644 --- a/ui-ngx/src/app/shared/models/firmware.models.ts +++ b/ui-ngx/src/app/shared/models/firmware.models.ts @@ -38,8 +38,8 @@ export interface FirmwareInfo extends BaseData { version?: string; hasData?: boolean; fileName: string; - checksum?: ChecksumAlgorithm; - checksumAlgorithm?: string; + checksum?: string; + checksumAlgorithm: ChecksumAlgorithm; contentType: string; dataSize?: number; additionalInfo?: any; From 7a4d302c858a98c5958c0ef516cbaadcc24c6c87 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 29 Apr 2021 09:35:02 +0300 Subject: [PATCH 02/40] Changes according to comments from @ashvayka --- .../server/controller/FirmwareController.java | 10 ++-- .../firmware/DefaultFirmwareStateService.java | 2 +- .../server/dao/firmware/FirmwareService.java | 6 ++- .../server/common/data/FirmwareInfo.java | 3 +- .../data/firmware/ChecksumAlgorithm.java | 26 +++++++++ .../dao/firmware/BaseFirmwareService.java | 54 +++++++++++++++---- .../server/dao/model/sql/FirmwareEntity.java | 6 ++- .../dao/model/sql/FirmwareInfoEntity.java | 8 ++- .../service/BaseDeviceProfileServiceTest.java | 3 +- .../dao/service/BaseDeviceServiceTest.java | 3 +- .../dao/service/BaseFirmwareServiceTest.java | 3 +- .../src/app/shared/models/firmware.models.ts | 16 ++++-- 12 files changed, 115 insertions(+), 25 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index 9fcf5a7ec9..d740d452f3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.controller; -import com.google.common.hash.Hashing; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.core.io.ByteArrayResource; @@ -33,6 +32,7 @@ import org.springframework.web.multipart.MultipartFile; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -49,6 +49,7 @@ import java.nio.ByteBuffer; public class FirmwareController extends BaseController { public static final String FIRMWARE_ID = "firmwareId"; + public static final String CHECKSUM_ALGORITHM = "checksumAlgorithm"; @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')") @RequestMapping(value = "/firmware/{firmwareId}/download", method = RequestMethod.GET) @@ -115,9 +116,10 @@ public class FirmwareController extends BaseController { @ResponseBody public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId, @RequestParam(required = false) String checksum, - @RequestParam() String checksumAlgorithm, + @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, @RequestBody MultipartFile file) throws ThingsboardException { checkParameter(FIRMWARE_ID, strFirmwareId); + checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr); try { FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ); @@ -129,6 +131,8 @@ public class FirmwareController extends BaseController { firmware.setVersion(info.getVersion()); firmware.setAdditionalInfo(info.getAdditionalInfo()); + ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase()); + ByteBuffer data = ByteBuffer.wrap(file.getBytes()); if (StringUtils.isEmpty(checksum)) { checksum = firmwareService.generateChecksum(checksumAlgorithm, data); @@ -139,7 +143,7 @@ public class FirmwareController extends BaseController { firmware.setFileName(file.getOriginalFilename()); firmware.setContentType(file.getContentType()); firmware.setData(data); - firmware.setDataSize((long) data.array().length); + firmware.setDataSize((long) data.capacity()); return firmwareService.saveFirmware(firmware); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java index 6e0e3fd246..58b44a52ce 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -227,7 +227,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_VERSION, firmware.getVersion()))); attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(FIRMWARE_SIZE, firmware.getDataSize()))); - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM_ALGORITHM, firmware.getChecksumAlgorithm()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM_ALGORITHM, firmware.getChecksumAlgorithm().name()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM, firmware.getChecksum()))); telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { @Override diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java index 0ba34ae90f..3edc950300 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java @@ -17,6 +17,8 @@ package org.thingsboard.server.dao.firmware; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -30,7 +32,9 @@ public interface FirmwareService { Firmware saveFirmware(Firmware firmware); - String generateChecksum(String checksumAlgorithm, ByteBuffer data); + PageData getSupportedChecksumAlgorithms(PageLink pageLink); + + String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data) throws ThingsboardException; Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java index 3a26763c4f..b0617f16bd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; @@ -34,7 +35,7 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo private boolean hasData; private String fileName; private String contentType; - private String checksumAlgorithm; + private ChecksumAlgorithm checksumAlgorithm; private String checksum; private Long dataSize; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java b/common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java new file mode 100644 index 0000000000..3998482e35 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.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.common.data.firmware; + +public enum ChecksumAlgorithm { + MD5, + SHA256, + SHA384, + SHA512, + CRC32, + MURMUR3_32, + MURMUR3_128 +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index c2b5394ae2..e3dc4f15cc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -26,6 +26,9 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -35,8 +38,13 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TenantDao; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Optional; import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; @@ -102,7 +110,7 @@ public class BaseFirmwareService implements FirmwareService { } @Override - public String generateChecksum(String checksumAlgorithm, ByteBuffer data) { + public String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data) throws ThingsboardException { if (data == null || !data.hasArray() || data.array().length == 0) { throw new DataValidationException("Firmware data should be specified!"); @@ -110,22 +118,43 @@ public class BaseFirmwareService implements FirmwareService { HashFunction hashFunction; switch (checksumAlgorithm) { - case "sha256": + case MD5: + hashFunction = Hashing.md5(); + break; + case SHA256: hashFunction = Hashing.sha256(); break; - case "md5": - hashFunction = Hashing.md5(); + case SHA384: + hashFunction = Hashing.sha384(); break; - case "crc32": + case SHA512: + hashFunction = Hashing.sha512(); + break; + case CRC32: hashFunction = Hashing.crc32(); break; + case MURMUR3_32: + hashFunction = Hashing.murmur3_32(); + break; + case MURMUR3_128: + hashFunction = Hashing.murmur3_128(); + break; default: throw new DataValidationException("Unknown checksum algorithm!"); } - return hashFunction.hashBytes(data.array()).toString(); } + @Override + public PageData getSupportedChecksumAlgorithms(PageLink pageLink) { + List methodNames = new ArrayList<>(); + Method[] hashingMethods = Hashing.class.getMethods(); + Arrays.stream(hashingMethods).forEach(method -> { + methodNames.add(method.getName()); + }); + return new PageData<>(methodNames, 1, methodNames.size(), false); + } + @Override public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) { @@ -150,7 +179,8 @@ public class BaseFirmwareService implements FirmwareService { } @Override - public PageData findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink) { + public PageData findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, + boolean hasData, PageLink pageLink) { log.trace("Executing findTenantFirmwaresByTenantIdAndHasData, tenantId [{}], hasData [{}] pageLink [{}]", tenantId, hasData, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validatePageLink(pageLink); @@ -243,14 +273,20 @@ public class BaseFirmwareService implements FirmwareService { throw new DataValidationException("Firmware content type should be specified!"); } - if (StringUtils.isEmpty(firmware.getChecksumAlgorithm())) { + if (firmware.getChecksumAlgorithm() == null) { throw new DataValidationException("Firmware checksum algorithm should be specified!"); } if (StringUtils.isEmpty(firmware.getChecksum())) { throw new DataValidationException("Firmware checksum should be specified!"); } - String currentChecksum = generateChecksum(firmware.getChecksumAlgorithm(), firmware.getData()); + String currentChecksum; + + try { + currentChecksum = generateChecksum(firmware.getChecksumAlgorithm(), firmware.getData()); + } catch (ThingsboardException e) { + throw new DataValidationException(e.getMessage()); + } if (!currentChecksum.equals(firmware.getChecksum())) { throw new DataValidationException("Wrong firmware file!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java index ccfa0229bd..8713a33842 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java @@ -21,6 +21,7 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -30,6 +31,8 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Table; import java.nio.ByteBuffer; import java.util.UUID; @@ -68,8 +71,9 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) private String contentType; + @Enumerated(EnumType.STRING) @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN) - private String checksumAlgorithm; + private ChecksumAlgorithm checksumAlgorithm; @Column(name = FIRMWARE_CHECKSUM_COLUMN) private String checksum; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java index 3549291e4b..91cfd1b5e1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java @@ -22,6 +22,7 @@ import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -31,6 +32,8 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Table; import javax.persistence.Transient; import java.util.UUID; @@ -70,8 +73,9 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) private String contentType; + @Enumerated(EnumType.STRING) @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN) - private String checksumAlgorithm; + private ChecksumAlgorithm checksumAlgorithm; @Column(name = FIRMWARE_CHECKSUM_COLUMN) private String checksum; @@ -108,7 +112,7 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S } public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String title, String version, - String fileName, String contentType, String checksumAlgorithm, String checksum, Long dataSize, + String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize, Object additionalInfo, boolean hasData) { this.id = id; this.createdTime = createdTime; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java index e377baa2cb..fd1097d7e1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -101,7 +102,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { firmware.setVersion("v1.0"); firmware.setFileName("test.txt"); firmware.setContentType("text/plain"); - firmware.setChecksumAlgorithm("sha256"); + firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); firmware.setData(ByteBuffer.wrap(new byte[]{1})); Firmware savedFirmware = firmwareService.saveFirmware(firmware); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java index 6d7b695ef1..c08b2748d2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -187,7 +188,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { firmware.setVersion("v1.0"); firmware.setFileName("test.txt"); firmware.setContentType("text/plain"); - firmware.setChecksumAlgorithm("sha256"); + firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); firmware.setData(ByteBuffer.wrap(new byte[]{1})); Firmware savedFirmware = firmwareService.saveFirmware(firmware); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java index bf96d09922..da459efc9b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -42,7 +43,7 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { private static final String FILE_NAME = "filename.txt"; private static final String VERSION = "v1.0"; private static final String CONTENT_TYPE = "text/plain"; - private static final String CHECKSUM_ALGORITHM = "sha256"; + private static final ChecksumAlgorithm CHECKSUM_ALGORITHM = ChecksumAlgorithm.SHA256; private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1}); diff --git a/ui-ngx/src/app/shared/models/firmware.models.ts b/ui-ngx/src/app/shared/models/firmware.models.ts index 147901a0cc..39775395d7 100644 --- a/ui-ngx/src/app/shared/models/firmware.models.ts +++ b/ui-ngx/src/app/shared/models/firmware.models.ts @@ -19,16 +19,24 @@ import { TenantId } from '@shared/models/id/tenant-id'; import { FirmwareId } from '@shared/models/id/firmware-id'; export enum ChecksumAlgorithm { - MD5 = 'md5', - SHA256 = 'sha256', - CRC32 = 'crc32' + MD5 = 'MD5', + SHA256 = 'SHA256', + SHA384 = 'SHA384', + SHA512 = 'SHA512', + CRC32 = 'CRC32', + MURMUR3_32 = 'MURMUR3_32', + MURMUR3_128 = 'MURMUR3_128' } export const ChecksumAlgorithmTranslationMap = new Map( [ [ChecksumAlgorithm.MD5, 'MD5'], [ChecksumAlgorithm.SHA256, 'SHA-256'], - [ChecksumAlgorithm.CRC32, 'CRC-32'] + [ChecksumAlgorithm.SHA384, 'SHA-384'], + [ChecksumAlgorithm.SHA512, 'SHA-512'], + [ChecksumAlgorithm.CRC32, 'CRC-32'], + [ChecksumAlgorithm.MURMUR3_32, 'MURMUR3-32'], + [ChecksumAlgorithm.MURMUR3_128, 'MURMUR3-128'] ] ); From b45d514b8332a893a076edb455d76c00d65dcc99 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 30 Apr 2021 08:53:00 +0300 Subject: [PATCH 03/40] Removed unused method for getting supported checksum algorithms --- .../server/dao/firmware/FirmwareService.java | 2 -- .../server/dao/firmware/BaseFirmwareService.java | 11 ----------- 2 files changed, 13 deletions(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java index 2362197a66..6dbd534228 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java @@ -33,8 +33,6 @@ public interface FirmwareService { Firmware saveFirmware(Firmware firmware); - PageData getSupportedChecksumAlgorithms(PageLink pageLink); - String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data) throws ThingsboardException; Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index 4e1913acfe..b17c01ad47 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -148,17 +148,6 @@ public class BaseFirmwareService implements FirmwareService { return hashFunction.hashBytes(data.array()).toString(); } - @Override - public PageData getSupportedChecksumAlgorithms(PageLink pageLink) { - List methodNames = new ArrayList<>(); - Method[] hashingMethods = Hashing.class.getMethods(); - Arrays.stream(hashingMethods).forEach(method -> { - methodNames.add(method.getName()); - }); - return new PageData<>(methodNames, 1, methodNames.size(), false); - } - - @Override public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) { log.trace("Executing findFirmwareById [{}]", firmwareId); From 7322afac0be4c15be9e47aae8e054367e5a17410 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 5 May 2021 19:09:15 +0300 Subject: [PATCH 04/40] Implementation draft --- .../src/main/resources/thingsboard.yml | 1 + .../TbCoapDtlsCertificateVerifier.java | 1 - ...va => LwM2MTransportBootstrapService.java} | 3 +- .../secure/LwM2MBootstrapSecurityStore.java | 22 +-- .../config/LwM2MTransportServerConfig.java | 4 +- .../lwm2m/secure/EndpointSecurityInfo.java | 5 +- ...LwM2mCredentialsSecurityInfoValidator.java | 10 +- .../lwm2m/secure/TbLwM2MAuthorizer.java | 28 +++ .../TbLwM2MDtlsCertificateVerifier.java | 177 ++++++++++++++++++ .../secure/TbLwM2MDtlsSessionStorage.java | 26 +++ .../server/DefaultLwM2mTransportService.java | 81 +++----- .../server/LwM2mTransportServerHelper.java | 19 +- .../lwm2m/server/client/LwM2mClient.java | 29 +-- .../common/transport/TransportService.java | 2 +- .../service/DefaultTransportService.java | 26 ++- 15 files changed, 326 insertions(+), 108 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/{LwM2MTransportBootstrapServerConfiguration.java => LwM2MTransportBootstrapService.java} (99%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d14adc0573..ae7fbda6d9 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -643,6 +643,7 @@ transport: private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" # Only Certificate_x509: alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" + skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" bootstrap: enable: "${LWM2M_ENABLED_BS:true}" id: "${LWM2M_SERVER_ID_BS:111}" diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java index 1de7bb1693..2076c7a354 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java @@ -145,7 +145,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri @Override public void setResultHandler(HandshakeResultHandler resultHandler) { - // empty implementation } public ConcurrentMap getTbCoapDtlsSessionIdsMap() { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java similarity index 99% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 16bb97aac5..9348cb31a5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -65,7 +65,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g @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')") @RequiredArgsConstructor -public class LwM2MTransportBootstrapServerConfiguration { +//TODO: @ybondarenko please refactor this to be similar to DefaultLwM2mTransportService +public class LwM2MTransportBootstrapService { private PublicKey publicKey; private PrivateKey privateKey; private boolean pskMode = false; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index 0a55d2c3cb..22e0540c09 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -103,7 +103,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { BootstrapConfig bsConfig = store.getBootstrapConfig(); if (bsConfig.security != null) { try { - bootstrapConfigStore.add(store.getEndPoint(), bsConfig); + bootstrapConfigStore.add(store.getEndpoint(), bsConfig); } catch (InvalidConfigurationException e) { log.error("", e); } @@ -121,22 +121,22 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { switch (SecurityMode.valueOf(lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode())) { /* Use RPK only */ case PSK: - store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndPoint(), + store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndpoint(), lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(), Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientSecretKey().toCharArray()))); store.setSecurityMode(SecurityMode.PSK.code); break; case RPK: try { - store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndPoint(), + store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndpoint(), SecurityUtil.publicKey.decode(Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId().toCharArray())))); store.setSecurityMode(SecurityMode.RPK.code); break; } catch (IOException | GeneralSecurityException e) { - log.error("Unable to decode Client public key for [{}] [{}]", store.getEndPoint(), e.getMessage()); + log.error("Unable to decode Client public key for [{}] [{}]", store.getEndpoint(), e.getMessage()); } case X509: - store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndPoint())); + store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndpoint())); store.setSecurityMode(SecurityMode.X509.code); break; case NO_SEC: @@ -166,22 +166,22 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { 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_LW2M_INFO, store.getEndPoint()); + String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndpoint()); helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo); 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_LW2M_ERROR, store.getEndPoint()); - String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint()); + log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint()); + log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint()); + String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint()); helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo); return null; } } } catch (JsonProcessingException e) { - log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndPoint(), e.getMessage()); + log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndpoint(), e.getMessage()); return null; } - log.error("Unable to decode Json or Certificate for [{}]", store.getEndPoint()); + log.error("Unable to decode Json or Certificate for [{}]", store.getEndpoint()); return null; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java index cd8287da4c..109898812b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java @@ -148,9 +148,7 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { keyStoreValue = KeyStore.getInstance(keyStoreType); keyStoreValue.load(inKeyStore, keyStorePassword == null ? null : keyStorePassword.toCharArray()); } catch (Exception e) { - log.warn("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage()); -// Absence of the key store should not block user from using plain LwM2M -// throw new RuntimeException("Failed to lookup LwM2M keystore: " + (uri != null ? uri.toString() : ""), e); + log.info("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage()); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java index 851934a806..f65acf04bd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java @@ -20,19 +20,20 @@ import lombok.Data; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DEFAULT_MODE; @Data public class EndpointSecurityInfo { - private ValidateDeviceCredentialsResponseMsg msg; + private ValidateDeviceCredentialsResponse msg; private SecurityInfo securityInfo; private int securityMode = DEFAULT_MODE.code; /** bootstrap */ private DeviceProfile deviceProfile; private JsonObject bootstrapJsonCredential; - private String endPoint; + private String endpoint; private BootstrapConfig bootstrapConfig; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index 0263e72fb2..ec5200602c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -24,6 +24,7 @@ import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; @@ -59,12 +60,11 @@ public class LwM2mCredentialsSecurityInfoValidator { context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(), new TransportServiceCallback<>() { @Override - public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) { - String credentialsBody = msg.getCredentialsBody(); + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + String credentialsBody = msg.getCredentials(); resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue); resultSecurityStore[0].setMsg(msg); - Optional deviceProfileOpt = LwM2mTransportUtil.decode(msg.getProfileBody().toByteArray()); - deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile)); + resultSecurityStore[0].setDeviceProfile(msg.getDeviceProfile()); latch.countDown(); } @@ -105,7 +105,7 @@ public class LwM2mCredentialsSecurityInfoValidator { if (object != null && !object.isJsonNull()) { if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { result.setBootstrapJsonCredential(object); - result.setEndPoint(endpoint); + result.setEndpoint(endpoint); result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code); } else { LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java new file mode 100644 index 0000000000..3719ce7246 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java @@ -0,0 +1,28 @@ +/** + * 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.secure; + +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.request.UplinkRequest; +import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.security.Authorizer; + +public class TbLwM2MAuthorizer implements Authorizer { + @Override + public Registration isAuthorized(UplinkRequest request, Registration registration, Identity senderIdentity) { + return null; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java new file mode 100644 index 0000000000..c3d9d85d04 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -0,0 +1,177 @@ +/** + * 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.secure; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.elements.util.CertPathUtil; +import org.eclipse.californium.scandium.dtls.AlertMessage; +import org.eclipse.californium.scandium.dtls.CertificateMessage; +import org.eclipse.californium.scandium.dtls.CertificateType; +import org.eclipse.californium.scandium.dtls.CertificateVerificationResult; +import org.eclipse.californium.scandium.dtls.ConnectionId; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.HandshakeException; +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.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.util.SslUtil; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; + +import javax.annotation.PostConstruct; +import javax.security.auth.x500.X500Principal; +import java.security.PublicKey; +import java.security.cert.CertPath; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@RequiredArgsConstructor +public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVerifier { + + private final TransportService transportService; + private final TbLwM2MDtlsSessionStorage sessionStorage; + private final LwM2MTransportServerConfig config; + + @SuppressWarnings("deprecation") + private StaticCertificateVerifier staticCertificateVerifier; + + @Value("${transport.lwm2m.server.security.skip_validity_check_for_client_cert:false}") + private boolean skipValidityCheckForClientCert; + + @Override + public List getSupportedCertificateType() { + return Arrays.asList(CertificateType.X_509, CertificateType.RAW_PUBLIC_KEY); + } + + @PostConstruct + public void init() { + try { + /* by default trust all */ + X509Certificate[] trustedCertificates = new X509Certificate[0]; + if (config.getKeyStoreValue() != null) { + X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias()); + if (rootCAX509Cert != null) { + trustedCertificates = new X509Certificate[1]; + trustedCertificates[0] = rootCAX509Cert; + } + } + staticCertificateVerifier = new StaticCertificateVerifier(trustedCertificates); + } catch (Exception e) { + log.info("Failed to initialize the "); + } + } + + @Override + public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) { + CertPath certChain = message.getCertificateChain(); + if (certChain == null) { + //We trust all RPK on this layer, and use TbLwM2MAuthorizer + PublicKey publicKey = message.getPublicKey(); + return new CertificateVerificationResult(cid, publicKey, null); + } else { + try { + String credentialsBody = null; + CertPath certpath = message.getCertificateChain(); + X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]); + for (X509Certificate cert : chain) { + try { + if (!skipValidityCheckForClientCert) { + cert.checkValidity(); + } + + String strCert = SslUtil.getCertificateString(cert); + String sha3Hash = EncryptionUtil.getSha3Hash(strCert); + final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1]; + CountDownLatch latch = new CountDownLatch(1); + transportService.process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(sha3Hash).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (!StringUtils.isEmpty(msg.getCredentials())) { + deviceCredentialsResponse[0] = msg; + } + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + }); + latch.await(10, TimeUnit.SECONDS); + ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; + if (msg != null && strCert.equals(msg.getCredentials())) { + credentialsBody = msg.getCredentials(); + DeviceProfile deviceProfile = msg.getDeviceProfile(); + if (msg.hasDeviceInfo() && deviceProfile != null) { + String endpoint = sha3Hash; //TODO: extract endpoint from credentials body and push to storage + sessionStorage.put(endpoint, msg); + } + break; + } + } catch (InterruptedException | + CertificateEncodingException | + CertificateExpiredException | + CertificateNotYetValidException e) { + log.error(e.getMessage(), e); + } + } + if (credentialsBody == null) { + if (staticCertificateVerifier != null) { + staticCertificateVerifier.verifyCertificate(message, session); + } else { + AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.INTERNAL_ERROR, + session.getPeer()); + throw new HandshakeException("x509 verification not enabled!", alert); + } + } + return new CertificateVerificationResult(cid, certpath, null); + } catch (HandshakeException e) { + log.trace("Certificate validation failed!", e); + return new CertificateVerificationResult(cid, e, null); + } + } + } + + @Override + public List getAcceptedIssuers() { + return CertPathUtil.toSubjects(null); + } + + @Override + public void setResultHandler(HandshakeResultHandler resultHandler) { + + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java new file mode 100644 index 0000000000..26c38a6327 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.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.transport.lwm2m.secure; + +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; + +public interface TbLwM2MDtlsSessionStorage { + + void put(String endpoint, ValidateDeviceCredentialsResponse msg); + + ValidateDeviceCredentialsResponse get(String endpoint); + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 3df1263a3f..8bd0fdaff2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server; 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.util.Hex; @@ -25,14 +26,14 @@ import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.model.LwM2mModelProvider; -import org.eclipse.leshan.server.security.DefaultAuthorizer; import org.eclipse.leshan.server.security.EditableSecurityStore; -import org.eclipse.leshan.server.security.SecurityChecker; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -41,7 +42,6 @@ import javax.annotation.PreDestroy; import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.KeyFactory; -import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; @@ -70,9 +70,10 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g @RequiredArgsConstructor public class DefaultLwM2mTransportService implements LwM2MTransportService { + public static final CipherSuite[] RPK_OR_X509_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}; + public static final CipherSuite[] PSK_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256}; private PublicKey publicKey; private PrivateKey privateKey; - private boolean pskMode = false; private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; @@ -81,6 +82,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private final CaliforniumRegistrationStore registrationStore; private final EditableSecurityStore securityStore; private final LwM2mClientContext lwM2mClientContext; + private final TbLwM2MDtlsCertificateVerifier certificateVerifier; + private final TbLwM2MAuthorizer authorizer; private LeshanServer server; @@ -128,9 +131,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { config.setModelProvider(modelProvider); builder.setObjectModelProvider(modelProvider); - /* Create credentials */ - this.setServerWithCredentials(builder); - /* Set securityStore with new registrationStore */ builder.setSecurityStore(securityStore); builder.setRegistrationStore(registrationStore); @@ -142,18 +142,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { dtlsConfig.setServerOnly(true); dtlsConfig.setRecommendedSupportedGroupsOnly(config.isRecommendedSupportedGroups()); dtlsConfig.setRecommendedCipherSuitesOnly(config.isRecommendedCiphers()); - if (this.pskMode) { - dtlsConfig.setSupportedCipherSuites( - TLS_PSK_WITH_AES_128_CCM_8, - TLS_PSK_WITH_AES_128_CBC_SHA256); - } else { - dtlsConfig.setSupportedCipherSuites( - TLS_PSK_WITH_AES_128_CCM_8, - TLS_PSK_WITH_AES_128_CBC_SHA256, - TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256); - } - + /* Create credentials */ + this.setServerWithCredentials(builder, dtlsConfig); /* Set DTLS Config */ builder.setDtlsConfig(dtlsConfig); @@ -162,40 +152,21 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { return builder.build(); } - private void setServerWithCredentials(LeshanServerBuilder builder) { - try { - if (config.getKeyStoreValue() != null) { - if (this.setBuilderX509(builder)) { - X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias()); - if (rootCAX509Cert != null) { - X509Certificate[] trustedCertificates = new X509Certificate[1]; - trustedCertificates[0] = rootCAX509Cert; - builder.setTrustedCertificates(trustedCertificates); - } else { - /* by default trust all */ - builder.setTrustedCertificates(new X509Certificate[0]); - } - /* Set securityStore with registrationStore*/ - builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() { - @Override - protected boolean matchX509Identity(String endpoint, String receivedX509CommonName, - String expectedX509CommonName) { - return endpoint.startsWith(expectedX509CommonName); - } - })); - } - } else if (this.setServerRPK(builder)) { - this.infoPramsUri("RPK"); - this.infoParamsServerKey(this.publicKey, this.privateKey); - } else { - /* by default trust all */ - builder.setTrustedCertificates(new X509Certificate[0]); - log.info("Unable to load X509 files for LWM2MServer"); - this.pskMode = true; - this.infoPramsUri("PSK"); - } - } catch (KeyStoreException ex) { - log.error("[{}] Unable to load X509 files server", ex.getMessage()); + private void setServerWithCredentials(LeshanServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) { + if (config.getKeyStoreValue() != null && this.setBuilderX509(builder)) { + dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier); + builder.setAuthorizer(authorizer); + dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES); + } else if (this.setServerRPK(builder)) { + this.infoPramsUri("RPK"); + this.infoParamsServerKey(this.publicKey, this.privateKey); + dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES); + } else { + /* by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + log.info("Unable to load X509 files for LWM2MServer"); + dtlsConfig.setSupportedCipherSuites(PSK_CIPHER_SUITES); + this.infoPramsUri("PSK"); } } @@ -241,7 +212,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private boolean setServerRPK(LeshanServerBuilder builder) { try { - this.generateKeyForRPK(); + this.loadOrGenerateRPKKeys(); if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && this.privateKey != null && this.privateKey.getEncoded().length > 0) { builder.setPublicKey(this.publicKey); @@ -254,7 +225,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { return false; } - private void generateKeyForRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { + private void loadOrGenerateRPKKeys() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { /* Get Elliptic Curve Parameter spec for secp256r1 */ AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); algoParameters.init(new ECGenParameterSpec("secp256r1")); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java index 79902348a9..789faecb75 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java @@ -40,6 +40,7 @@ import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.codec.CodecException; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; @@ -106,21 +107,21 @@ public class LwM2mTransportServerHelper { /** * @return - sessionInfo after access connect client */ - public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) { + public SessionInfoProto getValidateSessionInfo(ValidateDeviceCredentialsResponse msg, long mostSignificantBits, long leastSignificantBits) { return SessionInfoProto.newBuilder() .setNodeId(context.getNodeId()) .setSessionIdMSB(mostSignificantBits) .setSessionIdLSB(leastSignificantBits) - .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) - .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) - .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) - .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) - .setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB()) - .setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB()) + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits()) + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceId().getId().getLeastSignificantBits()) + .setTenantIdMSB(msg.getDeviceInfo().getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(msg.getDeviceInfo().getTenantId().getId().getLeastSignificantBits()) + .setCustomerIdMSB(msg.getDeviceInfo().getCustomerId().getId().getMostSignificantBits()) + .setCustomerIdLSB(msg.getDeviceInfo().getCustomerId().getId().getLeastSignificantBits()) .setDeviceName(msg.getDeviceInfo().getDeviceName()) .setDeviceType(msg.getDeviceInfo().getDeviceType()) - .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) - .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits()) .build(); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 691dd1d390..d20de6ebe4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -27,6 +27,7 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; @@ -79,7 +80,7 @@ public class LwM2mClient implements Cloneable { @Setter private Registration registration; - private ValidateDeviceCredentialsResponseMsg credentialsResponse; + private ValidateDeviceCredentialsResponse credentials; @Getter private final Map resources; @Getter @@ -98,11 +99,11 @@ public class LwM2mClient implements Cloneable { return super.clone(); } - public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileId, UUID sessionId) { + public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponse credentials, UUID profileId, UUID sessionId) { this.endpoint = endpoint; this.identity = identity; this.securityInfo = securityInfo; - this.credentialsResponse = credentialsResponse; + this.credentials = credentials; this.delayedRequests = new ConcurrentHashMap<>(); this.pendingReadRequests = new CopyOnWriteArrayList<>(); this.resources = new ConcurrentHashMap<>(); @@ -112,8 +113,8 @@ public class LwM2mClient implements Cloneable { this.updateFw = false; this.queuedRequests = new ConcurrentLinkedQueue<>(); this.frUpdate = new LwM2mFirmwareUpdate(); - if (this.credentialsResponse != null && this.credentialsResponse.hasDeviceInfo()) { - this.session = createSession(nodeId, sessionId, credentialsResponse); + if (this.credentials != null && this.credentials.hasDeviceInfo()) { + this.session = createSession(nodeId, sessionId, credentials); this.deviceId = new UUID(session.getDeviceIdMSB(), session.getDeviceIdLSB()); this.profileId = new UUID(session.getDeviceProfileIdMSB(), session.getDeviceProfileIdLSB()); this.deviceName = session.getDeviceName(); @@ -146,21 +147,21 @@ public class LwM2mClient implements Cloneable { builder.setDeviceType(this.deviceProfileName); } - private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponseMsg msg) { + private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponse msg) { return SessionInfoProto.newBuilder() .setNodeId(nodeId) .setSessionIdMSB(sessionId.getMostSignificantBits()) .setSessionIdLSB(sessionId.getLeastSignificantBits()) - .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) - .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) - .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) - .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) - .setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB()) - .setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB()) + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits()) + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceId().getId().getLeastSignificantBits()) + .setTenantIdMSB(msg.getDeviceInfo().getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(msg.getDeviceInfo().getTenantId().getId().getLeastSignificantBits()) + .setCustomerIdMSB(msg.getDeviceInfo().getCustomerId().getId().getMostSignificantBits()) + .setCustomerIdLSB(msg.getDeviceInfo().getCustomerId().getId().getLeastSignificantBits()) .setDeviceName(msg.getDeviceInfo().getDeviceName()) .setDeviceType(msg.getDeviceInfo().getDeviceType()) - .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) - .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits()) .build(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 4cdf6246e1..ce3bb7345e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -79,7 +79,7 @@ public interface TransportService { TransportServiceCallback callback); void process(ValidateDeviceLwM2MCredentialsRequestMsg msg, - TransportServiceCallback callback); + TransportServiceCallback callback); void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 69de33c8c0..b09b6bf223 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -348,11 +348,25 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg msg, TransportServiceCallback callback) { - log.trace("Processing msg: {}", msg); - TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(msg).build()); - AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), - response -> callback.onSuccess(response.getValue().getValidateCredResponseMsg()), callback::onError, transportCallbackExecutor); + public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg requestMsg, TransportServiceCallback callback) { + log.trace("Processing msg: {}", requestMsg); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(requestMsg).build()); + ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { + TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); + ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder(); + if (msg.hasDeviceInfo()) { + result.credentials(msg.getCredentialsBody()); + TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); + result.deviceInfo(tdi); + ByteString profileBody = msg.getProfileBody(); + if (!profileBody.isEmpty()) { + DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); + result.deviceProfile(profile); + } + } + return result.build(); + }, MoreExecutors.directExecutor()); + AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } @Override @@ -372,7 +386,7 @@ public class DefaultTransportService implements TransportService { TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); result.deviceInfo(tdi); ByteString profileBody = msg.getProfileBody(); - if (profileBody != null && !profileBody.isEmpty()) { + if (!profileBody.isEmpty()) { DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); if (transportType != DeviceTransportType.DEFAULT && profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) { From a4eb2df8eee8aecccbccfbb27f7e27f19d8fa0fc Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 6 May 2021 13:46:04 +0300 Subject: [PATCH 05/40] Fix for device software/firmware response topic --- .../server/common/data/device/profile/MqttTopics.java | 3 ++- .../server/transport/mqtt/adaptors/JsonMqttAdaptor.java | 4 ++-- .../server/transport/mqtt/adaptors/ProtoMqttAdaptor.java | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 6373d41803..2b1aba3c0f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -82,7 +82,8 @@ public class MqttTopics { public static final String DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN; public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC; public static final String DEVICE_FIRMWARE_ERROR_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + ERROR; - public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT = BASE_DEVICE_API_TOPIC_V2 + "%s" + RESPONSE + "/"+ "%s" + CHUNK + "%d"; + + public static final String DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT = BASE_DEVICE_API_TOPIC_V2 + "/%s" + RESPONSE + "/%s" + CHUNK + "%d"; public static final String DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN; public static final String DEVICE_SOFTWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index dbda48d15d..a9d7b3e2ea 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -44,7 +44,7 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; -import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT; +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT; /** @@ -156,7 +156,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { @Override public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, FirmwareType firmwareType) { - return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk)); + return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk)); } public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index 29df08e9c3..08a2f9abe3 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -39,7 +39,7 @@ import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionConte import java.util.Optional; -import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT; +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT; @Component @Slf4j @@ -169,7 +169,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @Override public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, FirmwareType firmwareType) throws AdaptorException { - return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk)); + return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk)); } @Override From 38af4d5d2da99804c2aebe829e9c6aeb9b786701 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 6 May 2021 13:48:09 +0300 Subject: [PATCH 06/40] Implementation of custom L2M2M Authorizer --- .../lwm2m/secure/TbLwM2MAuthorizer.java | 41 ++++++++++++++++++- .../TbLwM2MDtlsCertificateVerifier.java | 31 +++++++++----- ...torage.java => TbX509DtlsSessionInfo.java} | 9 ++-- .../server/client/LwM2mClientContext.java | 3 ++ .../server/client/LwM2mClientContextImpl.java | 8 ++++ .../TbL2M2MDtlsSessionInMemoryStore.java | 39 ++++++++++++++++++ .../server/store/TbLwM2MDtlsSessionStore.java | 29 +++++++++++++ .../server/store/TbLwM2mSecurityStore.java | 4 ++ 8 files changed, 149 insertions(+), 15 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/{TbLwM2MDtlsSessionStorage.java => TbX509DtlsSessionInfo.java} (81%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java index 3719ce7246..0236f6d276 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java @@ -15,14 +15,53 @@ */ package org.thingsboard.server.transport.lwm2m.secure; +import lombok.RequiredArgsConstructor; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.request.UplinkRequest; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.Authorizer; +import org.eclipse.leshan.server.security.SecurityChecker; +import org.eclipse.leshan.server.security.SecurityInfo; +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +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.TbLwM2mSecurityStore; +@Component +@RequiredArgsConstructor +@TbLwM2mTransportComponent public class TbLwM2MAuthorizer implements Authorizer { + + private final TbLwM2MDtlsSessionStore sessionStorage; + private final TbLwM2mSecurityStore securityStore; + private final SecurityChecker securityChecker = new SecurityChecker(); + private final LwM2mClientContext clientContext; + @Override public Registration isAuthorized(UplinkRequest request, Registration registration, Identity senderIdentity) { - return null; + if (senderIdentity.isX509()) { + TbX509DtlsSessionInfo sessionInfo = sessionStorage.get(registration.getEndpoint()); + if (sessionInfo != null) { + if (senderIdentity.getX509CommonName().equals(sessionInfo.getX509CommonName())) { + clientContext.registerClient(registration, sessionInfo.getCredentials()); + // X509 certificate is valid and matches endpoint. + return registration; + } else { + // X509 certificate is not valid. + return null; + } + } + // 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; + if (securityStore != null) { + expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); + } + if (securityChecker.checkSecurityInfo(registration.getEndpoint(), senderIdentity, expectedSecurityInfo)) { + return registration; + } else { + return null; + } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index c3d9d85d04..57fa8a7169 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.secure; +import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.util.CertPathUtil; @@ -32,6 +33,7 @@ import org.eclipse.californium.scandium.util.ServerNames; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.TransportService; @@ -40,6 +42,7 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import javax.annotation.PostConstruct; import javax.security.auth.x500.X500Principal; @@ -60,7 +63,7 @@ import java.util.concurrent.TimeUnit; public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVerifier { private final TransportService transportService; - private final TbLwM2MDtlsSessionStorage sessionStorage; + private final TbLwM2MDtlsSessionStore sessionStorage; private final LwM2MTransportServerConfig config; @SuppressWarnings("deprecation") @@ -130,16 +133,24 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer latch.countDown(); } }); - latch.await(10, TimeUnit.SECONDS); - ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; - if (msg != null && strCert.equals(msg.getCredentials())) { - credentialsBody = msg.getCredentials(); - DeviceProfile deviceProfile = msg.getDeviceProfile(); - if (msg.hasDeviceInfo() && deviceProfile != null) { - String endpoint = sha3Hash; //TODO: extract endpoint from credentials body and push to storage - sessionStorage.put(endpoint, msg); + if (latch.await(10, TimeUnit.SECONDS)) { + ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; + if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { + JsonNode credentialsJson = JacksonUtil.toJsonNode(msg.getCredentials()); + String certBody = credentialsJson.get("cert").asText(); + String endpoint = credentialsJson.get("endpoint").asText(); + if (strCert.equals(certBody)) { + //TODO: extract endpoint from credentials body and push to storage + credentialsBody = msg.getCredentials(); + DeviceProfile deviceProfile = msg.getDeviceProfile(); + if (msg.hasDeviceInfo() && deviceProfile != null) { + sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); + break; + } + } else { + log.trace("[{}][{}] Certificate mismatch. Expected: {}, Actual: {}", endpoint, sha3Hash, strCert, certBody); + } } - break; } } catch (InterruptedException | CertificateEncodingException | diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java similarity index 81% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java index 26c38a6327..1c038a9440 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java @@ -15,12 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.secure; +import lombok.Data; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -public interface TbLwM2MDtlsSessionStorage { +@Data +public class TbX509DtlsSessionInfo { - void put(String endpoint, ValidateDeviceCredentialsResponse msg); - - ValidateDeviceCredentialsResponse get(String endpoint); + private final String x509CommonName; + private final ValidateDeviceCredentialsResponse credentials; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java index 8e7f6a70ec..4aa8db59b6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Collection; @@ -59,4 +60,6 @@ public interface LwM2mClientContext { Set getSupportedIdVerInClient(Registration registration); LwM2mClient getClientByDeviceId(UUID deviceId); + + void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index 3a7b652ab3..b8583523d5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -21,6 +21,7 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.EditableSecurityStore; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; @@ -136,6 +137,13 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } } + @Override + public void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials) { + LwM2mClient client = new LwM2mClient(context.getNodeId(), registration.getEndpoint(), null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID()); + lwM2mClientsByEndpoint.put(registration.getEndpoint(), client); + lwM2mClientsByRegistrationId.put(registration.getId(), client); + } + @Override public Collection getLwM2mClients() { return lwM2mClientsByEndpoint.values(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java new file mode 100644 index 0000000000..b71b7b12c1 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java @@ -0,0 +1,39 @@ +/** + * 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.server.store; + +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; + +import java.util.concurrent.ConcurrentHashMap; + +@Component +@TbLwM2mTransportComponent +public class TbL2M2MDtlsSessionInMemoryStore implements TbLwM2MDtlsSessionStore { + + private final ConcurrentHashMap store = new ConcurrentHashMap<>(); + + @Override + public void put(String endpoint, TbX509DtlsSessionInfo msg) { + store.put(endpoint, msg); + } + + @Override + public TbX509DtlsSessionInfo get(String endpoint) { + return store.get(endpoint); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java new file mode 100644 index 0000000000..bc4ddff7ac --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.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.transport.lwm2m.server.store; + + +import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; + +public interface TbLwM2MDtlsSessionStore { + + void put(String endpoint, TbX509DtlsSessionInfo msg); + + TbX509DtlsSessionInfo get(String endpoint); + + //TODO: add way to delete the session by endpoint. + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java index e6ed99c5e6..512790036f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java @@ -20,12 +20,16 @@ import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityStoreListener; +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import java.util.Collection; @Slf4j +@Component +@TbLwM2mTransportComponent public class TbLwM2mSecurityStore implements EditableSecurityStore { private final LwM2mClientContext clientContext; From 7ca626a0868422018a8489dce417b02ee31ef4c5 Mon Sep 17 00:00:00 2001 From: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Thu, 6 May 2021 13:49:51 +0300 Subject: [PATCH 07/40] Created LwM2M credentials (#4546) * Created LwM2M credentials * psk endpoint --- .../server/controller/Lwm2mController.java | 5 +- .../LwM2MServerSecurityInfoRepository.java | 11 +- .../secure/LwM2MBootstrapConfig.java | 12 +- .../secure/LwM2MBootstrapSecurityStore.java | 20 ++-- .../secure/LwM2MServerBootstrap.java | 2 +- .../lwm2m/secure/EndpointSecurityInfo.java | 10 +- .../secure/LWM2MGenerationPSkRPkECC.java | 22 +--- .../lwm2m/secure/LwM2MSecurityMode.java | 58 --------- ...LwM2mCredentialsSecurityInfoValidator.java | 111 ++++++++---------- .../lwm2m/secure/credentials/HasKey.java | 17 +++ .../LwM2MClientCredentialsConfig.java | 22 ++++ .../secure/credentials/LwM2MCredentials.java | 10 ++ .../NoSecClientCredentialsConfig.java | 13 ++ .../PSKClientCredentialsConfig.java | 17 +++ .../RPKClientCredentialsConfig.java | 13 ++ .../X509ClientCredentialsConfig.java | 17 +++ .../server/client/LwM2mClientContextImpl.java | 7 +- 17 files changed, 199 insertions(+), 168 deletions(-) delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java diff --git a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java index 0855e4ee1f..9e6d393b30 100644 --- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.SecurityMode; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -46,9 +47,11 @@ public class Lwm2mController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET) @ResponseBody - public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode, + public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String strSecurityMode, @PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException { + checkNotNull(strSecurityMode); try { + SecurityMode securityMode = SecurityMode.valueOf(strSecurityMode); return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java index 012cd3e359..06190cdf70 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.lwm2m; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.Hex; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; @@ -25,7 +26,6 @@ import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; 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 org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import java.math.BigInteger; import java.security.AlgorithmParameters; @@ -55,17 +55,16 @@ public class LwM2MServerSecurityInfoRepository { * @param bootstrapServer * @return ServerSecurityConfig more value is default: Important - port, host, publicKey */ - public ServerSecurityConfig getServerSecurityInfo(String securityMode, boolean bootstrapServer) { - LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(securityMode.toLowerCase()); - ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, lwM2MSecurityMode); + public ServerSecurityConfig getServerSecurityInfo(SecurityMode securityMode, boolean bootstrapServer) { + ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, securityMode); result.setBootstrapServerIs(bootstrapServer); return result; } - private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, LwM2MSecurityMode mode) { + private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, SecurityMode securityMode) { ServerSecurityConfig bsServ = new ServerSecurityConfig(); bsServ.setServerId(serverConfig.getId()); - switch (mode) { + switch (securityMode) { case NO_SEC: bsServ.setHost(serverConfig.getHost()); bsServ.setPort(serverConfig.getPort()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java index 937257b189..2f175a6bce 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java @@ -73,17 +73,17 @@ public class LwM2MBootstrapConfig { 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, setServerSecuruty(this.bootstrapServer.getHost(), this.bootstrapServer.getPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId())); + configBs.security.put(0, setServerSecurity(this.bootstrapServer.getHost(), this.bootstrapServer.getPort(), 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, setServerSecuruty(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId())); + configBs.security.put(1, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId())); return configBs; } - private BootstrapConfig.ServerSecurity setServerSecuruty(String host, Integer port, boolean bootstrapServer, String securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) { + private BootstrapConfig.ServerSecurity setServerSecurity(String host, Integer port, boolean bootstrapServer, SecurityMode securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) { BootstrapConfig.ServerSecurity serverSecurity = new BootstrapConfig.ServerSecurity(); serverSecurity.uri = "coaps://" + host + ":" + Integer.toString(port); serverSecurity.bootstrapServer = bootstrapServer; - serverSecurity.securityMode = SecurityMode.valueOf(securityMode); + 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[]{}; @@ -91,9 +91,9 @@ public class LwM2MBootstrapConfig { return serverSecurity; } - private byte[] setPublicKeyOrId(String publicKeyOrIdStr, String securityMode) { + private byte[] setPublicKeyOrId(String publicKeyOrIdStr, SecurityMode securityMode) { return (publicKeyOrIdStr == null || publicKeyOrIdStr.isEmpty()) ? new byte[]{} : - SecurityMode.valueOf(securityMode).equals(SecurityMode.PSK) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) : + SecurityMode.PSK.equals(securityMode) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) : Hex.decodeHex(publicKeyOrIdStr.toCharArray()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index 22e0540c09..b1edd0a1a5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -31,7 +31,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; @@ -73,7 +72,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public List getAllByEndpoint(String endPoint) { EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); - if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfigNew = store.getBootstrapConfig(); @@ -97,7 +96,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); - if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfig = store.getBootstrapConfig(); @@ -118,29 +117,29 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store); if (lwM2MBootstrapConfig != null) { /* Security info */ - switch (SecurityMode.valueOf(lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode())) { + 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.code); + 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.code); + 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.code); + store.setSecurityMode(SecurityMode.X509); break; case NO_SEC: - store.setSecurityMode(SecurityMode.NO_SEC.code); + store.setSecurityMode(SecurityMode.NO_SEC); store.setSecurityInfo(null); break; default: @@ -152,10 +151,9 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private LwM2MBootstrapConfig getParametersBootstrap(EndpointSecurityInfo store) { try { - JsonObject bootstrapJsonCredential = store.getBootstrapJsonCredential(); - if (bootstrapJsonCredential != null) { + LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig(); + if (lwM2MBootstrapConfig != null) { ObjectMapper mapper = new ObjectMapper(); - LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class); JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class); LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java index 9dca6057da..27d2e8c865 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java @@ -32,7 +32,7 @@ public class LwM2MServerBootstrap { String host = "0.0.0.0"; Integer port = 0; - String securityMode = SecurityMode.NO_SEC.name(); + SecurityMode securityMode = SecurityMode.NO_SEC; Integer serverId = 123; boolean bootstrapServerIs = false; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java index f65acf04bd..e8d3ae3c2b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java @@ -15,25 +15,23 @@ */ package org.thingsboard.server.transport.lwm2m.secure; -import com.google.gson.JsonObject; import lombok.Data; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; - -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DEFAULT_MODE; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; @Data public class EndpointSecurityInfo { private ValidateDeviceCredentialsResponse msg; private SecurityInfo securityInfo; - private int securityMode = DEFAULT_MODE.code; + private SecurityMode securityMode; /** bootstrap */ private DeviceProfile deviceProfile; - private JsonObject bootstrapJsonCredential; + private LwM2MBootstrapConfig bootstrapCredentialConfig; private String endpoint; private BootstrapConfig bootstrapConfig; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java index b99192ece3..22c5878a58 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java @@ -33,16 +33,6 @@ import java.util.Arrays; @Slf4j public class LWM2MGenerationPSkRPkECC { - public LWM2MGenerationPSkRPkECC(Integer dtlsMode) { - switch (LwM2MSecurityMode.fromSecurityMode(dtlsMode)) { - case PSK: - generationPSkKey(); - break; - case RPK: - generationRPKECCKey(); - } - } - public LWM2MGenerationPSkRPkECC() { generationPSkKey(); generationRPKECCKey(); @@ -102,12 +92,12 @@ public class LWM2MGenerationPSkRPkECC { /* Get Curves params */ String privHex = Hex.encodeHexString(privKey.getEncoded()); log.info("\nCreating new RPK for the next start... \n" + - " Public Key (Hex): [{}]\n" + - " Private Key (Hex): [{}]" + - " public_x : [{}] \n" + - " public_y : [{}] \n" + - " private_encode : [{}] \n" + - " Elliptic Curve parameters : [{}] \n", + " Public Key (Hex): [{}]\n" + + " Private Key (Hex): [{}]" + + " public_x : [{}] \n" + + " public_y : [{}] \n" + + " private_encode : [{}] \n" + + " Elliptic Curve parameters : [{}] \n", Hex.encodeHexString(pubKey.getEncoded()), privHex, Hex.encodeHexString(x), diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java deleted file mode 100644 index faf776b76c..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java +++ /dev/null @@ -1,58 +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.secure; - -public enum LwM2MSecurityMode { - - PSK(0, "psk"), - RPK(1, "rpk"), - X509(2, "x509"), - NO_SEC(3, "no_sec"), - X509_EST(4, "x509_est"), - REDIS(7, "redis"), - DEFAULT_MODE(255, "default_mode"); - - public int code; - public String subEndpoint; - - LwM2MSecurityMode(int code, String subEndpoint) { - this.code = code; - this.subEndpoint = subEndpoint; - } - - public static LwM2MSecurityMode fromSecurityMode(long code) { - return fromSecurityMode((int) code); - } - - public static LwM2MSecurityMode fromSecurityMode(int code) { - for (LwM2MSecurityMode sm : LwM2MSecurityMode.values()) { - if (sm.code == code) { - return sm; - } - } - throw new IllegalArgumentException(String.format("Unsupported security code : %d", code)); - } - - - public static LwM2MSecurityMode fromSecurityMode(String subEndpoint) { - for (LwM2MSecurityMode sm : LwM2MSecurityMode.values()) { - if (sm.subEndpoint.equals(subEndpoint)) { - return sm; - } - } - throw new IllegalArgumentException(String.format("Unsupported security subEndpoint : %d", subEndpoint)); - } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index ec5200602c..cd78cd4c1e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -15,34 +15,36 @@ */ package org.thingsboard.server.transport.lwm2m.secure; -import com.google.gson.JsonObject; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MClientCredentialsConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.PSKClientCredentialsConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.RPKClientCredentialsConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.PublicKey; -import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; +import static org.eclipse.leshan.core.SecurityMode.NO_SEC; +import static org.eclipse.leshan.core.SecurityMode.PSK; +import static org.eclipse.leshan.core.SecurityMode.RPK; +import static org.eclipse.leshan.core.SecurityMode.X509; @Slf4j @Component @@ -53,7 +55,6 @@ public class LwM2mCredentialsSecurityInfoValidator { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; - public EndpointSecurityInfo getEndpointSecurityInfo(String endpoint, LwM2mTransportUtil.LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final EndpointSecurityInfo[] resultSecurityStore = new EndpointSecurityInfo[1]; @@ -92,39 +93,32 @@ public class LwM2mCredentialsSecurityInfoValidator { */ private EndpointSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) { EndpointSecurityInfo result = new EndpointSecurityInfo(); - JsonObject objectMsg = LwM2mTransportUtil.validateJson(jsonStr); - if (objectMsg != null && !objectMsg.isJsonNull()) { - JsonObject object = (objectMsg.has(keyValue.type) && !objectMsg.get(keyValue.type).isJsonNull()) ? objectMsg.get(keyValue.type).getAsJsonObject() : null; - /** - * Only PSK - */ - String endpointPsk = (objectMsg.has("client") - && objectMsg.get("client").getAsJsonObject().has("endpoint") - && objectMsg.get("client").getAsJsonObject().get("endpoint").isJsonPrimitive()) ? objectMsg.get("client").getAsJsonObject().get("endpoint").getAsString() : null; - endpoint = (endpointPsk == null || endpointPsk.isEmpty()) ? endpoint : endpointPsk; - if (object != null && !object.isJsonNull()) { - if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { - result.setBootstrapJsonCredential(object); - result.setEndpoint(endpoint); - result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code); - } else { - LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase()); - switch (lwM2MSecurityMode) { - case NO_SEC: - createClientSecurityInfoNoSec(result); - break; - case PSK: - createClientSecurityInfoPSK(result, endpoint, object); - break; - case RPK: - createClientSecurityInfoRPK(result, endpoint, object); - break; - case X509: - createClientSecurityInfoX509(result, endpoint); - break; - default: - break; - } + LwM2MCredentials credentials = JacksonUtil.fromString(jsonStr, LwM2MCredentials.class); + if (credentials != null) { + if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { + result.setBootstrapCredentialConfig(credentials.getBootstrap()); + if (SecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) { + PSKClientCredentialsConfig pskClientConfig = (PSKClientCredentialsConfig) credentials.getClient(); + endpoint = StringUtils.isNotEmpty(pskClientConfig.getEndpoint()) ? pskClientConfig.getEndpoint() : endpoint; + } + result.setEndpoint(endpoint); + result.setSecurityMode(credentials.getBootstrap().getBootstrapServer().getSecurityMode()); + } else { + 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; } } } @@ -133,19 +127,18 @@ public class LwM2mCredentialsSecurityInfoValidator { private void createClientSecurityInfoNoSec(EndpointSecurityInfo result) { result.setSecurityInfo(null); - result.setSecurityMode(NO_SEC.code); + result.setSecurityMode(NO_SEC); } - private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, JsonObject object) { - /** PSK Deserialization */ - String identity = (object.has("identity") && object.get("identity").isJsonPrimitive()) ? object.get("identity").getAsString() : null; - if (identity != null && !identity.isEmpty()) { + private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { + PSKClientCredentialsConfig pskConfig = (PSKClientCredentialsConfig) clientCredentialsConfig; + if (StringUtils.isNotEmpty(pskConfig.getIdentity())) { try { - byte[] key = (object.has("key") && object.get("key").isJsonPrimitive()) ? Hex.decodeHex(object.get("key").getAsString().toCharArray()) : null; - if (key != null && key.length > 0) { + if (pskConfig.getKey() != null && pskConfig.getKey().length > 0) { + endpoint = StringUtils.isNotEmpty(pskConfig.getEndpoint()) ? pskConfig.getEndpoint() : endpoint; if (endpoint != null && !endpoint.isEmpty()) { - result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, identity, key)); - result.setSecurityMode(PSK.code); + result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getKey())); + result.setSecurityMode(PSK); } } } catch (IllegalArgumentException e) { @@ -156,13 +149,13 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, JsonObject object) { + private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { + RPKClientCredentialsConfig rpkConfig = (RPKClientCredentialsConfig) clientCredentialsConfig; try { - if (object.has("key") && object.get("key").isJsonPrimitive()) { - byte[] rpkkey = Hex.decodeHex(object.get("key").getAsString().toLowerCase().toCharArray()); - PublicKey key = SecurityUtil.publicKey.decode(rpkkey); + if (rpkConfig.getKey() != null) { + PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getKey()); result.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(endpoint, key)); - result.setSecurityMode(RPK.code); + result.setSecurityMode(RPK); } else { log.error("Missing RPK key"); } @@ -171,8 +164,8 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint) { + private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); - result.setSecurityMode(X509.code); + result.setSecurityMode(X509); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java new file mode 100644 index 0000000000..7c75589c4c --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import org.eclipse.leshan.core.util.Hex; + +public class HasKey { + private byte[] key; + + public void setKey(String key) { + if (key != null) { + this.key = Hex.decodeHex(key.toLowerCase().toCharArray()); + } + } + + public byte[] getKey() { + return key; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java new file mode 100644 index 0000000000..33a297c91a --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java @@ -0,0 +1,22 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +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.eclipse.leshan.core.SecurityMode; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "securityConfigClientMode") +@JsonSubTypes({ + @JsonSubTypes.Type(value = NoSecClientCredentialsConfig.class, name = "NO_SEC"), + @JsonSubTypes.Type(value = PSKClientCredentialsConfig.class, name = "PSK"), + @JsonSubTypes.Type(value = RPKClientCredentialsConfig.class, name = "RPK"), + @JsonSubTypes.Type(value = X509ClientCredentialsConfig.class, name = "X509")}) +public interface LwM2MClientCredentialsConfig { + + @JsonIgnore + SecurityMode getSecurityConfigClientMode(); +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java new file mode 100644 index 0000000000..ebb1fc138b --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java @@ -0,0 +1,10 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import lombok.Data; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; + +@Data +public class LwM2MCredentials { + private LwM2MClientCredentialsConfig client; + private LwM2MBootstrapConfig bootstrap; +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java new file mode 100644 index 0000000000..bcac7174aa --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java @@ -0,0 +1,13 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.NO_SEC; + +public class NoSecClientCredentialsConfig implements LwM2MClientCredentialsConfig { + + @Override + public SecurityMode getSecurityConfigClientMode() { + return NO_SEC; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java new file mode 100644 index 0000000000..d79083647f --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import lombok.Data; +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.PSK; + +@Data +public class PSKClientCredentialsConfig extends HasKey implements LwM2MClientCredentialsConfig { + private String identity; + private String endpoint; + + @Override + public SecurityMode getSecurityConfigClientMode() { + return PSK; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java new file mode 100644 index 0000000000..280e8492f8 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java @@ -0,0 +1,13 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.RPK; + +public class RPKClientCredentialsConfig extends HasKey implements LwM2MClientCredentialsConfig { + + @Override + public SecurityMode getSecurityConfigClientMode() { + return RPK; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java new file mode 100644 index 0000000000..35fe63c376 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import lombok.Data; +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.X509; + +@Data +public class X509ClientCredentialsConfig implements LwM2MClientCredentialsConfig { + private boolean allowTrustedOnly; + private String cert; + + @Override + public SecurityMode getSecurityConfigClientMode() { + return X509; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index b8583523d5..02ef1ccfb2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -25,7 +25,6 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -38,7 +37,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; +import static org.eclipse.leshan.core.SecurityMode.NO_SEC; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @Service @@ -111,7 +110,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public LwM2mClient fetchClientByEndpoint(String endpoint) { EndpointSecurityInfo securityInfo = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endpoint, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); - if (securityInfo.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + if (securityInfo.getSecurityMode() != null) { if (securityInfo.getDeviceProfile() != null) { toClientProfile(securityInfo.getDeviceProfile()); UUID profileUuid = securityInfo.getDeviceProfile().getUuidId(); @@ -120,7 +119,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { client = new LwM2mClient(context.getNodeId(), securityInfo.getSecurityInfo().getEndpoint(), securityInfo.getSecurityInfo().getIdentity(), securityInfo.getSecurityInfo(), securityInfo.getMsg(), profileUuid, UUID.randomUUID()); - } else if (securityInfo.getSecurityMode() == NO_SEC.code) { + } else if (NO_SEC.equals(securityInfo.getSecurityMode())) { client = new LwM2mClient(context.getNodeId(), endpoint, null, null, securityInfo.getMsg(), profileUuid, UUID.randomUUID()); From 38843c839c18c294c8526bfc665e7b5f59333cc8 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 6 May 2021 14:07:29 +0300 Subject: [PATCH 08/40] Merge with the new data structures --- .../TbLwM2MDtlsCertificateVerifier.java | 20 ++++++++++++------- .../X509ClientCredentialsConfig.java | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index 57fa8a7169..d2542192bf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -30,6 +30,7 @@ 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.SecurityMode; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -42,6 +43,8 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; 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.X509ClientCredentialsConfig; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import javax.annotation.PostConstruct; @@ -104,7 +107,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer return new CertificateVerificationResult(cid, publicKey, null); } else { try { - String credentialsBody = null; + boolean x509CredentialsFound = false; CertPath certpath = message.getCertificateChain(); X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]); for (X509Certificate cert : chain) { @@ -136,12 +139,15 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer if (latch.await(10, TimeUnit.SECONDS)) { ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { - JsonNode credentialsJson = JacksonUtil.toJsonNode(msg.getCredentials()); - String certBody = credentialsJson.get("cert").asText(); - String endpoint = credentialsJson.get("endpoint").asText(); + LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class); + if(!credentials.getClient().getSecurityConfigClientMode().equals(SecurityMode.X509)){ + continue; + } + X509ClientCredentialsConfig config = (X509ClientCredentialsConfig) credentials.getClient(); + String certBody = config.getCert(); + String endpoint = config.getEndpoint(); if (strCert.equals(certBody)) { - //TODO: extract endpoint from credentials body and push to storage - credentialsBody = msg.getCredentials(); + x509CredentialsFound = true; DeviceProfile deviceProfile = msg.getDeviceProfile(); if (msg.hasDeviceInfo() && deviceProfile != null) { sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); @@ -159,7 +165,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer log.error(e.getMessage(), e); } } - if (credentialsBody == null) { + if (!x509CredentialsFound) { if (staticCertificateVerifier != null) { staticCertificateVerifier.verifyCertificate(message, session); } else { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java index 35fe63c376..0aff74d189 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java @@ -9,6 +9,7 @@ import static org.eclipse.leshan.core.SecurityMode.X509; public class X509ClientCredentialsConfig implements LwM2MClientCredentialsConfig { private boolean allowTrustedOnly; private String cert; + private String endpoint; @Override public SecurityMode getSecurityConfigClientMode() { From 4ec25beeb3a109277260018163832eaa320a27b9 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 6 May 2021 17:11:24 +0300 Subject: [PATCH 09/40] LwM2M Integration test --- .../server/controller/AbstractWebTest.java | 3 +- .../transport/TransportSqlTestSuite.java | 3 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 95 ++++ .../lwm2m/NoSecLwM2MIntegrationTest.java | 156 +++++++ .../lwm2m/client/LwM2MTestClient.java | 262 +++++++++++ .../lwm2m/client/SimpleLwM2MDevice.java | 199 +++++++++ application/src/test/resources/logback.xml | 6 +- application/src/test/resources/lwm2m/0.xml | 405 ++++++++++++++++++ application/src/test/resources/lwm2m/1.xml | 360 ++++++++++++++++ application/src/test/resources/lwm2m/2.xml | 123 ++++++ application/src/test/resources/lwm2m/3.xml | 331 ++++++++++++++ .../lwm2m/secure/credentials/HasKey.java | 15 + .../LwM2MClientCredentialsConfig.java | 15 + .../secure/credentials/LwM2MCredentials.java | 15 + .../NoSecClientCredentialsConfig.java | 15 + .../PSKClientCredentialsConfig.java | 15 + .../RPKClientCredentialsConfig.java | 15 + .../X509ClientCredentialsConfig.java | 15 + .../DefaultLwM2MTransportMsgHandler.java | 3 +- 19 files changed, 2045 insertions(+), 6 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java create mode 100644 application/src/test/resources/lwm2m/0.xml create mode 100644 application/src/test/resources/lwm2m/1.xml create mode 100644 application/src/test/resources/lwm2m/2.xml create mode 100644 application/src/test/resources/lwm2m/3.xml diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 2329e8086c..937244fc93 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -22,6 +22,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwts; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; @@ -120,7 +121,7 @@ public abstract class AbstractWebTest { protected String refreshToken; protected String username; - private TenantId tenantId; + protected TenantId tenantId; @SuppressWarnings("rawtypes") private HttpMessageConverter mappingJackson2HttpMessageConverter; diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index d16bbc3885..25df3bee00 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -32,7 +32,8 @@ import java.util.Arrays; "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", "org.thingsboard.server.transport.*.attributes.request.sql.*Test", "org.thingsboard.server.transport.*.claim.sql.*Test", - "org.thingsboard.server.transport.*.provision.sql.*Test" + "org.thingsboard.server.transport.*.provision.sql.*Test", + "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java new file mode 100644 index 0000000000..97f7bfbc3c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -0,0 +1,95 @@ +/** + * 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 com.fasterxml.jackson.core.type.TypeReference; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.controller.AbstractWebsocketTest; +import org.thingsboard.server.controller.TbTestWebSocketClient; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.Base64; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +@DaoSqlTest +public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { + + protected DeviceProfile deviceProfile; + protected ScheduledExecutorService executor; + protected TbTestWebSocketClient wsClient; + + @Before + public void beforeTest() throws Exception { + executor = Executors.newScheduledThreadPool(10); + loginTenantAdmin(); + + String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; + for (String resourceName : resources) { + TbResource lwModel = new TbResource(); + lwModel.setResourceType(ResourceType.LWM2M_MODEL); + lwModel.setTitle(resourceName); + lwModel.setFileName(resourceName); + lwModel.setTenantId(tenantId); + byte[] bytes = IOUtils.toByteArray(AbstractLwM2MIntegrationTest.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName)); + lwModel.setData(Base64.getEncoder().encodeToString(bytes)); + lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>(){}); + Assert.assertNotNull(lwModel); + } + wsClient = buildAndConnectWebSocketClient(); + } + + protected void createDeviceProfile(String transportConfiguration) throws Exception { + deviceProfile = new DeviceProfile(); + + deviceProfile.setName("LwM2M No Security"); + deviceProfile.setType(DeviceProfileType.DEFAULT); + deviceProfile.setTenantId(tenantId); + deviceProfile.setTransportType(DeviceTransportType.LWM2M); + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + deviceProfile.setDescription(deviceProfile.getName()); + + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); + deviceProfileData.setTransportConfiguration(JacksonUtil.fromString(transportConfiguration, Lwm2mDeviceProfileTransportConfiguration.class)); + deviceProfile.setProfileData(deviceProfileData); + + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertNotNull(deviceProfile); + } + + @After + public void after() { + executor.shutdownNow(); + wsClient.close(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java new file mode 100644 index 0000000000..809f1f609b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -0,0 +1,156 @@ +/** + * 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.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.query.EntityData; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.SingleEntityFilter; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; +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 org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.NoSecClientCredentialsConfig; + +import java.util.Collections; +import java.util.List; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + + protected final String TRANSPORT_CONFIGURATION = "{\n" + + " \"type\": \"LWM2M\",\n" + + " \"observeAttr\": {\n" + + " \"keyName\": {\n" + + " \"/3_1.0/0/9\": \"batteryLevel\"\n" + + " },\n" + + " \"observe\": [],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"/3_1.0/0/9\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " },\n" + + " \"bootstrap\": {\n" + + " \"servers\": {\n" + + " \"binding\": \"UQ\",\n" + + " \"shortId\": 123,\n" + + " \"lifetime\": 300,\n" + + " \"notifIfDisabled\": true,\n" + + " \"defaultMinPeriod\": 1\n" + + " },\n" + + " \"lwm2mServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5685,\n" + + " \"serverId\": 123,\n" + + " \"securityMode\": \"NO_SEC\",\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" + + " },\n" + + " \"clientLwM2mSettings\": {\n" + + " \"clientOnlyObserveAfterConnect\": 1\n" + + " }\n" + + "}"; + + @NotNull + private Device createDevice(String deviceAEndpoint) throws Exception { + Device device = new Device(); + device.setName("Device A"); + device.setDeviceProfileId(deviceProfile.getId()); + device.setTenantId(tenantId); + device = doPost("/api/device", device, Device.class); + Assert.assertNotNull(device); + + DeviceCredentials deviceCredentials = + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); + Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); + deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); + + deviceCredentials.setCredentialsId(deviceAEndpoint); + + LwM2MCredentials noSecCredentials = new LwM2MCredentials(); + noSecCredentials.setClient(new NoSecClientCredentialsConfig()); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials)); + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); + return device; + } + + @Test + public void testConnectAndObserveTelemetry() throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + + String deviceAEndpoint = "deviceAEndpoint"; + + Device device = createDevice(deviceAEndpoint); + + 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(); + LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint); + client.init(); + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List 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())); + client.destroy(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java new file mode 100644 index 0000000000..6061813bcf --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -0,0 +1,262 @@ +/** + * 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; +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.californium.elements.Connector; +import org.eclipse.californium.scandium.DTLSConnector; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.californium.scandium.dtls.ClientHandshaker; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.californium.scandium.dtls.Handshaker; +import org.eclipse.californium.scandium.dtls.ResumingClientHandshaker; +import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker; +import org.eclipse.californium.scandium.dtls.ServerHandshaker; +import org.eclipse.californium.scandium.dtls.SessionAdapter; +import org.eclipse.leshan.client.californium.LeshanClient; +import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; +import org.eclipse.leshan.client.object.Server; +import org.eclipse.leshan.client.observer.LwM2mClientObserver; +import org.eclipse.leshan.client.resource.ObjectsInitializer; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.californium.DefaultEndpointFactory; +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.request.BindingMode; +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 java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; + +import static org.eclipse.leshan.client.object.Security.noSec; +import static org.eclipse.leshan.core.LwM2mId.DEVICE; +import static org.eclipse.leshan.core.LwM2mId.SECURITY; +import static org.eclipse.leshan.core.LwM2mId.SERVER; + +@Slf4j +@Data +public class LwM2MTestClient { + + private final ScheduledExecutorService executor; + private final String endpoint; + private LeshanClient client; + + public void init() { + String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; + List 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, noSec("coap://localhost:5685", 123)); + initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false)); + initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); + + NetworkConfig coapConfig = new NetworkConfig(); + coapConfig.setString("COAP_PORT", Integer.toString(5685)); + + DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); + dtlsConfig.setRecommendedCipherSuitesOnly(true); + + DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory(); + engineFactory.setReconnectOnUpdate(false); + engineFactory.setResumeOnConnect(true); + + DefaultEndpointFactory endpointFactory = new DefaultEndpointFactory(endpoint) { + @Override + protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) { + + return new DTLSConnector(dtlsConfig) { + @Override + protected void onInitializeHandshaker(Handshaker handshaker) { + handshaker.addSessionListener(new SessionAdapter() { + + @Override + public void handshakeStarted(Handshaker handshaker) throws HandshakeException { + if (handshaker instanceof ServerHandshaker) { + log.info("DTLS Full Handshake initiated by server : STARTED ..."); + } else if (handshaker instanceof ResumingServerHandshaker) { + log.info("DTLS abbreviated Handshake initiated by server : STARTED ..."); + } else if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : STARTED ..."); + } else if (handshaker instanceof ResumingClientHandshaker) { + log.info("DTLS abbreviated Handshake initiated by client : STARTED ..."); + } + } + + @Override + public void sessionEstablished(Handshaker handshaker, DTLSSession establishedSession) + throws HandshakeException { + if (handshaker instanceof ServerHandshaker) { + log.info("DTLS Full Handshake initiated by server : SUCCEED, handshaker {}", handshaker); + } else if (handshaker instanceof ResumingServerHandshaker) { + log.info("DTLS abbreviated Handshake initiated by server : SUCCEED, handshaker {}", handshaker); + } else if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : SUCCEED, handshaker {}", handshaker); + } else if (handshaker instanceof ResumingClientHandshaker) { + log.info("DTLS abbreviated Handshake initiated by client : SUCCEED, handshaker {}", handshaker); + } + } + + @Override + public void handshakeFailed(Handshaker handshaker, Throwable error) { + /** get cause */ + String cause; + if (error != null) { + if (error.getMessage() != null) { + cause = error.getMessage(); + } else { + cause = error.getClass().getName(); + } + } else { + cause = "unknown cause"; + } + + if (handshaker instanceof ServerHandshaker) { + log.info("DTLS Full Handshake initiated by server : FAILED [{}]", cause); + } else if (handshaker instanceof ResumingServerHandshaker) { + log.info("DTLS abbreviated Handshake initiated by server : FAILED [{}]", cause); + } else if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : FAILED [{}]", cause); + } else if (handshaker instanceof ResumingClientHandshaker) { + log.info("DTLS abbreviated Handshake initiated by client : FAILED [{}]", cause); + } + } + }); + } + }; + } + }; + + LeshanClientBuilder builder = new LeshanClientBuilder(endpoint); + builder.setLocalAddress("0.0.0.0", 11000); + 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)); + client = builder.build(); + + LwM2mClientObserver observer = new LwM2mClientObserver() { + @Override + public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) { + log.info("ClientObserver -> onBootstrapStarted..."); + } + + @Override + public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) { + log.info("ClientObserver -> onBootstrapSuccess..."); + } + + @Override + public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.info("ClientObserver -> onBootstrapFailure..."); + } + + @Override + public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) { + log.info("ClientObserver -> onBootstrapTimeout..."); + } + + @Override + public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) { +// log.info("ClientObserver -> onRegistrationStarted... EndpointName [{}]", request.getEndpointName()); + } + + @Override + public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) { + log.info("ClientObserver -> onRegistrationSuccess... EndpointName [{}] [{}]", request.getEndpointName(), registrationID); + } + + @Override + public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.info("ClientObserver -> onRegistrationFailure... ServerIdentity [{}]", server); + } + + @Override + public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) { + log.info("ClientObserver -> onRegistrationTimeout... RegisterRequest [{}]", request); + } + + @Override + public void onUpdateStarted(ServerIdentity server, UpdateRequest request) { +// log.info("ClientObserver -> onUpdateStarted... UpdateRequest [{}]", request); + } + + @Override + public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) { +// log.info("ClientObserver -> onUpdateSuccess... UpdateRequest [{}]", request); + } + + @Override + public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + + } + + @Override + public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) { + + } + + @Override + public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) { + log.info("ClientObserver ->onDeregistrationStarted... DeregisterRequest [{}]", request.getRegistrationId()); + + } + + @Override + public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) { + log.info("ClientObserver ->onDeregistrationSuccess... DeregisterRequest [{}]", request.getRegistrationId()); + + } + + @Override + public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.info("ClientObserver ->onDeregistrationFailure... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId()); + } + + @Override + public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) { + log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId()); + } + }; + this.client.addObserver(observer); + + client.start(); + } + + public void destroy() { + client.stop(false); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java new file mode 100644 index 0000000000..4512a94a27 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -0,0 +1,199 @@ +/** + * 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.LwM2mResource; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.WriteResponse; + +import javax.security.auth.Destroyable; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.TimeZone; + +@Slf4j +public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable { + + + private static final Random RANDOM = new Random(); + private static final List 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) { + if (!identity.isSystem()) + log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); + switch (resourceid) { + case 0: + return ReadResponse.success(resourceid, getManufacturer()); + case 1: + return ReadResponse.success(resourceid, getModelNumber()); + case 2: + return ReadResponse.success(resourceid, getSerialNumber()); + case 3: + return ReadResponse.success(resourceid, getFirmwareVersion()); + case 9: + return ReadResponse.success(resourceid, getBatteryLevel()); + case 10: + return ReadResponse.success(resourceid, getMemoryFree()); + case 11: + Map errorCodes = new HashMap<>(); + errorCodes.put(0, getErrorCode()); + return ReadResponse.success(resourceid, errorCodes, ResourceModel.Type.INTEGER); + case 14: + return ReadResponse.success(resourceid, getUtcOffset()); + case 15: + return ReadResponse.success(resourceid, getTimezone()); + case 16: + return ReadResponse.success(resourceid, getSupportedBinding()); + case 17: + return ReadResponse.success(resourceid, getDeviceType()); + case 18: + return ReadResponse.success(resourceid, getHardwareVersion()); + case 19: + return ReadResponse.success(resourceid, getSoftwareVersion()); + case 20: + return ReadResponse.success(resourceid, getBatteryStatus()); + case 21: + return ReadResponse.success(resourceid, getMemoryTotal()); + default: + return super.read(identity, resourceid); + } + } + + @Override + public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) { + String withParams = null; + if (params != null && params.length() != 0) { + withParams = " with params " + params; + } + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceid, withParams != null ? withParams : ""); + return ExecuteResponse.success(); + } + + @Override + public WriteResponse write(ServerIdentity identity, int resourceid, LwM2mResource value) { + log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); + + switch (resourceid) { + case 13: + return WriteResponse.notFound(); + case 14: + setUtcOffset((String) value.getValue()); + fireResourcesChange(resourceid); + return WriteResponse.success(); + case 15: + setTimezone((String) value.getValue()); + fireResourcesChange(resourceid); + return WriteResponse.success(); + default: + return super.write(identity, resourceid, value); + } + } + + private String getManufacturer() { + return "Leshan Demo Device"; + } + + private String getModelNumber() { + return "Model 500"; + } + + private String getSerialNumber() { + return "LT-500-000-0001"; + } + + private String getFirmwareVersion() { + return "1.0.0"; + } + + private long getErrorCode() { + return 0; + } + + private int getBatteryLevel() { + return 42; + } + + private long getMemoryFree() { + return Runtime.getRuntime().freeMemory() / 1024; + } + + private String utcOffset = new SimpleDateFormat("X").format(Calendar.getInstance().getTime()); + + private String getUtcOffset() { + return utcOffset; + } + + private void setUtcOffset(String t) { + utcOffset = t; + } + + private String timeZone = TimeZone.getDefault().getID(); + + private String getTimezone() { + return timeZone; + } + + private void setTimezone(String t) { + timeZone = t; + } + + private String getSupportedBinding() { + return "U"; + } + + private String getDeviceType() { + return "Demo"; + } + + private String getHardwareVersion() { + return "1.0.1"; + } + + private String getSoftwareVersion() { + return "1.0.2"; + } + + private int getBatteryStatus() { + return RANDOM.nextInt(7); + } + + private long getMemoryTotal() { + return Runtime.getRuntime().totalMemory() / 1024; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + @Override + public void destroy() { + } +} diff --git a/application/src/test/resources/logback.xml b/application/src/test/resources/logback.xml index f991a40078..81d213b42d 100644 --- a/application/src/test/resources/logback.xml +++ b/application/src/test/resources/logback.xml @@ -9,13 +9,15 @@ - + + - + + diff --git a/application/src/test/resources/lwm2m/0.xml b/application/src/test/resources/lwm2m/0.xml new file mode 100644 index 0000000000..81e8523880 --- /dev/null +++ b/application/src/test/resources/lwm2m/0.xml @@ -0,0 +1,405 @@ + + + + + + + LWM2M Security + + 0 + urn:oma:lwm2m:oma:0:1.2 + 1.1 + 1.2 + Multiple + Mandatory + + + LWM2M Server URI + + Single + Mandatory + String + 0..255 + + + + + Bootstrap-Server + + Single + Mandatory + Boolean + + + + + + Security Mode + + Single + Mandatory + Integer + 0..4 + + + + + Public Key or Identity + + Single + Mandatory + Opaque + + + + + + Server Public Key + + Single + Mandatory + Opaque + + + + + + Secret Key + + Single + Mandatory + Opaque + + + + + + SMS Security Mode + + Single + Optional + Integer + 0..255 + + + + + SMS Binding Key Parameters + + Single + Optional + Opaque + 6 + + + + + SMS Binding Secret Key(s) + + Single + Optional + Opaque + 16,32,48 + + + + + LwM2M Server SMS Number + + Single + Optional + String + + + + + + Short Server ID + + Single + Optional + Integer + 1..65534 + + + + + Client Hold Off Time + + Single + Optional + Integer + + s + + + + Bootstrap-Server Account Timeout + + Single + Optional + Integer + + s + + + + Matching Type + + Single + Optional + Integer + 0..3 + + + + + SNI + + Single + Optional + String + + + + + + Certificate Usage + + Single + Optional + Integer + 0..3 + + + + + DTLS/TLS Ciphersuite + + Multiple + Optional + Integer + + + + + OSCORE Security Mode + + Single + Optional + Objlnk + + + + + + Groups To Use by Client + + Multiple + Optional + Integer + 0..65535 + + + + + Signature Algorithms Supported by Server + + Multiple + Optional + Integer + 0..65535 + + + + Signature Algorithms To Use by Client + + Multiple + Optional + Integer + 0..65535 + + + + + Signature Algorithm Certs Supported by Server + + Multiple + Optional + Integer + 0..65535 + + + + + TLS 1.3 Features To Use by Client + + Single + Optional + Integer + 0..65535 + + + + + TLS Extensions Supported by Server + + Single + Optional + Integer + 0..65535 + + + + + TLS Extensions To Use by Client + + Single + Optional + Integer + 0..65535 + + + + + Secondary LwM2M Server URI + + Multiple + Optional + String + 0..255 + + + + MQTT Server + + Single + Optional + Objlnk + + + + + LwM2M COSE Security + + Multiple + Optional + Objlnk + + + + + RDS Destination Port + + Single + Optional + Integer + 0..15 + + + + RDS Source Port + + Single + Optional + Integer + 0..15 + + + + RDS Application ID + + Single + Optional + String + + + + + + + + diff --git a/application/src/test/resources/lwm2m/1.xml b/application/src/test/resources/lwm2m/1.xml new file mode 100644 index 0000000000..f31e839c96 --- /dev/null +++ b/application/src/test/resources/lwm2m/1.xml @@ -0,0 +1,360 @@ + + + + + + + LwM2M Server + + 1 + urn:oma:lwm2m:oma:1:1.2 + 1.2 + 1.2 + Multiple + Mandatory + + + Short Server ID + R + Single + Mandatory + Integer + 1..65534 + + + + + Lifetime + RW + Single + Mandatory + Integer + + s + + + + Default Minimum Period + RW + Single + Optional + Integer + + s + + + + Default Maximum Period + RW + Single + Optional + Integer + + s + + + + Disable + E + Single + Optional + + + + + + + Disable Timeout + RW + Single + Optional + Integer + + s + + + + Notification Storing When Disabled or Offline + RW + Single + Mandatory + Boolean + + + + + + Binding + RW + Single + Mandatory + String + + + + + + Registration Update Trigger + E + Single + Mandatory + + + + + + + Bootstrap-Request Trigger + E + Single + Optional + + + + + + + APN Link + RW + Single + Optional + Objlnk + + + + + + TLS-DTLS Alert Code + R + Single + Optional + Integer + 0..255 + + + + + Last Bootstrapped + R + Single + Optional + Time + + + + + + Registration Priority Order + R + Single + Optional + Integer + + + + + + Initial Registration Delay Timer + RW + Single + Optional + Integer + + s + + + + Registration Failure Block + R + Single + Optional + Boolean + + + + + + Bootstrap on Registration Failure + R + Single + Optional + Boolean + + + + + + Communication Retry Count + RW + Single + Optional + Integer + + + + + + Communication Retry Timer + RW + Single + Optional + Integer + + s + + + + Communication Sequence Delay Timer + RW + Single + Optional + Integer + + s + + + + Communication Sequence Retry Count + RW + Single + Optional + Integer + + + + + + Trigger + RW + Single + Optional + Boolean + + + + + + Preferred Transport + RW + Single + Optional + String + The possible values are those listed in the LwM2M Core Specification + + + + Mute Send + RW + Single + Optional + Boolean + + + + + + Alternate APN Links + RW + Multiple + Optional + Objlnk + + + + + + Supported Server Versions + RW + Multiple + Optional + String + + + + + + Default Notification Mode + RW + Single + Optional + Integer + 0..1 + + + + + Profile ID Hash Algorithm + RW + Single + Optional + Integer + 0..255 + + + + + + + diff --git a/application/src/test/resources/lwm2m/2.xml b/application/src/test/resources/lwm2m/2.xml new file mode 100644 index 0000000000..4ea5805b36 --- /dev/null +++ b/application/src/test/resources/lwm2m/2.xml @@ -0,0 +1,123 @@ + + + + + + + LwM2M Access Control + + 2 + urn:oma:lwm2m:oma:2:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Object ID + R + Single + Mandatory + Integer + 1..65534 + + + + + Object Instance ID + R + Single + Mandatory + Integer + 0..65535 + + + + + ACL + RW + Multiple + Optional + Integer + 0..31 + + + + + Access Control Owner + RW + Single + Mandatory + Integer + 0..65535 + + + + + + + diff --git a/application/src/test/resources/lwm2m/3.xml b/application/src/test/resources/lwm2m/3.xml new file mode 100644 index 0000000000..724fc4cb33 --- /dev/null +++ b/application/src/test/resources/lwm2m/3.xml @@ -0,0 +1,331 @@ + + + + + + + Device + + 3 + urn:oma:lwm2m:oma:3:1.0 + 1.1 + 1.0 + Single + Mandatory + + + Manufacturer + R + Single + Optional + String + + + + + + Model Number + R + Single + Optional + String + + + + + + Serial Number + R + Single + Optional + String + + + + + + Firmware Version + R + Single + Optional + String + + + + + + Reboot + E + Single + Mandatory + + + + + + + Factory Reset + E + Single + Optional + + + + + + + Available Power Sources + R + Multiple + Optional + Integer + 0..7 + + + + + Power Source Voltage + R + Multiple + Optional + Integer + + + + + + Power Source Current + R + Multiple + Optional + Integer + + + + + + Battery Level + R + Single + Optional + Integer + 0..100 + /100 + + + + Memory Free + R + Single + Optional + Integer + + + + + + Error Code + R + Multiple + Mandatory + Integer + 0..32 + + + + + Reset Error Code + E + Single + Optional + + + + + + + Current Time + RW + Single + Optional + Time + + + + + + UTC Offset + RW + Single + Optional + String + + + + + + Timezone + RW + Single + Optional + String + + + + + + Supported Binding and Modes + R + Single + Mandatory + String + + + + + Device Type + R + Single + Optional + String + + + + + Hardware Version + R + Single + Optional + String + + + + + Software Version + R + Single + Optional + String + + + + + Battery Status + R + Single + Optional + Integer + 0..6 + + + + Memory Total + R + Single + Optional + Integer + + + + + ExtDevInfo + R + Multiple + Optional + Objlnk + + + + + + + diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java index 7c75589c4c..65be16bfd6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java @@ -1,3 +1,18 @@ +/** + * 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.secure.credentials; import org.eclipse.leshan.core.util.Hex; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java index 33a297c91a..65f027a849 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java @@ -1,3 +1,18 @@ +/** + * 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.secure.credentials; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java index ebb1fc138b..09c27f0e42 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java @@ -1,3 +1,18 @@ +/** + * 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.secure.credentials; import lombok.Data; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java index bcac7174aa..03933972c3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java @@ -1,3 +1,18 @@ +/** + * 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.secure.credentials; import org.eclipse.leshan.core.SecurityMode; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java index d79083647f..8de85ce72d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java @@ -1,3 +1,18 @@ +/** + * 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.secure.credentials; import lombok.Data; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java index 280e8492f8..025c8b3b10 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java @@ -1,3 +1,18 @@ +/** + * 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.secure.credentials; import org.eclipse.leshan.core.SecurityMode; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java index 0aff74d189..0a2df6852e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java @@ -1,3 +1,18 @@ +/** + * 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.secure.credentials; import lombok.Data; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index b9de7bcea2..e93984e87c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -646,8 +646,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { LwM2mClient lwM2MClient = clientContext.getOrRegister(registration); - if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config - .getModelProvider())) { + if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) { if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) && lwM2MClient.getFrUpdate().getCurrentFwVersion() != null && !lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion()) From 92719c2ac2f5aad06836915718ad0b18e33995b5 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 11 May 2021 14:14:00 +0300 Subject: [PATCH 10/40] added lwm2m x509 test --- .../transport/TransportSqlTestSuite.java | 14 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 133 +++++++++++- .../lwm2m/NoSecLwM2MIntegrationTest.java | 9 +- .../lwm2m/X509LwM2MIntegrationTest.java | 205 ++++++++++++++++++ .../lwm2m/client/LwM2MTestClient.java | 11 +- .../resources/application-test.properties | 2 + .../lwm2m/credentials/clientKeyStore.jks | Bin 0 -> 3180 bytes .../lwm2m/credentials/serverKeyStore.jks | Bin 0 -> 3120 bytes .../lwm2m/secure/TbLwM2MAuthorizer.java | 2 +- .../X509ClientCredentialsConfig.java | 5 +- .../DefaultLwM2MTransportMsgHandler.java | 6 +- .../server/client/LwM2mClientContextImpl.java | 1 + .../TbL2M2MDtlsSessionInMemoryStore.java | 5 + .../server/store/TbLwM2MDtlsSessionStore.java | 3 + .../device/DeviceCredentialsServiceImpl.java | 19 +- 15 files changed, 391 insertions(+), 24 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java create mode 100644 application/src/test/resources/application-test.properties create mode 100644 application/src/test/resources/lwm2m/credentials/clientKeyStore.jks create mode 100644 application/src/test/resources/lwm2m/credentials/serverKeyStore.jks diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index 25df3bee00..d059ea1449 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.transport.*.rpc.sql.*Test", - "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", - "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", - "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", - "org.thingsboard.server.transport.*.attributes.request.sql.*Test", - "org.thingsboard.server.transport.*.claim.sql.*Test", - "org.thingsboard.server.transport.*.provision.sql.*Test", +// "org.thingsboard.server.transport.*.rpc.sql.*Test", +// "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", +// "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", +// "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", +// "org.thingsboard.server.transport.*.attributes.request.sql.*Test", +// "org.thingsboard.server.transport.*.claim.sql.*Test", +// "org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 97f7bfbc3c..91b2afc8c6 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.io.IOUtils; +import org.eclipse.leshan.core.util.Hex; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -35,6 +36,23 @@ import org.thingsboard.server.controller.AbstractWebsocketTest; import org.thingsboard.server.controller.TbTestWebSocketClient; import org.thingsboard.server.dao.service.DaoSqlTest; +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.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -46,6 +64,114 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { 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]; + + 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); + } + } + @Before public void beforeTest() throws Exception { executor = Executors.newScheduledThreadPool(10); @@ -60,7 +186,8 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { lwModel.setTenantId(tenantId); byte[] bytes = IOUtils.toByteArray(AbstractLwM2MIntegrationTest.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName)); lwModel.setData(Base64.getEncoder().encodeToString(bytes)); - lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>(){}); + lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>() { + }); Assert.assertNotNull(lwModel); } wsClient = buildAndConnectWebSocketClient(); @@ -69,7 +196,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { protected void createDeviceProfile(String transportConfiguration) throws Exception { deviceProfile = new DeviceProfile(); - deviceProfile.setName("LwM2M No Security"); + deviceProfile.setName("LwM2M"); deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setTenantId(tenantId); deviceProfile.setTransportType(DeviceTransportType.LWM2M); @@ -87,7 +214,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { } @After - public void after() { + public void after() throws InterruptedException { executor.shutdownNow(); wsClient.close(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index 809f1f609b..f4a7f7f9e2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m; +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.leshan.client.object.Security; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import org.junit.Test; @@ -39,6 +41,7 @@ import org.thingsboard.server.transport.lwm2m.secure.credentials.NoSecClientCred import java.util.Collections; import java.util.List; +import static org.eclipse.leshan.client.object.Security.noSec; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -91,6 +94,10 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { " }\n" + "}"; + private final int port = 5685; + private final Security security = noSec("coap://localhost:" + port, 123); + private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port)); + @NotNull private Device createDevice(String deviceAEndpoint) throws Exception { Device device = new Device(); @@ -138,7 +145,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { wsClient.registerWaitForUpdate(); LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint); - client.init(); + client.init(security, coapConfig); String msg = wsClient.waitForUpdate(); EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java new file mode 100644 index 0000000000..06541e5c56 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java @@ -0,0 +1,205 @@ +/** + * 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 org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.query.EntityData; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.SingleEntityFilter; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.transport.util.SslUtil; +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; +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 org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.X509ClientCredentialsConfig; + +import java.util.Collections; +import java.util.List; + +import static org.eclipse.leshan.client.object.Security.x509; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + + protected final String TRANSPORT_CONFIGURATION = "{\n" + + " \"type\": \"LWM2M\",\n" + + " \"observeAttr\": {\n" + + " \"keyName\": {\n" + + " \"/3_1.0/0/9\": \"batteryLevel\"\n" + + " },\n" + + " \"observe\": [],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"/3_1.0/0/9\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " },\n" + + " \"bootstrap\": {\n" + + " \"servers\": {\n" + + " \"binding\": \"UQ\",\n" + + " \"shortId\": 123,\n" + + " \"lifetime\": 300,\n" + + " \"notifIfDisabled\": true,\n" + + " \"defaultMinPeriod\": 1\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" + + " },\n" + + " \"clientLwM2mSettings\": {\n" + + " \"clientOnlyObserveAfterConnect\": 1\n" + + " }\n" + + "}"; + + + private final int port = 5686; + private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port)); + private final String endpoint = "deviceAEndpoint"; + private final String serverUri = "coaps://localhost:" + port; + + @NotNull + private Device createDevice(String credentialsId, X509ClientCredentialsConfig credentialsConfig) throws Exception { + Device device = new Device(); + device.setName("Device A"); + device.setDeviceProfileId(deviceProfile.getId()); + device.setTenantId(tenantId); + device = doPost("/api/device", device, Device.class); + Assert.assertNotNull(device); + + DeviceCredentials deviceCredentials = + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); + Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); + deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); + + deviceCredentials.setCredentialsId(credentialsId); + + LwM2MCredentials X509Credentials = new LwM2MCredentials(); + + X509Credentials.setClient(credentialsConfig); + + deviceCredentials.setCredentialsValue(JacksonUtil.toString(X509Credentials)); + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); + return device; + } + + @Test + public void testConnectAndObserveTelemetry() throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + + Device device = createDevice(endpoint, new X509ClientCredentialsConfig(null, null)); + + 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(); + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); + Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); + client.init(security, coapConfig); + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List 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())); + client.destroy(); + } + + @Test + public void testConnectWithCertAndObserveTelemetry() throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + Device device = createDevice(null, new X509ClientCredentialsConfig(SslUtil.getCertificateString(clientX509CertNotTrusted), endpoint)); + + 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(); + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); + + Security security = x509(serverUri, 123, clientX509CertNotTrusted.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); + + client.init(security, coapConfig); + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List 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())); + client.destroy(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 6061813bcf..8a17b6e3c9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -32,6 +32,7 @@ import org.eclipse.californium.scandium.dtls.SessionAdapter; import org.eclipse.leshan.client.californium.LeshanClient; import org.eclipse.leshan.client.californium.LeshanClientBuilder; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; +import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.resource.ObjectsInitializer; @@ -54,7 +55,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; -import static org.eclipse.leshan.client.object.Security.noSec; import static org.eclipse.leshan.core.LwM2mId.DEVICE; import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; @@ -67,7 +67,7 @@ public class LwM2MTestClient { private final String endpoint; private LeshanClient client; - public void init() { + public void init(Security security, NetworkConfig coapConfig) { String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; List models = new ArrayList<>(); for (String resourceName : resources) { @@ -75,13 +75,10 @@ public class LwM2MTestClient { } LwM2mModel model = new StaticModel(models); ObjectsInitializer initializer = new ObjectsInitializer(model); - initializer.setInstancesForObject(SECURITY, noSec("coap://localhost:5685", 123)); + initializer.setInstancesForObject(SECURITY, security); initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false)); initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); - NetworkConfig coapConfig = new NetworkConfig(); - coapConfig.setString("COAP_PORT", Integer.toString(5685)); - DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); dtlsConfig.setRecommendedCipherSuitesOnly(true); @@ -256,7 +253,7 @@ public class LwM2MTestClient { } public void destroy() { - client.stop(false); + client.destroy(true); } } diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties new file mode 100644 index 0000000000..6638504b2f --- /dev/null +++ b/application/src/test/resources/application-test.properties @@ -0,0 +1,2 @@ +transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks +transport.lwm2m.security.key_store_password=server \ No newline at end of file diff --git a/application/src/test/resources/lwm2m/credentials/clientKeyStore.jks b/application/src/test/resources/lwm2m/credentials/clientKeyStore.jks new file mode 100644 index 0000000000000000000000000000000000000000..7cc58589b72517c57a1086144275218ad04b75de GIT binary patch literal 3180 zcmY+EcQhLgyT>I~#h#@`&C&{j+M^=W-m|s$Dlyu|jM=CdX-UmeTS=(BDJ7^?tEm3$ z-D;v$6|}s)_uTuw_x|;q^ZB0Vd!Elf4E~`y z_c5=z7aiXiMP0SIp3X%Zap%Z$u5o#n@rt3Ni{ZVz8sNQ!BJU_!EeP+w52WLeWfEYZ z@Su1=;YZ;`5k&D2@*gicPFW5rHfz5?FF{#FIf#;+qO6jVoPshWfnN0A4YV}H3G|$Q z5i1Zt@ppj!lK}poAI*RH1%Rg>D$|YFIKb2gpEW5N9}71w{I_3R0%+=L4gR(-Ux$}= zrm0RO$19#zUzA_G3G8i7vuD>K*KK(PwRHrBK`AvmJJmkd!pe&uC*)(P;5ld71zZ3nuS1haTTmybS^XcAe)So$acDI| zmC*^Gf!EzPXY8x~_>;H9?m{U=9jZWPu%xf2q14AY93aolJz4Lu0+{AFNooT?9dtWz`(x=LG7zcaw5@tiW6 zmN7Em+7$q-do3HSnjr~K7i$OMF~&oJ-78kIbuYA|-W$8}+aB?CWmX~#($Ss!|I7&8 zfzpcY|8#*-X9$@bd@~1Kxj!&SvaHrl<<~TWt0F&f=gH`1dGss};YfXU%L1>@5(5?8 zo|Yz-=nPBV4!`{UW?-x&DSt)i`o@H4&UT_N$3Al?kw~xEz@uIOX>_CvZqsY7E>`~< z)u*Ig9OBuoh^RM|RpcGI}(s>QSJNWeWpNW|N4DYAP|kGi@> z0_}UVlN$JJX%%~{e?gb|ER3<*#P5>xqxchlz0x zdf-!mQz6b>!Ofl~woyu43-;@;oczXcNkA>gnT4T8349H4O)Nz&(3#G2!9F=|f%zMi z;B;)}v&n`X#el-~waY$)-IZ8RPQ|dIyR)_(Xg8IaeF2p=NivE{WrI6#_Kk0qdC83) zt8Kw1lF=*v%8?z=+X<|ezsSc*XZx2~c{srB~%&tf5yj*pxL10uj$iFwb_=xkXmfAQVWy$)OTCIO2^!(%q zxwYh2rLAb*+ZHn{l6_)MkB&qfn!mdBU^9A&<25(d37M%`4oO9I?`tX-Kv?q&8RveWJfTj|C*(~?a#XX* zHc(Kk*0x$06q2$&-h*yOv-F2@<*;(v=PEq<>?(zw>`knBgne}3G$IN&q>UzqPAomA z9%$1-D5=Hok(GkAn+y0IKFm#1<*%U2ZvFBl*);k|4D|2lUJQ>MmxEpV`gh;ld1rEv zflhw7)RFQ@=6A4DN0QHvgW~bihUx8!_HXhwlU#K>8k06ja}hpatemUXh5rQ9swEiGJ1d><-1Yn+7nJy9>Sx7w z;=~!rnUdx1%RGUqAEL*(bui)Z!Hac=&(Y4ZX^#p$BrXT=!v^E!aA0no{1@99M z@y$C;m9h3otJ2vBDLjtAJ&*`3n+x`5Hr@!H72_`-KU`^z$Wr9V?F!ZXesRi~(m5uQ zrmL29!G383?}8oj3MWbIfioD)ui2eS1+{gO^4K;^_WBh@)M#hmK4#>>E&iT~IKQ0G zd%9t~Td|-nRlbA$XuS5cHYnQcIqBVru2A2 zz@*?0t6R8i+L=2C$of411@kr ziwo=QI@w;(No~GjiNPc%ecPX{8As|KFW$P%K4ssybl4l$x=H19epGEbbN?VozQPWZ zHRRz|Lz62iPT6j2wv2CWiYn)pVgetR(62!%nlN^8-#O+@W6xAVK51xO=MQ=% zM%#nP*jvSr*%2y1%2dydZL}mRwgaE_Dj3n-ym8Uj&W-)7{l+dkAo2tp81>JS(9L7Q z#TWlFvY_oJ4F54dCarark`7_Xp!ebjl3e{|tNov$sp&b)=MwaNAhs?zf*AMPWQ?E~ zKCiO$_k~+-F2Oamgl}kIs>nVceYQNeiwL8#Y>HCiTZQ7=<7xT7)9oXtoDcy;`^Lsd zMjLBJhOqg$oE>PMEx1(<|O)1HA9sm+Nu(eMq@G_by%kOo}7V{ryp4 z@T<~pv9?Z^(H7Rrr5_enmuER~)Y$8;a%>PWsXloG)WNPvuDS!;2wg{nK>bMV3V4{6eZAHBpRy z1nc~^$CBLtM4#X5r9>|`8y+fhF%rd*24!pd$azQaxq3e7rN$q}80e9h1U;hMNWJ4G zZ7TBeD>JH2%L@W5ZWuP4Zma~P+mc^9=lNx-1L?zEJ}Mp9%=v8kH_@a=S|n#(U7lj) zU=^aS+<`VRu{|vtOq4KOZMdVb?1`G0?OJf}_;DrHU4C(UqO!%RYzBoQ7ENywB1Pr7 zv(vcn<7sU!l>>UP%5OhboL<#IVF&FLuf;ofQkXbndq)P`62!6=Sh5L5N{?G=KLu7T z#Zw}ME)jjpF|E zmXHTaZWr$LoRLcT8<(k`=(C~#DS~R+23BZE0Ag+YU2(9KjC*qWk>3u;C05Q8of!d- zUFw!jFxDi!>2s$)cAcYs-Y{T!emm@^e}VWkxA;5tRrvWyvm^qzONEI}46wyN>B MoMV8L095h60D(LA`2YX_ literal 0 HcmV?d00001 diff --git a/application/src/test/resources/lwm2m/credentials/serverKeyStore.jks b/application/src/test/resources/lwm2m/credentials/serverKeyStore.jks new file mode 100644 index 0000000000000000000000000000000000000000..f1f03005e129985fd17b09e8dd6e0e2caa2d366f GIT binary patch literal 3120 zcmb7?2{aUH8^>pc!7vD8SJuc-1`|edMaB}L#3hEIn2uW2`P}c^@7{CI`Of+7ciwZ}=bZolyzhB_&;LOXp|T(#9fHWR%m9ItbV+-x zKt^B&k!6&g$TD;y_aTU&h`(7NJUtQQdm?)wf;$;l{&)ms27)qt5f2d349x$LPW~07 zhj9?PR?blS-^PeIRe7>qzlKfC76Q=$T8-&-wd!1sA1ALX%mpGE>qRQyYQ@=L@jGH9 zwlzgWqguGkP2JL?Z!!ypYphpOvh!i;{et~-hS;E6h914$VaZ5!8ONS3_b=Wn@)`)i zKYN70Z;pqc=Ef9=BZ6&-7Mc9C||ERjTud|b7$z(dsId?92!yS5B)mT1E>WH));h6 zscGKlO-5>NW;KK!m=^Sm-{@5b=e{u4cc}hG#Aiyp_Pl1WDF(ielD~mC|5vv#d66?` z01p7JCm^0D)$63*1>E^R;1m^6Cvg8HCvV$h9O&?oB+R10Azjn)&E@fSqd(w4>ZEr+ z1IBT6>@K7U4KZq-^O9MPREcl;nx!Y#$n(X0Okj3TnT~m@3x}r@y^@w;WXzyC*0NR;wL1QDY4=Xe+yGKi4#Cz3P>2skNX ze>uRv?IFYOdzd0DuJ8H^e7LqzBSE~Fx3Lne^5oxq1R~R_e=}h*Tk!b@kZ_RAR;?l{ z{J^+uvHZ+;TWQu)0!|P65R+2I)-KBL-AhLu#Xs`-`u|F;9yeY!&*3cccvly^p1PMz zyMxEHw66MVhv)98S|B(H3j0?YI?D!(*#8}zC+ zG!ze)K6)xg$;W8hbE$7LnbCFK&4hh4T?=Oj>{}3gaUEkwOO-HSjF-hh`M-9KCL8FF zx<$&+g{3}q7=uCkTa@F3W*C}%9J9mCa;8t2WU)uFSJSD6*5hIUr8=*2SN3rCas z4x)TN?m?Z64X|Cv-H3++S5Lpfc5%k>H$WHo%^59yadG?RoBQ?m1>f54-AXh~P~(>R zqC^XOZtLnW@yWvVC5KaGa}jx%^;6F_Tbei~JJCLQt8O5_;aq_c)E@Z>B>!!;R3Yv>a7DsSefN9@{g5~bOq&uvm=C&ZGW_PJWzVL!{m z+V&Zd1lb$Qfpai(d|L0C+~?aPCZ)UTIF-jM*`9d; zHzS?%D(y)s)GGbj?UZa>K!DWq$lJ3hbzjmjaYRrACpu5@Q((*X;X}J{luqW90{G+` zNkJfC{dMjy7Ji1Z0Xev!&0(^?WR&fr2NF5|K(g|;@8;NN zWAdCdufb47p$JuDWwi*on_}Bq@mpN|QR_p>@WPUe>Drved6RL>seLL2RZ+kO1YeUt zE_J!GdFQ&wF-wPr{*tvR31(b5<_l`37f+1783!MaTI!mb?tgCBFm zCj}NS3Sk*Cp(aRaP=;DWk(+*7r_DRKZ%gEDal=g)_P@%?Vje2?bxqH2gztp=6a0;) zhDKc%Jb7KJI0u9&AAdD=<*Pr$H}{f@s4u-BJK@B-8a%qCKhoe@SR6NY(oYFlR+}$w zct3j0iKKxnC14)p1YgW~FRs5oI*4=Jl)r>rvQ;cAf>)V6P^Gqq;7Cw&4nkKQgIXq__R(T-&~r`Kr=kc<#4!o>-H%`Wuz8 zs(}?jTs-g9LG>B(ykbMLQ+Tq%Y@fH>nu(5&RR-o;;LVZ7h?S+xzE_EMFEqkOD(^XX zIqlf;=YQF6cyrW;E>D*?!>`YjIKVqLLU?Zv3b&hhwU&32v;;PQEvoAsri#F*ka7<& zc_;1q*ZPT~(BhZH&hB#GQ*AW>8{4i9?_$d|N*y2HJx406k(b?Ar1<1#_v&1#e#Li&t1P`~}OT}+!`r>=7 z*a}U}ha~P$MZMkXmV4JjtQFTM9bOZPvMB?7OYx`1ta`;SlF{WXrlC?AfeH)lvRjui z!?dnCtlmcR6=7OhyTshguRD@~R`Zujn6Pptx;s9x{NlNJc2j%y9~r1JT97t(|4o9j zYf0H_wHE$+qo3>w%ZGV+EL|(KIY352RtdVzbDY$h3h7)~g0z0ygEt5F{qj;EmjY(d z0T~&WsKR97y(@-B#Wn0Ttx2W|BV%)9OE7-U^8VGpv)<63&H9;y>(BR2@h+afz{{_8 zgGUCE@+m8`NLv5wo%ve^47(q2^;JKD8@!R7?K6(Ak;g%mRRH@hV~)=snhOUarRy75 zx)iA%8O4MjRgDYs$*MMBNqol1JF>CL5SoaFr zI4I$}{w)@M=v7w=C@)e^yFuvI^p?z50d1`xeBHOqcPT}v!Ii;XXUAdD6y?tA-2TUA zdc-adXpE1Ydo61DYOsyXi&+R}eb)WfJ#jPIf~UP8YIXDv1e!M%$dLm{eSY*_FJb%ZDa!T^?GqXP+m0raq|ZrxS|MbSSj qL;D3%%-d?jak~%bVSeI3r^Om(r)};qh1!z#p-K Date: Tue, 11 May 2021 14:48:32 +0300 Subject: [PATCH 11/40] refactored --- .../server/transport/TransportSqlTestSuite.java | 14 +++++++------- .../lwm2m/AbstractLwM2MIntegrationTest.java | 2 +- .../transport/lwm2m/NoSecLwM2MIntegrationTest.java | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index d059ea1449..25df3bee00 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ -// "org.thingsboard.server.transport.*.rpc.sql.*Test", -// "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", -// "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", -// "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", -// "org.thingsboard.server.transport.*.attributes.request.sql.*Test", -// "org.thingsboard.server.transport.*.claim.sql.*Test", -// "org.thingsboard.server.transport.*.provision.sql.*Test", + "org.thingsboard.server.transport.*.rpc.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", + "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", + "org.thingsboard.server.transport.*.attributes.request.sql.*Test", + "org.thingsboard.server.transport.*.claim.sql.*Test", + "org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 91b2afc8c6..46ad3c1c34 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -214,7 +214,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { } @After - public void after() throws InterruptedException { + public void after() { executor.shutdownNow(); wsClient.close(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index f4a7f7f9e2..72f4d041f3 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -95,7 +95,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { "}"; private final int port = 5685; - private final Security security = noSec("coap://localhost:" + port, 123); + private final Security security = noSec("coap://localhost:" + port, 123); private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port)); @NotNull From b59c846885764bded676deab7949365728ad74c4 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 12 May 2021 18:38:12 +0300 Subject: [PATCH 12/40] Improvements to data converter --- .../transport/adaptor/JsonConverter.java | 32 +++++++++++++------ .../src/test/java/JsonConverterTest.java | 8 +++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index a4e8c60841..4db4aa9bd8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -226,19 +226,29 @@ public class JsonConverter { } private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { - if (value.getAsString().contains(".")) { - return KeyValueProto.newBuilder() - .setKey(key) - .setType(KeyValueType.DOUBLE_V) - .setDoubleV(value.getAsDouble()) - .build(); + String valueAsString = value.getAsString(); + KeyValueProto.Builder builder = KeyValueProto.newBuilder().setKey(key); + if (valueAsString.contains("e") || valueAsString.contains("E")) { + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String + var bd = new BigDecimal(valueAsString); + if (bd.stripTrailingZeros().scale() <= 0) { + try { + return builder.setType(KeyValueType.LONG_V).setLongV(bd.longValueExact()).build(); + } catch (ArithmeticException e) { + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build(); + } + } else { + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build(); + } + } else if (valueAsString.contains(".")) { + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(value.getAsDouble()).build(); } else { try { long longValue = Long.parseLong(value.getAsString()); - return KeyValueProto.newBuilder().setKey(key).setType(KeyValueType.LONG_V) - .setLongV(longValue).build(); + return builder.setType(KeyValueType.LONG_V).setLongV(longValue).build(); } catch (NumberFormatException e) { - throw new JsonSyntaxException("Big integer values are not supported!"); + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(new BigDecimal(valueAsString).doubleValue()).build(); } } } @@ -252,6 +262,7 @@ public class JsonConverter { String valueAsString = value.getAsString(); String key = valueEntry.getKey(); if (valueAsString.contains("e") || valueAsString.contains("E")) { + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String var bd = new BigDecimal(valueAsString); if (bd.stripTrailingZeros().scale() <= 0) { try { @@ -269,7 +280,8 @@ public class JsonConverter { long longValue = Long.parseLong(value.getAsString()); result.add(new LongDataEntry(key, longValue)); } catch (NumberFormatException e) { - throw new JsonSyntaxException("Big integer values are not supported!"); + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String + result.add(new DoubleDataEntry(key, new BigDecimal(valueAsString).doubleValue())); } } } diff --git a/common/transport/transport-api/src/test/java/JsonConverterTest.java b/common/transport/transport-api/src/test/java/JsonConverterTest.java index cedbef50c9..dc28b268f5 100644 --- a/common/transport/transport-api/src/test/java/JsonConverterTest.java +++ b/common/transport/transport-api/src/test/java/JsonConverterTest.java @@ -21,6 +21,8 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import java.util.ArrayList; + @RunWith(MockitoJUnitRunner.class) public class JsonConverterTest { @@ -38,6 +40,12 @@ public class JsonConverterTest { Assert.assertEquals(10.1, result.get(0L).get(0).getDoubleValue().get(), 0.0); } + @Test + public void testParseAttributesBigDecimalAsLong() { + var result = new ArrayList<>(JsonConverter.convertToAttributes(JSON_PARSER.parse("{\"meterReadingDelta\": 1E1}"))); + Assert.assertEquals(10L, result.get(0).getLongValue().get().longValue()); + } + @Test public void testParseAsDouble() { var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1.1}"), 0L); From 53c51953c48be65352a9bdd34fe68f9d27fc2172 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 13 May 2021 16:20:39 +0300 Subject: [PATCH 13/40] UI: Refactoring lwm2m security config and add X509 certificate in lwm2m security model --- .../device/device-credentials.component.html | 29 +- .../device/device-credentials.component.ts | 90 +----- .../security-config-lwm2m-server.component.ts | 5 + .../security-config-lwm2m.component.html | 232 +++++++--------- .../device/security-config-lwm2m.component.ts | 262 +++++++----------- .../models/lwm2m-security-config.models.ts | 99 ++----- .../assets/locale/locale.constant-en_US.json | 12 +- 7 files changed, 242 insertions(+), 487 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html index db28ff0453..0cf349a074 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html @@ -75,32 +75,7 @@ ('device.client-id-or-user-name-necessary' | translate) : ''">
- - device.lwm2m-key - - - {{ 'device.lwm2m-key-required' | translate }} - - - - device.lwm2m-value - - - {{ 'device.lwm2m-value-required' | translate }} - - - {{ 'device.lwm2m-value-format-error' | translate }} - -
- -
-
+ +
diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts index 17ad828b48..19e9419829 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts @@ -34,19 +34,7 @@ import { DeviceCredentialsType } from '@shared/models/device.models'; import { Subject } from 'rxjs'; -import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; -import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component'; -import { - ClientSecurityConfig, - DEFAULT_END_POINT, - DeviceCredentialsDialogLwm2mData, - END_POINT, - getDefaultSecurityConfig, - JSON_ALL_CONFIG, - validateSecurityConfig -} from '@shared/models/lwm2m-security-config.models'; -import { TranslateService } from '@ngx-translate/core'; -import { MatDialog } from '@angular/material/dialog'; +import { takeUntil } from 'rxjs/operators'; import { isDefinedAndNotNull } from '@core/utils'; @Component({ @@ -84,9 +72,7 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, private propagateChange = (v: any) => {}; - constructor(public fb: FormBuilder, - private translate: TranslateService, - private dialog: MatDialog) { + constructor(public fb: FormBuilder) { this.deviceCredentialsFormGroup = this.fb.group({ credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], credentialsId: [null], @@ -99,15 +85,14 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, }); this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); this.deviceCredentialsFormGroup.valueChanges.pipe( - distinctUntilChanged(), takeUntil(this.destroy$) ).subscribe(() => { this.updateView(); }); this.deviceCredentialsFormGroup.get('credentialsType').valueChanges.pipe( takeUntil(this.destroy$) - ).subscribe((type) => { - this.credentialsTypeChanged(type); + ).subscribe(() => { + this.credentialsTypeChanged(); }); } @@ -128,8 +113,6 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, let credentialsValue = null; if (value.credentialsType === DeviceCredentialsType.MQTT_BASIC) { credentialsBasic = JSON.parse(value.credentialsValue) as DeviceCredentialMQTTBasic; - } else if (value.credentialsType === DeviceCredentialsType.LWM2M_CREDENTIALS) { - credentialsValue = JSON.parse(JSON.stringify(value.credentialsValue)) as ClientSecurityConfig; } else { credentialsValue = value.credentialsValue; } @@ -176,11 +159,10 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, }; } - credentialsTypeChanged(credentialsType: DeviceCredentialsType): void { - const credentialsValue = credentialsType === DeviceCredentialsType.LWM2M_CREDENTIALS ? this.lwm2mDefaultConfig : null; + credentialsTypeChanged(): void { this.deviceCredentialsFormGroup.patchValue({ credentialsId: null, - credentialsValue, + credentialsValue: null, credentialsBasic: {clientId: '', userName: '', password: ''} }); this.updateValidators(); @@ -198,14 +180,8 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false}); break; case DeviceCredentialsType.X509_CERTIFICATE: - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); - this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false}); - break; case DeviceCredentialsType.LWM2M_CREDENTIALS: - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required, this.lwm2mConfigJsonValidator]); + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); @@ -245,56 +221,4 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, onlySelf: true }); } - - openSecurityInfoLwM2mDialog($event: Event): void { - if ($event) { - $event.stopPropagation(); - $event.preventDefault(); - } - let credentialsValue = this.deviceCredentialsFormGroup.get('credentialsValue').value; - if (credentialsValue === null || credentialsValue.length === 0) { - credentialsValue = getDefaultSecurityConfig(); - } else { - try { - credentialsValue = JSON.parse(credentialsValue); - } catch (e) { - credentialsValue = getDefaultSecurityConfig(); - } - } - const credentialsId = this.deviceCredentialsFormGroup.get('credentialsId').value || DEFAULT_END_POINT; - this.dialog.open(SecurityConfigLwm2mComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - jsonAllConfig: credentialsValue, - endPoint: credentialsId - } - }).afterClosed().subscribe( - (res) => { - if (res) { - this.deviceCredentialsFormGroup.patchValue({ - credentialsValue: this.isDefaultLw2mResponse(res[JSON_ALL_CONFIG]) ? null : JSON.stringify(res[JSON_ALL_CONFIG]), - credentialsId: this.isDefaultLw2mResponse(res[END_POINT]) ? null : JSON.stringify(res[END_POINT]).split('\"').join('') - }); - this.deviceCredentialsFormGroup.get('credentialsValue').markAsDirty(); - } - } - ); - } - - private isDefaultLw2mResponse(response: object): boolean { - return Object.keys(response).length === 0 || JSON.stringify(response) === '[{}]'; - } - - private lwm2mConfigJsonValidator(control: FormControl) { - return validateSecurityConfig(control.value) ? null : {jsonError: {parsedJson: 'error'}}; - } - - private get lwm2mDefaultConfig(): string { - return JSON.stringify(getDefaultSecurityConfig(), null, 2); - } - - lwm2mCredentialsValueTooltip(flag: boolean): string { - return !flag ? '' : 'Example (mode=\"NoSec\"):\n\r ' + this.lwm2mDefaultConfig; - } } diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts index b8e1b42ef4..d599927834 100644 --- a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts @@ -130,6 +130,8 @@ export class SecurityConfigLwm2mServerComponent implements OnDestroy, ControlVal case Lwm2mSecurityType.NO_SEC: this.serverFormGroup.get('clientPublicKeyOrId').clearValidators(); this.serverFormGroup.get('clientSecretKey').clearValidators(); + this.serverFormGroup.get('clientPublicKeyOrId').disable({emitEvent: false}); + this.serverFormGroup.get('clientSecretKey').disable(); break; case Lwm2mSecurityType.PSK: this.lenMinClientPublicKeyOrId = 0; @@ -172,5 +174,8 @@ export class SecurityConfigLwm2mServerComponent implements OnDestroy, ControlVal Validators.minLength(this.lengthClientSecretKey), Validators.maxLength(this.lengthClientSecretKey) ]); + + this.serverFormGroup.get('clientPublicKeyOrId').enable({emitEvent: false}); + this.serverFormGroup.get('clientSecretKey').enable(); } } diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html index bd83cc817b..77b7601ed5 100644 --- a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html @@ -15,129 +15,109 @@ limitations under the License. --> -
- -

{{ title }}

- - -
-
- - device.lwm2m-security-config.endpoint - - - {{ 'device.lwm2m-security-config.endpoint-required' | translate }} - - - - - - device.lwm2m-security-config.mode - - - {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }} - - - -
- - {{ 'device.lwm2m-security-config.identity' | translate }} - - - {{ 'device.lwm2m-security-config.identity-required' | translate }} - - -
- - {{ 'device.lwm2m-security-config.client-key' | translate }} - - {{key.value?.length || 0}}/{{lenMaxKeyClient}} - - {{ 'device.lwm2m-security-config.client-key-required' | translate }} - - - {{ 'device.lwm2m-security-config.client-key-pattern' | translate }} - - - {{ 'device.lwm2m-security-config.client-key-length' | translate: { - count: lenMaxKeyClient - } }} - - - - {{ 'device.lwm2m-security-config.client-certificate' | translate }} - -
- -
- - - - - {{ 'device.lwm2m-security-config.bootstrap-server' | translate }} - - - - - - - - - - - {{ 'device.lwm2m-security-config.lwm2m-server' | translate }} - - - - - - - - -
-
- - - - - - -
-
-
- - -
-
+ + + + + device.lwm2m-security-config.endpoint + + + {{ 'device.lwm2m-security-config.endpoint-required' | translate }} + + + + device.lwm2m-security-config.mode + + + {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }} + + + + + {{ 'device.lwm2m-security-config.identity' | translate }} + + + {{ 'device.lwm2m-security-config.identity-required' | translate }} + + + + {{ 'device.lwm2m-security-config.client-key' | translate }} + + {{key.value?.length || 0}}/{{lenMaxKeyClient}} + + {{ 'device.lwm2m-security-config.client-key-required' | translate }} + + + {{ 'device.lwm2m-security-config.client-key-pattern' | translate }} + + + {{ 'device.lwm2m-security-config.client-key-length' | translate: { + count: lenMaxKeyClient + } }} + + + + device.lwm2m-security-config.client-public-key + + device.lwm2m-security-config.client-public-key-hint + + + + +
+ + + + + {{ 'device.lwm2m-security-config.bootstrap-server' | translate }} + + + + + + + + + + + {{ 'device.lwm2m-security-config.lwm2m-server' | translate }} + + + + + + + + +
+
+ + + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts index 419576753c..eb920d6190 100644 --- a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts @@ -14,19 +14,20 @@ /// limitations under the License. /// - -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { DialogComponent } from '@shared/components/dialog.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { Router } from '@angular/router'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { TranslateService } from '@ngx-translate/core'; +import { Component, forwardRef, OnDestroy } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; import { - DeviceCredentialsDialogLwm2mData, - getClientSecurityConfig, - JSON_ALL_CONFIG, + getDefaultClientSecurityConfig, + getDefaultServerSecurityConfig, KEY_REGEXP_HEX_DEC, LEN_MAX_PSK, LEN_MAX_PUBLIC_KEY_RPK, @@ -34,48 +35,67 @@ import { Lwm2mSecurityType, Lwm2mSecurityTypeTranslationMap } from '@shared/models/lwm2m-security-config.models'; -import { MatTabChangeEvent } from '@angular/material/tabs'; -import { MatTab } from '@angular/material/tabs/tab'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-security-config-lwm2m', templateUrl: './security-config-lwm2m.component.html', - styleUrls: ['./security-config-lwm2m.component.scss'] + styleUrls: ['./security-config-lwm2m.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SecurityConfigLwm2mComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SecurityConfigLwm2mComponent), + multi: true + } + ] }) -export class SecurityConfigLwm2mComponent extends DialogComponent implements OnInit, OnDestroy { - - private destroy$ = new Subject(); +export class SecurityConfigLwm2mComponent implements ControlValueAccessor, Validator, OnDestroy { lwm2mConfigFormGroup: FormGroup; - title: string; securityConfigLwM2MType = Lwm2mSecurityType; securityConfigLwM2MTypes = Object.keys(Lwm2mSecurityType); credentialTypeLwM2MNamesMap = Lwm2mSecurityTypeTranslationMap; - formControlNameJsonAllConfig = JSON_ALL_CONFIG; - jsonAllConfig: Lwm2mSecurityConfigModels; lenMaxKeyClient = LEN_MAX_PSK; - tabPrevious: MatTab; - tabIndexPrevious = 0; - constructor(protected store: Store, - protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData, - public dialogRef: MatDialogRef, - public fb: FormBuilder, - public translate: TranslateService) { - super(store, router, dialogRef); + private destroy$ = new Subject(); + private propagateChange = (v: any) => {}; + + constructor(private fb: FormBuilder) { + this.lwm2mConfigFormGroup = this.initLwm2mConfigForm(); } - ngOnInit() { - this.jsonAllConfig = JSON.parse(JSON.stringify(this.data.jsonAllConfig)); - this.lwm2mConfigFormGroup = this.initLwm2mConfigFormGroup(); - this.title = this.translate.instant('device.lwm2m-security-info') + ': ' + this.data.endPoint; - this.lwm2mConfigFormGroup.get('x509').disable(); - this.initClientSecurityConfig(this.lwm2mConfigFormGroup.get('jsonAllConfig').value); - this.registerDisableOnLoadFormControl(this.lwm2mConfigFormGroup.get('securityConfigClientMode')); + writeValue(obj: string) { + if (isDefinedAndNotNull(obj)) { + this.initClientSecurityConfig(JSON.parse(obj)); + } + } + + registerOnChange(fn: any) { + this.propagateChange = fn; + } + + registerOnTouched(fn: any) {} + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.lwm2mConfigFormGroup.disable({emitEvent: false}); + } else { + this.lwm2mConfigFormGroup.enable({emitEvent: false}); + } + } + + validate(): ValidationErrors | null { + return this.lwm2mConfigFormGroup.valid ? null : { + securityConfigLWm2m: false + }; } ngOnDestroy() { @@ -83,175 +103,97 @@ export class SecurityConfigLwm2mComponent extends DialogComponent { - if (jsonAllConfig.client.securityConfigClientMode !== Lwm2mSecurityType.NO_SEC) { - this.lwm2mConfigFormGroup.patchValue(jsonAllConfig.client, {emitEvent: false}); - } - this.securityConfigClientUpdateValidators(jsonAllConfig.client.securityConfigClientMode); + private initClientSecurityConfig(config: Lwm2mSecurityConfigModels): void { + this.lwm2mConfigFormGroup.patchValue(config, {emitEvent: false}); + this.securityConfigClientUpdateValidators(config.client.securityConfigClientMode); } private securityConfigClientModeChanged(type: Lwm2mSecurityType): void { - const config = getClientSecurityConfig(type, this.lwm2mConfigFormGroup.get('endPoint').value); + const config = getDefaultClientSecurityConfig(type, this.lwm2mConfigFormGroup.get('client.endpoint').value); switch (type) { case Lwm2mSecurityType.PSK: - config.identity = this.data.endPoint; - config.key = this.lwm2mConfigFormGroup.get('key').value; + config.key = this.lwm2mConfigFormGroup.get('client.key').value; break; case Lwm2mSecurityType.RPK: - config.key = this.lwm2mConfigFormGroup.get('key').value; + config.key = this.lwm2mConfigFormGroup.get('client.key').value; break; } - this.jsonAllConfig.client = config; - this.lwm2mConfigFormGroup.patchValue({ - ...config, - jsonAllConfig: this.jsonAllConfig - }, {emitEvent: false}); + this.lwm2mConfigFormGroup.get('client').patchValue(config, {emitEvent: false}); this.securityConfigClientUpdateValidators(type); } private securityConfigClientUpdateValidators = (mode: Lwm2mSecurityType): void => { switch (mode) { case Lwm2mSecurityType.NO_SEC: + this.setValidatorsNoSecX509(); + this.lwm2mConfigFormGroup.get('client.cert').disable(); + break; case Lwm2mSecurityType.X509: this.setValidatorsNoSecX509(); + this.lwm2mConfigFormGroup.get('client.cert').enable(); break; case Lwm2mSecurityType.PSK: this.lenMaxKeyClient = LEN_MAX_PSK; this.setValidatorsPskRpk(mode); + this.lwm2mConfigFormGroup.get('client.identity').enable(); break; case Lwm2mSecurityType.RPK: this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK; this.setValidatorsPskRpk(mode); + this.lwm2mConfigFormGroup.get('client.identity').disable(); break; } - this.lwm2mConfigFormGroup.get('identity').updateValueAndValidity({emitEvent: false}); - this.lwm2mConfigFormGroup.get('key').updateValueAndValidity({emitEvent: false}); + this.lwm2mConfigFormGroup.get('client.identity').updateValueAndValidity({emitEvent: false}); + this.lwm2mConfigFormGroup.get('client.key').updateValueAndValidity({emitEvent: false}); } private setValidatorsNoSecX509 = (): void => { - this.lwm2mConfigFormGroup.get('identity').setValidators([]); - this.lwm2mConfigFormGroup.get('key').setValidators([]); + this.lwm2mConfigFormGroup.get('client.identity').clearValidators(); + this.lwm2mConfigFormGroup.get('client.key').clearValidators(); + this.lwm2mConfigFormGroup.get('client.identity').disable({emitEvent: false}); + this.lwm2mConfigFormGroup.get('client.key').disable({emitEvent: false}); } private setValidatorsPskRpk = (mode: Lwm2mSecurityType): void => { if (mode === Lwm2mSecurityType.PSK) { - this.lwm2mConfigFormGroup.get('identity').setValidators([Validators.required]); + this.lwm2mConfigFormGroup.get('client.identity').setValidators([Validators.required]); } else { - this.lwm2mConfigFormGroup.get('identity').setValidators([]); - } - this.lwm2mConfigFormGroup.get('key').setValidators([Validators.required, - Validators.pattern(KEY_REGEXP_HEX_DEC), - Validators.maxLength(this.lenMaxKeyClient), Validators.minLength(this.lenMaxKeyClient)]); - } - - tabChanged = (tabChangeEvent: MatTabChangeEvent): void => { - if (this.tabIndexPrevious !== tabChangeEvent.index) { - this.upDateValueToJson(); + this.lwm2mConfigFormGroup.get('client.identity').clearValidators(); } - this.tabIndexPrevious = tabChangeEvent.index; + this.lwm2mConfigFormGroup.get('client.key').setValidators([ + Validators.required, + Validators.pattern(KEY_REGEXP_HEX_DEC), + Validators.maxLength(this.lenMaxKeyClient), + Validators.minLength(this.lenMaxKeyClient) + ]); + this.lwm2mConfigFormGroup.get('client.key').enable({emitEvent: false}); + this.lwm2mConfigFormGroup.get('client.cert').disable({emitEvent: false}); } - private upDateValueToJson(): void { - switch (this.tabIndexPrevious) { - case 0: - this.upDateValueToJsonTab0(); - break; - case 1: - this.upDateValueToJsonTab1(); - break; - } - } - - private upDateValueToJsonTab0 = (): void => { - if (this.lwm2mConfigFormGroup.get('identity').dirty && this.lwm2mConfigFormGroup.get('identity').valid || - this.lwm2mConfigFormGroup.get('key').dirty && this.lwm2mConfigFormGroup.get('key').valid) { - this.updateBootstrapSettings(); - this.upDateJsonAllConfig(); - } - } - - private upDateValueToJsonTab1 = (): void => { - const bootstrap = this.lwm2mConfigFormGroup.get('bootstrapServer').value; - if (bootstrap !== null - && this.lwm2mConfigFormGroup.get('bootstrapServer').dirty - && this.lwm2mConfigFormGroup.get('bootstrapServer').valid) { - this.jsonAllConfig.bootstrap.bootstrapServer = bootstrap; - this.upDateJsonAllConfig(); - } - const serverConfig = this.lwm2mConfigFormGroup.get('lwm2mServer').value; - if (serverConfig !== null - && this.lwm2mConfigFormGroup.get('lwm2mServer').dirty - && this.lwm2mConfigFormGroup.get('lwm2mServer').valid) { - this.jsonAllConfig.bootstrap.lwm2mServer = serverConfig; - this.upDateJsonAllConfig(); - } - } - - private updateBootstrapSettings() { - const securityMode = 'securityMode'; - this.jsonAllConfig.client.identity = this.lwm2mConfigFormGroup.get('identity').value; - this.jsonAllConfig.client.key = this.lwm2mConfigFormGroup.get('key').value; - if (this.lwm2mConfigFormGroup.get('bootstrapServer').value[securityMode] === Lwm2mSecurityType.PSK) { - this.jsonAllConfig.bootstrap.bootstrapServer.clientPublicKeyOrId = this.jsonAllConfig.client.identity; - this.jsonAllConfig.bootstrap.bootstrapServer.clientSecretKey = this.jsonAllConfig.client.key; - this.lwm2mConfigFormGroup.get('bootstrapServer').patchValue(this.jsonAllConfig.bootstrap.bootstrapServer, {emitEvent: false}); - } - if (this.lwm2mConfigFormGroup.get('lwm2mServer').value[securityMode] === Lwm2mSecurityType.PSK) { - this.jsonAllConfig.bootstrap.lwm2mServer.clientPublicKeyOrId = this.jsonAllConfig.client.identity; - this.jsonAllConfig.bootstrap.lwm2mServer.clientSecretKey = this.jsonAllConfig.client.key; - this.lwm2mConfigFormGroup.get('lwm2mServer').patchValue(this.jsonAllConfig.bootstrap.lwm2mServer, {emitEvent: false}); - } - } - - private upDateJsonAllConfig = (): void => { - this.lwm2mConfigFormGroup.patchValue({ - jsonAllConfig: this.jsonAllConfig - }, {emitEvent: false}); - } - - private initLwm2mConfigFormGroup = (): FormGroup => { - if (this.jsonAllConfig.client.securityConfigClientMode === Lwm2mSecurityType.PSK) { - this.data.endPoint = this.jsonAllConfig.client.endpoint; - } + private initLwm2mConfigForm = (): FormGroup => { const formGroup = this.fb.group({ - securityConfigClientMode: [this.jsonAllConfig.client.securityConfigClientMode], - identity: [''], - key: [''], - x509: [false], - bootstrapServer: [this.jsonAllConfig.bootstrap.bootstrapServer], - lwm2mServer: [this.jsonAllConfig.bootstrap.lwm2mServer], - endPoint: [this.data.endPoint], - jsonAllConfig: [this.jsonAllConfig] + client: this.fb.group({ + endpoint: ['', Validators.required], + securityConfigClientMode: [Lwm2mSecurityType.NO_SEC], + identity: [{value: '', disabled: true}], + key: [{value: '', disabled: true}], + cert: [{value: '', disabled: true}] + }), + bootstrap: this.fb.group({ + bootstrapServer: [getDefaultServerSecurityConfig()], + lwm2mServer: [getDefaultServerSecurityConfig()] + }) }); - formGroup.get('securityConfigClientMode').valueChanges.pipe( + formGroup.get('client.securityConfigClientMode').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((type) => { this.securityConfigClientModeChanged(type); }); - formGroup.get('endPoint').valueChanges.pipe( + formGroup.valueChanges.pipe( takeUntil(this.destroy$) - ).subscribe((endpoint) => { - if (formGroup.get('securityConfigClientMode').value === Lwm2mSecurityType.PSK) { - this.jsonAllConfig.client.endpoint = endpoint; - this.upDateJsonAllConfig(); - } + ).subscribe((value) => { + this.propagateChange(JSON.stringify(value)); }); return formGroup; } - - save(): void { - this.upDateValueToJson(); - this.data.endPoint = this.lwm2mConfigFormGroup.get('endPoint').value.split('\'').join(''); - this.data.jsonAllConfig = this.jsonAllConfig; - if (this.lwm2mConfigFormGroup.get('securityConfigClientMode').value === Lwm2mSecurityType.PSK) { - this.data.endPoint = this.data.jsonAllConfig.client.identity; - } - this.dialogRef.close(this.data); - } - - cancel(): void { - this.dialogRef.close(undefined); - } } - - diff --git a/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts index de0f2dc8a5..d3dd6d3ff9 100644 --- a/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts +++ b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts @@ -14,21 +14,12 @@ /// limitations under the License. /// -export const JSON_ALL_CONFIG = 'jsonAllConfig'; -export const END_POINT = 'endPoint'; -export const DEFAULT_END_POINT = 'default_client_lwm2m_end_point_no_sec'; export const LEN_MAX_PSK = 64; export const LEN_MAX_PRIVATE_KEY = 134; export const LEN_MAX_PUBLIC_KEY_RPK = 182; export const LEN_MAX_PUBLIC_KEY_X509 = 3000; export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; - -export interface DeviceCredentialsDialogLwm2mData { - jsonAllConfig?: Lwm2mSecurityConfigModels; - endPoint?: string; -} - export enum Lwm2mSecurityType { PSK = 'PSK', RPK = 'RPK', @@ -48,9 +39,9 @@ export const Lwm2mSecurityTypeTranslationMap = new Map - p.hasOwnProperty('client') && - isClientSecurityConfigType(p.client) && - p.hasOwnProperty('bootstrap') && - isBootstrapSecurityConfig(p.bootstrap); - -const isClientSecurityConfigType = (p: any): boolean => - p.hasOwnProperty('securityConfigClientMode') && - p.hasOwnProperty('endpoint') && - p.hasOwnProperty('identity') && - p.hasOwnProperty('key') && - p.hasOwnProperty('x509'); - -const isBootstrapSecurityConfig = (p: any): boolean => - p.hasOwnProperty('bootstrapServer') && - isServerSecurityConfig(p.bootstrapServer) && - p.hasOwnProperty('lwm2mServer') && - isServerSecurityConfig(p.lwm2mServer); - -const isServerSecurityConfig = (p: any): boolean => - p.hasOwnProperty('securityMode') && - p.hasOwnProperty('clientPublicKeyOrId') && - p.hasOwnProperty('clientSecretKey'); - -export function validateSecurityConfig(config: string): boolean { - try { - const securityConfig = JSON.parse(config); - return isSecurityConfigModels(securityConfig); - } catch (e) { - return false; - } -} - - diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index f664dc27a7..d8a21107bc 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -917,15 +917,7 @@ "access-token-invalid": "Access token length must be from 1 to 20 characters.", "rsa-key": "RSA public key", "rsa-key-required": "RSA public key is required.", - "lwm2m-key": "LwM2M Security config key", - "lwm2m-key-required": "LwM2M Security config key is required.", "lwm2m-value": "LwM2M Security config", - "lwm2m-value-required": "LwM2M Security config value is required.", - "lwm2m-value-format-error": "Security config value must be in LwM2M Security config format.", - "lwm2m-endpoint": "Client endpoint/identity", - "lwm2m-security-info": "Security Config Info", - "lwm2m-value-edit": "Edit Security config", - "lwm2m-credentials-value-tip": "Edit security config json editor", "lwm2m-security-config": { "identity": "Client Identity", "identity-required": "Client Identity is required.", @@ -949,7 +941,9 @@ "client-secret-key-required": "Client Secret Key is required.", "client-secret-key-pattern": "Client Secret Key must be hexadecimal format.", "client-secret-key-length": "Client Secret Key must be {{ count }} characters.", - "config-json-tab": "Json Client Security Config" + "config-json-tab": "Json Client Security Config", + "client-public-key": "Client public key", + "client-public-key-hint": "If client public key is empty, the trusted certificate will be used" }, "client-id": "Client ID", "client-id-pattern": "Contains invalid character.", From 07926350f5096bd602fc59cb20a1d643e4a1656a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 26 May 2021 13:49:30 +0300 Subject: [PATCH 14/40] UI: Improve dashboard performance - move widget header to widget container --- .../dashboard/dashboard.component.html | 109 ++----------- .../dashboard/dashboard.component.scss | 99 ------------ .../dashboard/dashboard.component.ts | 51 +++--- .../components/details-panel.component.ts | 29 +++- .../home/components/home-components.module.ts | 3 + .../widget/widget-container.component.html | 112 +++++++++++++ .../widget/widget-container.component.scss | 117 ++++++++++++++ .../widget/widget-container.component.ts | 153 ++++++++++++++++++ .../components/widget/widget.component.ts | 8 +- .../home/models/dashboard-component.models.ts | 29 +++- .../home/models/widget-component.models.ts | 15 ++ 11 files changed, 499 insertions(+), 226 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/widget-container.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index c050b524cf..e5341f241b 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -64,101 +64,20 @@
-
-
-
- - {{widget.titleIcon}} - {{widget.customTranslatedTitle}} - - - -
-
- - - - - - -
-
-
- - -
-
+ +
diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss index 5414291787..9d9057b77d 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss @@ -48,105 +48,6 @@ } } -tb-widget.tb-widget { - position: relative; - height: 100%; - margin: 0; - overflow: hidden; - outline: none; - - transition: all .2s ease-in-out; -} - -div.tb-widget { - position: relative; - height: 100%; - margin: 0; - overflow: hidden; - outline: none; - - transition: all .2s ease-in-out; - - .tb-widget-title { - max-height: 65px; - padding-top: 5px; - padding-left: 5px; - overflow: hidden; - - tb-timewindow { - font-size: 14px; - opacity: .85; - margin: 0; - } - - .title { - width: 100%; - overflow: hidden; - text-overflow: ellipsis; - line-height: 24px; - letter-spacing: .01em; - margin: 0; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - - &.single-row{ - -webkit-line-clamp: 1; - } - } - } - - .tb-widget-actions { - z-index: 19; - margin: 5px 0 0; - - &-absolute { - position: absolute; - top: 3px; - right: 8px; - z-index: 150; - } - - button.mat-icon-button { - width: 32px; - min-width: 32px; - height: 32px; - min-height: 32px; - padding: 0 !important; - margin: 0 !important; - line-height: 20px; - - mat-icon { - width: 20px; - min-width: 20px; - height: 20px; - min-height: 20px; - font-size: 20px; - line-height: 20px; - } - } - } - - .tb-widget-content { - &.tb-no-interaction { - pointer-events: none; - } - tb-widget { - position: relative; - width: 100%; - } - } - - &.tb-highlighted { - border: 1px solid #039be5; - box-shadow: 0 0 20px #039be5; - } - - &.tb-not-highlighted { - opacity: .5; - } -} - .tb-dashboard-context-menu-items { min-width: 256px; } diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index c871587ba2..72c242c308 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -55,6 +55,7 @@ import { SafeStyle } from '@angular/platform-browser'; import { distinct } from 'rxjs/operators'; import { ResizeObserver } from '@juggle/resize-observer'; import { UtilsService } from '@core/services/utils.service'; +import { WidgetComponentAction, WidgetComponentActionType } from '@home/components/widget/widget-container.component'; @Component({ selector: 'tb-dashboard', @@ -348,7 +349,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } } - openWidgetContextMenu($event: MouseEvent, widget: DashboardWidget) { + private openWidgetContextMenu($event: MouseEvent, widget: DashboardWidget) { if (this.callbacks && this.callbacks.prepareWidgetContextMenu) { const items = this.callbacks.prepareWidgetContextMenu($event, widget.widget); if (items && items.length) { @@ -363,23 +364,47 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } } - onWidgetFullscreenChanged(expanded: boolean, widget: DashboardWidget) { + onWidgetFullscreenChanged(expanded: boolean) { this.isWidgetExpanded = expanded; } - widgetMouseDown($event: Event, widget: DashboardWidget) { + onWidgetComponentAction(action: WidgetComponentAction, widget: DashboardWidget) { + const $event = action.event; + switch (action.actionType) { + case WidgetComponentActionType.MOUSE_DOWN: + this.widgetMouseDown($event, widget); + break; + case WidgetComponentActionType.CLICKED: + this.widgetClicked($event, widget); + break; + case WidgetComponentActionType.CONTEXT_MENU: + this.openWidgetContextMenu($event, widget); + break; + case WidgetComponentActionType.EDIT: + this.editWidget($event, widget); + break; + case WidgetComponentActionType.EXPORT: + this.exportWidget($event, widget); + break; + case WidgetComponentActionType.REMOVE: + this.removeWidget($event, widget); + break; + } + } + + private widgetMouseDown($event: Event, widget: DashboardWidget) { if (this.callbacks && this.callbacks.onWidgetMouseDown) { this.callbacks.onWidgetMouseDown($event, widget.widget); } } - widgetClicked($event: Event, widget: DashboardWidget) { + private widgetClicked($event: Event, widget: DashboardWidget) { if (this.callbacks && this.callbacks.onWidgetClicked) { this.callbacks.onWidgetClicked($event, widget.widget); } } - editWidget($event: Event, widget: DashboardWidget) { + private editWidget($event: Event, widget: DashboardWidget) { if ($event) { $event.stopPropagation(); } @@ -388,7 +413,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } } - exportWidget($event: Event, widget: DashboardWidget) { + private exportWidget($event: Event, widget: DashboardWidget) { if ($event) { $event.stopPropagation(); } @@ -397,7 +422,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } } - removeWidget($event: Event, widget: DashboardWidget) { + private removeWidget($event: Event, widget: DashboardWidget) { if ($event) { $event.stopPropagation(); } @@ -454,14 +479,6 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } } - isHighlighted(widget: DashboardWidget) { - return this.dashboardWidgets.isHighlighted(widget); - } - - isNotHighlighted(widget: DashboardWidget) { - return this.dashboardWidgets.isNotHighlighted(widget); - } - private scrollToWidget(widget: DashboardWidget, delay?: number) { const parentElement = this.gridster.el as HTMLElement; widget.gridsterItemComponent$().subscribe((gridsterItem) => { @@ -534,10 +551,6 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo this.updateWidgetLayouts(); } - public detectChanges() { - this.cd.detectChanges(); - } - private detectRowSize(isMobile: boolean, autofillHeight: boolean, parentHeight?: number): number | null { let rowHeight = null; if (!autofillHeight) { diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.ts b/ui-ngx/src/app/modules/home/components/details-panel.component.ts index 26391db994..edb1d3c405 100644 --- a/ui-ngx/src/app/modules/home/components/details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.ts @@ -14,18 +14,19 @@ /// limitations under the License. /// -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormGroup } from '@angular/forms'; +import { Subscription } from 'rxjs'; @Component({ selector: 'tb-details-panel', templateUrl: './details-panel.component.html', styleUrls: ['./details-panel.component.scss'] }) -export class DetailsPanelComponent extends PageComponent { +export class DetailsPanelComponent extends PageComponent implements OnDestroy { @Input() headerHeightPx = 100; @Input() headerTitle = ''; @@ -35,11 +36,21 @@ export class DetailsPanelComponent extends PageComponent { @Input() isShowSearch = false; @Input() backgroundColor = '#FFF'; - theFormValue: FormGroup; + private theFormValue: FormGroup; + private formSubscription: Subscription = null; @Input() set theForm(value: FormGroup) { - this.theFormValue = value; + if (this.theFormValue !== value) { + if (this.formSubscription !== null) { + this.formSubscription.unsubscribe(); + this.formSubscription = null; + } + this.theFormValue = value; + if (this.theFormValue !== null) { + this.formSubscription = this.theFormValue.valueChanges.subscribe(() => this.cd.detectChanges()); + } + } } get theForm(): FormGroup { @@ -72,10 +83,18 @@ export class DetailsPanelComponent extends PageComponent { } - constructor(protected store: Store) { + constructor(protected store: Store, + private cd: ChangeDetectorRef) { super(store); } + ngOnDestroy() { + if (this.formSubscription !== null) { + this.formSubscription.unsubscribe(); + } + super.ngOnDestroy(); + } + onCloseDetails() { this.closeDetails.emit(); } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 98525ffa90..3958830088 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -142,6 +142,7 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component'; import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component'; import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component'; +import { WidgetContainerComponent } from '@home/components/widget/widget-container.component'; @NgModule({ declarations: @@ -172,6 +173,7 @@ import { DashboardImageDialogComponent } from '@home/components/dashboard-page/d EntityAliasesDialogComponent, EntityAliasDialogComponent, DashboardComponent, + WidgetContainerComponent, WidgetComponent, LegendComponent, WidgetConfigComponent, @@ -290,6 +292,7 @@ import { DashboardImageDialogComponent } from '@home/components/dashboard-page/d EntityAliasesDialogComponent, EntityAliasDialogComponent, DashboardComponent, + WidgetContainerComponent, WidgetComponent, LegendComponent, WidgetConfigComponent, diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html new file mode 100644 index 0000000000..09f3199b06 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html @@ -0,0 +1,112 @@ + +
+
+
+ + {{widget.titleIcon}} + {{widget.customTranslatedTitle}} + + + +
+
+ + + + + + +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss new file mode 100644 index 0000000000..ed66a2b235 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss @@ -0,0 +1,117 @@ +/** + * 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. + */ +:host { + +} + +tb-widget.tb-widget { + position: relative; + height: 100%; + margin: 0; + overflow: hidden; + outline: none; + + transition: all .2s ease-in-out; +} + +div.tb-widget { + position: relative; + height: 100%; + margin: 0; + overflow: hidden; + outline: none; + + transition: all .2s ease-in-out; + + .tb-widget-title { + max-height: 65px; + padding-top: 5px; + padding-left: 5px; + overflow: hidden; + + tb-timewindow { + font-size: 14px; + opacity: .85; + margin: 0; + } + + .title { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + line-height: 24px; + letter-spacing: .01em; + margin: 0; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + + &.single-row{ + -webkit-line-clamp: 1; + } + } + } + + .tb-widget-actions { + z-index: 19; + margin: 5px 0 0; + + &-absolute { + position: absolute; + top: 3px; + right: 8px; + z-index: 150; + } + + button.mat-icon-button { + width: 32px; + min-width: 32px; + height: 32px; + min-height: 32px; + padding: 0 !important; + margin: 0 !important; + line-height: 20px; + + mat-icon { + width: 20px; + min-width: 20px; + height: 20px; + min-height: 20px; + font-size: 20px; + line-height: 20px; + } + } + } + + .tb-widget-content { + &.tb-no-interaction { + pointer-events: none; + } + tb-widget { + position: relative; + width: 100%; + } + } + + &.tb-highlighted { + border: 1px solid #039be5; + box-shadow: 0 0 20px #039be5; + } + + &.tb-not-highlighted { + opacity: .5; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts new file mode 100644 index 0000000000..cd2b322420 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts @@ -0,0 +1,153 @@ +/// +/// 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. +/// + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnInit, + Output +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { DashboardWidget, DashboardWidgets } from '@home/models/dashboard-component.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { SafeStyle } from '@angular/platform-browser'; + +export enum WidgetComponentActionType { + MOUSE_DOWN, + CLICKED, + CONTEXT_MENU, + EDIT, + EXPORT, + REMOVE +} + +export class WidgetComponentAction { + event: MouseEvent; + actionType: WidgetComponentActionType; +} + +@Component({ + selector: 'tb-widget-container', + templateUrl: './widget-container.component.html', + styleUrls: ['./widget-container.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class WidgetContainerComponent extends PageComponent implements OnInit { + + @Input() + widget: DashboardWidget; + + @Input() + dashboardStyle: {[klass: string]: any}; + + @Input() + backgroundImage: SafeStyle | string; + + @Input() + isEdit: boolean; + + @Input() + isMobile: boolean; + + @Input() + dashboardWidgets: DashboardWidgets; + + @Input() + isEditActionEnabled: boolean; + + @Input() + isExportActionEnabled: boolean; + + @Input() + isRemoveActionEnabled: boolean; + + @Input() + disableWidgetInteraction = false; + + @Output() + widgetFullscreenChanged: EventEmitter = new EventEmitter(); + + @Output() + widgetComponentAction: EventEmitter = new EventEmitter(); + + constructor(protected store: Store, + private cd: ChangeDetectorRef) { + super(store); + } + + ngOnInit(): void { + this.widget.widgetContext.containerChangeDetector = this.cd; + } + + isHighlighted(widget: DashboardWidget) { + return this.dashboardWidgets.isHighlighted(widget); + } + + isNotHighlighted(widget: DashboardWidget) { + return this.dashboardWidgets.isNotHighlighted(widget); + } + + onFullscreenChanged(expanded: boolean) { + this.widgetFullscreenChanged.emit(expanded); + } + + onMouseDown(event: MouseEvent) { + this.widgetComponentAction.emit({ + event, + actionType: WidgetComponentActionType.MOUSE_DOWN + }); + } + + onClicked(event: MouseEvent) { + this.widgetComponentAction.emit({ + event, + actionType: WidgetComponentActionType.CLICKED + }); + } + + onContextMenu(event: MouseEvent) { + this.widgetComponentAction.emit({ + event, + actionType: WidgetComponentActionType.CONTEXT_MENU + }); + } + + onEdit(event: MouseEvent) { + this.widgetComponentAction.emit({ + event, + actionType: WidgetComponentActionType.EDIT + }); + } + + onExport(event: MouseEvent) { + this.widgetComponentAction.emit({ + event, + actionType: WidgetComponentActionType.EXPORT + }); + } + + onRemove(event: MouseEvent) { + this.widgetComponentAction.emit({ + event, + actionType: WidgetComponentActionType.REMOVE + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index 4caf5f3edd..fea6e14291 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -468,12 +468,12 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI ); } - private detectChanges(detectDashboardChanges = false) { + private detectChanges(detectContainerChanges = false) { if (!this.destroyed) { try { this.cd.detectChanges(); - if (detectDashboardChanges) { - this.widgetContext.dashboard.detectChanges(); + if (detectContainerChanges) { + this.widgetContext.detectContainerChanges(); } } catch (e) { // console.log(e); @@ -494,7 +494,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } if (!this.widgetContext.inited && this.isReady()) { this.widgetContext.inited = true; - this.widgetContext.dashboard.detectChanges(); + this.widgetContext.detectContainerChanges(); if (this.cafs.init) { this.cafs.init(); this.cafs.init = null; diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts index 88d09ac855..2edd7f9afa 100644 --- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts @@ -79,7 +79,6 @@ export interface IDashboardComponent { pauseChangeNotifications(); resumeChangeNotifications(); notifyLayoutUpdated(); - detectChanges(); } declare type DashboardWidgetUpdateOperation = 'add' | 'remove' | 'update'; @@ -287,8 +286,8 @@ export class DashboardWidgets implements Iterable { export class DashboardWidget implements GridsterItem, IDashboardWidget { - highlighted = false; - selected = false; + private highlightedValue = false; + private selectedValue = false; isFullscreen = false; @@ -335,6 +334,28 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { this.gridsterItemComponentSubject.complete(); } + get highlighted() { + return this.highlightedValue; + } + + set highlighted(highlighted: boolean) { + if (this.highlightedValue !== highlighted) { + this.highlightedValue = highlighted; + this.widgetContext.detectContainerChanges(); + } + } + + get selected() { + return this.selectedValue; + } + + set selected(selected: boolean) { + if (this.selectedValue !== selected) { + this.selectedValue = selected; + this.widgetContext.detectContainerChanges(); + } + } + constructor( private dashboard: IDashboardComponent, public widget: Widget, @@ -407,7 +428,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { this.customHeaderActions = this.widgetContext.customHeaderActions ? this.widgetContext.customHeaderActions : []; this.widgetActions = this.widgetContext.widgetActions ? this.widgetContext.widgetActions : []; if (detectChanges) { - this.dashboard.detectChanges(); + this.widgetContext.detectContainerChanges(); } } diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 64fbe59efa..50466d7600 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -136,6 +136,10 @@ export class WidgetContext { this.changeDetectorValue = cd; } + set containerChangeDetector(cd: ChangeDetectorRef) { + this.containerChangeDetectorValue = cd; + } + get currentUser(): AuthUser { if (this.store) { return getCurrentAuthUser(this.store); @@ -162,6 +166,7 @@ export class WidgetContext { router: Router; private changeDetectorValue: ChangeDetectorRef; + private containerChangeDetectorValue: ChangeDetectorRef; inited = false; destroyed = false; @@ -309,6 +314,16 @@ export class WidgetContext { } } + detectContainerChanges() { + if (!this.destroyed) { + try { + this.containerChangeDetectorValue.detectChanges(); + } catch (e) { + // console.log(e); + } + } + } + updateWidgetParams() { if (!this.destroyed) { setTimeout(() => { From 99f15f5fdbd32b30ed4ae9005c654a8bd8f6f32f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 26 May 2021 13:58:50 +0300 Subject: [PATCH 15/40] Fix isMobileSize parameter --- .../modules/home/components/dashboard/dashboard.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index e5341f241b..58dea3b6f6 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -70,7 +70,7 @@ [dashboardStyle]="dashboardStyle" [backgroundImage]="backgroundImage" [isEdit]="isEdit" - [isMobile]="isMobile" + [isMobile]="isMobileSize" [isEditActionEnabled]="isEditActionEnabled" [isExportActionEnabled]="isExportActionEnabled" [isRemoveActionEnabled]="isRemoveActionEnabled" From 61a5047e34a43a81322dfd4a9ec777980f544d98 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 26 May 2021 15:17:03 +0300 Subject: [PATCH 16/40] UI: Performance improvements by using On Push change detection strategy on top level components --- ui-ngx/src/app/modules/home/menu/menu-link.component.ts | 5 +++-- ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts | 5 +++-- ui-ngx/src/app/modules/home/menu/side-menu.component.ts | 5 +++-- .../modules/home/pages/home-links/home-links.component.ts | 7 +++++-- ui-ngx/src/app/shared/components/breadcrumb.component.ts | 5 +++-- ui-ngx/src/app/shared/components/user-menu.component.ts | 5 +++-- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/modules/home/menu/menu-link.component.ts b/ui-ngx/src/app/modules/home/menu/menu-link.component.ts index a0000e40be..58724b89f9 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-link.component.ts +++ b/ui-ngx/src/app/modules/home/menu/menu-link.component.ts @@ -14,13 +14,14 @@ /// limitations under the License. /// -import { Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { MenuSection } from '@core/services/menu.models'; @Component({ selector: 'tb-menu-link', templateUrl: './menu-link.component.html', - styleUrls: ['./menu-link.component.scss'] + styleUrls: ['./menu-link.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class MenuLinkComponent implements OnInit { diff --git a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts index a3afd99508..6bbc5414fe 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts @@ -14,14 +14,15 @@ /// limitations under the License. /// -import { Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { MenuSection } from '@core/services/menu.models'; import { Router } from '@angular/router'; @Component({ selector: 'tb-menu-toggle', templateUrl: './menu-toggle.component.html', - styleUrls: ['./menu-toggle.component.scss'] + styleUrls: ['./menu-toggle.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class MenuToggleComponent implements OnInit { diff --git a/ui-ngx/src/app/modules/home/menu/side-menu.component.ts b/ui-ngx/src/app/modules/home/menu/side-menu.component.ts index aa96b517f9..c2b974cd6b 100644 --- a/ui-ngx/src/app/modules/home/menu/side-menu.component.ts +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.ts @@ -14,14 +14,15 @@ /// limitations under the License. /// -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { MenuService } from '@core/services/menu.service'; import { MenuSection } from '@core/services/menu.models'; @Component({ selector: 'tb-side-menu', templateUrl: './side-menu.component.html', - styleUrls: ['./side-menu.component.scss'] + styleUrls: ['./side-menu.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class SideMenuComponent implements OnInit { diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts index 7f485df45c..79accbe99b 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { MenuService } from '@core/services/menu.service'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; @@ -25,7 +25,8 @@ import { HomeDashboard } from '@shared/models/dashboard.models'; @Component({ selector: 'tb-home-links', templateUrl: './home-links.component.html', - styleUrls: ['./home-links.component.scss'] + styleUrls: ['./home-links.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class HomeLinksComponent implements OnInit { @@ -37,6 +38,7 @@ export class HomeLinksComponent implements OnInit { constructor(private menuService: MenuService, public breakpointObserver: BreakpointObserver, + private cd: ChangeDetectorRef, private route: ActivatedRoute) { } @@ -57,6 +59,7 @@ export class HomeLinksComponent implements OnInit { if (this.breakpointObserver.isMatched(MediaBreakpoints['gt-lg'])) { this.cols = 4; } + this.cd.detectChanges(); } sectionColspan(section: HomeSection): number { diff --git a/ui-ngx/src/app/shared/components/breadcrumb.component.ts b/ui-ngx/src/app/shared/components/breadcrumb.component.ts index c967c3815e..3fd2f05ff3 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.component.ts +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core'; import { BehaviorSubject, Subject } from 'rxjs'; import { BreadCrumb, BreadCrumbConfig } from './breadcrumb'; import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; @@ -25,7 +25,8 @@ import { guid } from '@core/utils'; @Component({ selector: 'tb-breadcrumb', templateUrl: './breadcrumb.component.html', - styleUrls: ['./breadcrumb.component.scss'] + styleUrls: ['./breadcrumb.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class BreadcrumbComponent implements OnInit, OnDestroy { diff --git a/ui-ngx/src/app/shared/components/user-menu.component.ts b/ui-ngx/src/app/shared/components/user-menu.component.ts index 8598eed5d8..f27f272195 100644 --- a/ui-ngx/src/app/shared/components/user-menu.component.ts +++ b/ui-ngx/src/app/shared/components/user-menu.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core'; import { User } from '@shared/models/user.model'; import { Authority } from '@shared/models/authority.enum'; import { select, Store } from '@ngrx/store'; @@ -27,7 +27,8 @@ import { Router } from '@angular/router'; @Component({ selector: 'tb-user-menu', templateUrl: './user-menu.component.html', - styleUrls: ['./user-menu.component.scss'] + styleUrls: ['./user-menu.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class UserMenuComponent implements OnInit, OnDestroy { From e9f5fd27066f2bfeaf1316d70d83d59c7e52a0cf Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 25 May 2021 17:02:21 +0300 Subject: [PATCH 17/40] added ability to use redis cluster for lwm2m redis stores. --- application/pom.xml | 4 + .../data/device/profile/MqttTopics.java | 2 +- common/transport/lwm2m/pom.xml | 4 + .../server/DefaultLwM2mTransportService.java | 5 +- .../store/TbLwM2mRedisRegistrationStore.java | 373 ++++++++---------- .../store/TbLwM2mRedisSecurityStore.java | 64 +-- .../server/store/TbLwM2mStoreFactory.java | 6 +- pom.xml | 5 + .../src/main/resources/tb-lwm2m-transport.yml | 3 +- 9 files changed, 230 insertions(+), 236 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index e7cedeb540..303f206601 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -145,6 +145,10 @@ ${project.version} runtime + + org.springframework.integration + spring-integration-redis + org.springframework.boot spring-boot-starter-security diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 6373d41803..7b634bffdc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -82,7 +82,7 @@ public class MqttTopics { public static final String DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN; public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC; public static final String DEVICE_FIRMWARE_ERROR_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + ERROR; - public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT = BASE_DEVICE_API_TOPIC_V2 + "%s" + RESPONSE + "/"+ "%s" + CHUNK + "%d"; + public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT = BASE_DEVICE_API_TOPIC_V2 + "/%s" + RESPONSE + "/"+ "%s" + CHUNK + "%d"; public static final String DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN; public static final String DEVICE_SOFTWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC; diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index aae103fc4f..1b639af89d 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -52,6 +52,10 @@ org.springframework spring-context + + org.springframework.integration + spring-integration-redis + org.slf4j slf4j-api diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index e23d55f3a1..c058712234 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -24,7 +24,6 @@ import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; -import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.security.DefaultAuthorizer; import org.eclipse.leshan.server.security.EditableSecurityStore; @@ -127,8 +126,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); /* InMemoryRegistrationStore(ScheduledExecutorService schedExecutor, long cleanPeriodInSec) */ - InMemoryRegistrationStore registrationStore = new InMemoryRegistrationStore(this.registrationStoreExecutor, this.config.getCleanPeriodInSec()); - builder.setRegistrationStore(registrationStore); +//// InMemoryRegistrationStore registrationStore = new InMemoryRegistrationStore(this.registrationStoreExecutor, this.config.getCleanPeriodInSec()); +// builder.setRegistrationStore(registrationStore); /* Create CoAP Config */ builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort())); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index d947e22133..1de2c1a4aa 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -26,9 +26,7 @@ import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.server.californium.observation.ObserveUtil; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; -import org.eclipse.leshan.server.redis.JedisLock; import org.eclipse.leshan.server.redis.RedisRegistrationStore; -import org.eclipse.leshan.server.redis.SingleInstanceJedisLock; import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.registration.Deregistration; @@ -38,11 +36,12 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.UpdatedRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.redis.connection.RedisClusterConnection; +import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.ScanParams; -import redis.clients.jedis.ScanResult; -import redis.clients.jedis.Transaction; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.integration.redis.util.RedisLockRegistry; import java.net.InetSocketAddress; import java.util.ArrayList; @@ -50,13 +49,14 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; -import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; import static java.nio.charset.StandardCharsets.UTF_8; @@ -92,7 +92,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto private final int cleanLimit; // maximum number to clean in a clean period private final long gracePeriod; // in seconds - private final JedisLock lock; + private final RedisLockRegistry redisLock; public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory) { this(connectionFactory, DEFAULT_CLEAN_PERIOD, DEFAULT_GRACE_PERIOD, DEFAULT_CLEAN_LIMIT); // default clean period 60s @@ -106,20 +106,12 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory, ScheduledExecutorService schedExecutor, long cleanPeriodInSec, long lifetimeGracePeriodInSec, int cleanLimit) { - this(connectionFactory, schedExecutor, cleanPeriodInSec, lifetimeGracePeriodInSec, cleanLimit, new SingleInstanceJedisLock()); - } - - /** - * @since 1.1 - */ - public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory, ScheduledExecutorService schedExecutor, long cleanPeriodInSec, - long lifetimeGracePeriodInSec, int cleanLimit, JedisLock redisLock) { this.connectionFactory = connectionFactory; this.schedExecutor = schedExecutor; this.cleanPeriod = cleanPeriodInSec; this.cleanLimit = cleanLimit; this.gracePeriod = lifetimeGracePeriodInSec; - this.lock = redisLock; + this.redisLock = new RedisLockRegistry(connectionFactory, "Registration"); } /* *************** Redis Key utility function **************** */ @@ -135,76 +127,79 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return (prefix + registrationID).getBytes(); } - private byte[] toLockKey(String endpoint) { - return toKey(LOCK_EP, endpoint); + private String toLockKey(String endpoint) { + return new String(toKey(LOCK_EP, endpoint)); } - private byte[] toLockKey(byte[] endpoint) { - return toKey(LOCK_EP.getBytes(UTF_8), endpoint); + private String toLockKey(byte[] endpoint) { + return new String(toKey(LOCK_EP.getBytes(UTF_8), endpoint)); } /* *************** Leshan Registration API **************** */ @Override public Deregistration addRegistration(Registration registration) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - byte[] lockValue = null; - byte[] lockKey = toLockKey(registration.getEndpoint()); + Lock lock = null; + try (var connection = connectionFactory.getConnection()) { + String lockKey = toLockKey(registration.getEndpoint()); try { - lockValue = lock.acquire(j, lockKey); - + lock = redisLock.obtain(lockKey); + lock.lock(); // add registration byte[] k = toEndpointKey(registration.getEndpoint()); - byte[] old = j.getSet(k, serializeReg(registration)); + byte[] old = connection.getSet(k, serializeReg(registration)); // add registration: secondary indexes byte[] regid_idx = toRegIdKey(registration.getId()); - j.set(regid_idx, registration.getEndpoint().getBytes(UTF_8)); + connection.set(regid_idx, registration.getEndpoint().getBytes(UTF_8)); byte[] addr_idx = toRegAddrKey(registration.getSocketAddress()); - j.set(addr_idx, registration.getEndpoint().getBytes(UTF_8)); + connection.set(addr_idx, registration.getEndpoint().getBytes(UTF_8)); // Add or update expiration - addOrUpdateExpiration(j, registration); + addOrUpdateExpiration(connection, registration); if (old != null) { Registration oldRegistration = deserializeReg(old); // remove old secondary index if (!registration.getId().equals(oldRegistration.getId())) - j.del(toRegIdKey(oldRegistration.getId())); + connection.del(toRegIdKey(oldRegistration.getId())); if (!oldRegistration.getSocketAddress().equals(registration.getSocketAddress())) { - removeAddrIndex(j, oldRegistration); + removeAddrIndex(connection, oldRegistration); } // remove old observation - Collection obsRemoved = unsafeRemoveAllObservations(j, oldRegistration.getId()); + Collection obsRemoved = unsafeRemoveAllObservations(connection, oldRegistration.getId()); return new Deregistration(oldRegistration, obsRemoved); } return null; } finally { - lock.release(j, lockKey, lockValue); + if (lock != null) { + lock.unlock(); + } } } } @Override public UpdatedRegistration updateRegistration(RegistrationUpdate update) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { + Lock lock = null; + try (var connection = connectionFactory.getConnection()) { // Fetch the registration ep by registration ID index - byte[] ep = j.get(toRegIdKey(update.getRegistrationId())); + byte[] ep = connection.get(toRegIdKey(update.getRegistrationId())); if (ep == null) { return null; } - byte[] lockValue = null; - byte[] lockKey = toLockKey(ep); + String lockKey = toLockKey(ep); try { - lockValue = lock.acquire(j, lockKey); + lock = redisLock.obtain(lockKey); + lock.lock(); // Fetch the registration - byte[] data = j.get(toEndpointKey(ep)); + byte[] data = connection.get(toEndpointKey(ep)); if (data == null) { return null; } @@ -214,40 +209,42 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto Registration updatedRegistration = update.update(r); // Store the new registration - j.set(toEndpointKey(updatedRegistration.getEndpoint()), serializeReg(updatedRegistration)); + connection.set(toEndpointKey(updatedRegistration.getEndpoint()), serializeReg(updatedRegistration)); // Add or update expiration - addOrUpdateExpiration(j, updatedRegistration); + addOrUpdateExpiration(connection, updatedRegistration); // Update secondary index : // If registration is already associated to this address we don't care as we only want to keep the most // recent binding. byte[] addr_idx = toRegAddrKey(updatedRegistration.getSocketAddress()); - j.set(addr_idx, updatedRegistration.getEndpoint().getBytes(UTF_8)); + connection.set(addr_idx, updatedRegistration.getEndpoint().getBytes(UTF_8)); if (!r.getSocketAddress().equals(updatedRegistration.getSocketAddress())) { - removeAddrIndex(j, r); + removeAddrIndex(connection, r); } return new UpdatedRegistration(r, updatedRegistration); } finally { - lock.release(j, lockKey, lockValue); + if (lock != null) { + lock.unlock(); + } } } } @Override public Registration getRegistration(String registrationId) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - return getRegistration(j, registrationId); + try (var connection = connectionFactory.getConnection()) { + return getRegistration(connection, registrationId); } } @Override public Registration getRegistrationByEndpoint(String endpoint) { Validate.notNull(endpoint); - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - byte[] data = j.get(toEndpointKey(endpoint)); + try (var connection = connectionFactory.getConnection()) { + byte[] data = connection.get(toEndpointKey(endpoint)); if (data == null) { return null; } @@ -258,12 +255,12 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto @Override public Registration getRegistrationByAdress(InetSocketAddress address) { Validate.notNull(address); - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - byte[] ep = j.get(toRegAddrKey(address)); + try (var connection = connectionFactory.getConnection()) { + byte[] ep = connection.get(toRegAddrKey(address)); if (ep == null) { return null; } - byte[] data = j.get(toEndpointKey(ep)); + byte[] data = connection.get(toEndpointKey(ep)); if (data == null) { return null; } @@ -273,140 +270,99 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto @Override public Iterator getAllRegistrations() { - return new TbLwM2mRedisRegistrationStore.RedisIterator(connectionFactory, new ScanParams().match(REG_EP + "*").count(100)); - } - - protected class RedisIterator implements Iterator { - - private final RedisConnectionFactory connectionFactory; - private final ScanParams scanParams; - - private String cursor; - private List scanResult; - - public RedisIterator(RedisConnectionFactory connectionFactory, ScanParams scanParams) { - this.connectionFactory = connectionFactory; - this.scanParams = scanParams; - // init scan result - scanNext("0"); - } - - private void scanNext(String cursor) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - do { - ScanResult sr = j.scan(cursor.getBytes(), scanParams); - - this.scanResult = new ArrayList<>(); - if (sr.getResult() != null && !sr.getResult().isEmpty()) { - for (byte[] value : j.mget(sr.getResult().toArray(new byte[][]{}))) { - this.scanResult.add(deserializeReg(value)); - } - } - - cursor = sr.getCursor(); - } while (!"0".equals(cursor) && scanResult.isEmpty()); - - this.cursor = cursor; - } - } - - @Override - public boolean hasNext() { - if (!scanResult.isEmpty()) { - return true; - } - if ("0".equals(cursor)) { - // no more elements to scan - return false; - } - - // read more elements - scanNext(cursor); - return !scanResult.isEmpty(); - } - - @Override - public Registration next() { - if (!hasNext()) { - throw new NoSuchElementException(); + try (var connection = connectionFactory.getConnection()) { + Collection list = new LinkedList<>(); + ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(REG_EP + "*").build(); + List> scans = new ArrayList<>(); + if (connection instanceof RedisClusterConnection) { + ((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> { + scans.add(((RedisClusterConnection) connection).scan(node, scanOptions)); + }); + } else { + scans.add(connection.scan(scanOptions)); } - return scanResult.remove(0); - } - @Override - public void remove() { - throw new UnsupportedOperationException(); + scans.forEach(scan -> { + scan.forEachRemaining(key -> { + byte[] element = connection.get(key); + list.add(deserializeReg(element)); + }); + }); + return list.iterator(); } } @Override public Deregistration removeRegistration(String registrationId) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - return removeRegistration(j, registrationId, false); + try (var connection = connectionFactory.getConnection()) { + return removeRegistration(connection, registrationId, false); } } - private Deregistration removeRegistration(Jedis j, String registrationId, boolean removeOnlyIfNotAlive) { + private Deregistration removeRegistration(RedisConnection connection, String registrationId, boolean removeOnlyIfNotAlive) { // fetch the client ep by registration ID index - byte[] ep = j.get(toRegIdKey(registrationId)); + byte[] ep = connection.get(toRegIdKey(registrationId)); if (ep == null) { return null; } - byte[] lockValue = null; - byte[] lockKey = toLockKey(ep); + Lock lock = null; + String lockKey = toLockKey(ep); try { - lockValue = lock.acquire(j, lockKey); + lock = redisLock.obtain(lockKey); + lock.lock(); // fetch the client - byte[] data = j.get(toEndpointKey(ep)); + byte[] data = connection.get(toEndpointKey(ep)); if (data == null) { return null; } Registration r = deserializeReg(data); if (!removeOnlyIfNotAlive || !r.isAlive(gracePeriod)) { - long nbRemoved = j.del(toRegIdKey(r.getId())); + long nbRemoved = connection.del(toRegIdKey(r.getId())); if (nbRemoved > 0) { - j.del(toEndpointKey(r.getEndpoint())); - Collection obsRemoved = unsafeRemoveAllObservations(j, r.getId()); - removeAddrIndex(j, r); - removeExpiration(j, r); + connection.del(toEndpointKey(r.getEndpoint())); + Collection obsRemoved = unsafeRemoveAllObservations(connection, r.getId()); + removeAddrIndex(connection, r); + removeExpiration(connection, r); return new Deregistration(r, obsRemoved); } } return null; } finally { - lock.release(j, lockKey, lockValue); + if (lock != null) { + lock.unlock(); + } } } - private void removeAddrIndex(Jedis j, Registration registration) { + private void removeAddrIndex(RedisConnection connection, Registration registration) { // Watch the key to remove. byte[] regAddrKey = toRegAddrKey(registration.getSocketAddress()); - j.watch(regAddrKey); + connection.watch(regAddrKey); - byte[] epFromAddr = j.get(regAddrKey); + byte[] epFromAddr = connection.get(regAddrKey); // Delete the key if needed. if (Arrays.equals(epFromAddr, registration.getEndpoint().getBytes(UTF_8))) { // Try to delete the key - Transaction transaction = j.multi(); - transaction.del(regAddrKey); - transaction.exec(); + connection.multi(); + connection.del(regAddrKey); + connection.exec(); // if transaction failed this is not an issue as the socket address is probably reused and we don't neeed to // delete it anymore. } else { // the key must not be deleted. - j.unwatch(); + connection.unwatch(); } } - private void addOrUpdateExpiration(Jedis j, Registration registration) { - j.zadd(EXP_EP, registration.getExpirationTimeStamp(gracePeriod), registration.getEndpoint().getBytes(UTF_8)); + private void addOrUpdateExpiration(RedisConnection connection, Registration registration) { + connection.zAdd(EXP_EP, registration.getExpirationTimeStamp(gracePeriod), registration.getEndpoint().getBytes(UTF_8)); } - private void removeExpiration(Jedis j, Registration registration) { - j.zrem(EXP_EP, registration.getEndpoint().getBytes(UTF_8)); + private void removeExpiration(RedisConnection connection, Registration registration) { + connection.zRem(EXP_EP, registration.getEndpoint().getBytes(UTF_8)); } private byte[] toRegIdKey(String registrationId) { @@ -441,33 +397,35 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto */ @Override public Collection addObservation(String registrationId, Observation observation) { - List removed = new ArrayList<>(); - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { + try (var connection = connectionFactory.getConnection()) { // fetch the client ep by registration ID index - byte[] ep = j.get(toRegIdKey(registrationId)); + byte[] ep = connection.get(toRegIdKey(registrationId)); if (ep == null) { return null; } - byte[] lockValue = null; - byte[] lockKey = toLockKey(ep); + Lock lock = null; + String lockKey = toLockKey(ep); try { - lockValue = lock.acquire(j, lockKey); + lock = redisLock.obtain(lockKey); + lock.lock(); // cancel existing observations for the same path and registration id. - for (Observation obs : getObservations(j, registrationId)) { + for (Observation obs : getObservations(connection, registrationId)) { if (observation.getPath().equals(obs.getPath()) && !Arrays.equals(observation.getId(), obs.getId())) { removed.add(obs); - unsafeRemoveObservation(j, registrationId, obs.getId()); + unsafeRemoveObservation(connection, registrationId, obs.getId()); } } } finally { - lock.release(j, lockKey, lockValue); + if (lock != null) { + lock.unlock(); + } } } return removed; @@ -475,29 +433,32 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto @Override public Observation removeObservation(String registrationId, byte[] observationId) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { + try (var connection = connectionFactory.getConnection()) { // fetch the client ep by registration ID index - byte[] ep = j.get(toRegIdKey(registrationId)); + byte[] ep = connection.get(toRegIdKey(registrationId)); if (ep == null) { return null; } // remove observation - byte[] lockValue = null; - byte[] lockKey = toLockKey(ep); + Lock lock = null; + String lockKey = toLockKey(ep); try { - lockValue = lock.acquire(j, lockKey); + lock = redisLock.obtain(lockKey); + lock.lock(); Observation observation = build(get(new Token(observationId))); if (observation != null && registrationId.equals(observation.getRegistrationId())) { - unsafeRemoveObservation(j, registrationId, observationId); + unsafeRemoveObservation(connection, registrationId, observationId); return observation; } return null; } finally { - lock.release(j, lockKey, lockValue); + if (lock != null) { + lock.unlock(); + } } } } @@ -509,15 +470,15 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto @Override public Collection getObservations(String registrationId) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - return getObservations(j, registrationId); + try (var connection = connectionFactory.getConnection()) { + return getObservations(connection, registrationId); } } - private Collection getObservations(Jedis j, String registrationId) { + private Collection getObservations(RedisConnection connection, String registrationId) { Collection result = new ArrayList<>(); - for (byte[] token : j.lrange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) { - byte[] obs = j.get(toKey(OBS_TKN, token)); + for (byte[] token : connection.lRange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) { + byte[] obs = connection.get(toKey(OBS_TKN, token)); if (obs != null) { result.add(build(deserializeObs(obs))); } @@ -527,22 +488,24 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto @Override public Collection removeObservations(String registrationId) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { + try (var connection = connectionFactory.getConnection()) { // check registration exists - Registration registration = getRegistration(j, registrationId); + Registration registration = getRegistration(connection, registrationId); if (registration == null) return Collections.emptyList(); // get endpoint and create lock String endpoint = registration.getEndpoint(); - byte[] lockValue = null; - byte[] lockKey = toKey(LOCK_EP, endpoint); + Lock lock = null; + String lockKey = toLockKey(endpoint); try { - lockValue = lock.acquire(j, lockKey); - - return unsafeRemoveAllObservations(j, registrationId); + lock = redisLock.obtain(lockKey); + lock.lock(); + return unsafeRemoveAllObservations(connection, registrationId); } finally { - lock.release(j, lockKey, lockValue); + if (lock != null) { + lock.unlock(); + } } } } @@ -565,31 +528,32 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto String endpoint = ObserveUtil.validateCoapObservation(obs); org.eclipse.californium.core.observe.Observation previousObservation = null; - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - byte[] lockValue = null; - byte[] lockKey = toKey(LOCK_EP, endpoint); + try (var connection = connectionFactory.getConnection()) { + Lock lock = null; + String lockKey = toLockKey(endpoint); try { - lockValue = lock.acquire(j, lockKey); + lock = redisLock.obtain(lockKey); + lock.lock(); String registrationId = ObserveUtil.extractRegistrationId(obs); - if (!j.exists(toRegIdKey(registrationId))) + if (!connection.exists(toRegIdKey(registrationId))) throw new ObservationStoreException("no registration for this Id"); byte[] key = toKey(OBS_TKN, obs.getRequest().getToken().getBytes()); byte[] serializeObs = serializeObs(obs); byte[] previousValue; if (ifAbsent) { - previousValue = j.get(key); + previousValue = connection.get(key); if (previousValue == null || previousValue.length == 0) { - j.set(key, serializeObs); + connection.set(key, serializeObs); } else { return deserializeObs(previousValue); } } else { - previousValue = j.getSet(key, serializeObs); + previousValue = connection.getSet(key, serializeObs); } // secondary index to get the list by registrationId - j.lpush(toKey(OBS_TKNS_REGID_IDX, registrationId), obs.getRequest().getToken().getBytes()); + connection.lPush(toKey(OBS_TKNS_REGID_IDX, registrationId), obs.getRequest().getToken().getBytes()); // log any collisions if (previousValue != null && previousValue.length != 0) { @@ -599,7 +563,9 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto previousObservation.getRequest(), obs.getRequest()); } } finally { - lock.release(j, lockKey, lockValue); + if (lock != null) { + lock.unlock(); + } } } return previousObservation; @@ -607,17 +573,17 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto @Override public void remove(Token token) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { + try (var connection = connectionFactory.getConnection()) { byte[] tokenKey = toKey(OBS_TKN, token.getBytes()); // fetch the observation by token - byte[] serializedObs = j.get(tokenKey); + byte[] serializedObs = connection.get(tokenKey); if (serializedObs == null) return; org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs); String registrationId = ObserveUtil.extractRegistrationId(obs); - Registration registration = getRegistration(j, registrationId); + Registration registration = getRegistration(connection, registrationId); if (registration == null) { LOG.warn("Unable to remove observation {}, registration {} does not exist anymore", obs.getRequest(), registrationId); @@ -625,14 +591,17 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } String endpoint = registration.getEndpoint(); - byte[] lockValue = null; - byte[] lockKey = toKey(LOCK_EP, endpoint); + Lock lock = null; + String lockKey = toLockKey(endpoint); try { - lockValue = lock.acquire(j, lockKey); + lock = redisLock.obtain(lockKey); + lock.lock(); - unsafeRemoveObservation(j, registrationId, token.getBytes()); + unsafeRemoveObservation(connection, registrationId, token.getBytes()); } finally { - lock.release(j, lockKey, lockValue); + if (lock != null) { + lock.unlock(); + } } } @@ -640,8 +609,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto @Override public org.eclipse.californium.core.observe.Observation get(Token token) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - byte[] obs = j.get(toKey(OBS_TKN, token.getBytes())); + try (var connection = connectionFactory.getConnection()) { + byte[] obs = connection.get(toKey(OBS_TKN, token.getBytes())); if (obs == null) { return null; } else { @@ -652,12 +621,12 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto /* *************** Observation utility functions **************** */ - private Registration getRegistration(Jedis j, String registrationId) { - byte[] ep = j.get(toRegIdKey(registrationId)); + private Registration getRegistration(RedisConnection connection, String registrationId) { + byte[] ep = connection.get(toRegIdKey(registrationId)); if (ep == null) { return null; } - byte[] data = j.get(toEndpointKey(ep)); + byte[] data = connection.get(toEndpointKey(ep)); if (data == null) { return null; } @@ -665,25 +634,25 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return deserializeReg(data); } - private void unsafeRemoveObservation(Jedis j, String registrationId, byte[] observationId) { - if (j.del(toKey(OBS_TKN, observationId)) > 0L) { - j.lrem(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, observationId); + private void unsafeRemoveObservation(RedisConnection connection, String registrationId, byte[] observationId) { + if (connection.del(toKey(OBS_TKN, observationId)) > 0L) { + connection.lRem(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, observationId); } } - private Collection unsafeRemoveAllObservations(Jedis j, String registrationId) { + private Collection unsafeRemoveAllObservations(RedisConnection connection, String registrationId) { Collection removed = new ArrayList<>(); byte[] regIdKey = toKey(OBS_TKNS_REGID_IDX, registrationId); // fetch all observations by token - for (byte[] token : j.lrange(regIdKey, 0, -1)) { - byte[] obs = j.get(toKey(OBS_TKN, token)); + for (byte[] token : connection.lRange(regIdKey, 0, -1)) { + byte[] obs = connection.get(toKey(OBS_TKN, token)); if (obs != null) { removed.add(build(deserializeObs(obs))); } - j.del(toKey(OBS_TKN, token)); + connection.del(toKey(OBS_TKN, token)); } - j.del(regIdKey); + connection.del(regIdKey); return removed; } @@ -754,14 +723,14 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto @Override public void run() { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - Set endpointsExpired = j.zrangeByScore(EXP_EP, Double.NEGATIVE_INFINITY, + try (var connection = connectionFactory.getConnection()) { + Set endpointsExpired = connection.zRangeByScore(EXP_EP, Double.NEGATIVE_INFINITY, System.currentTimeMillis(), 0, cleanLimit); for (byte[] endpoint : endpointsExpired) { - Registration r = deserializeReg(j.get(toEndpointKey(endpoint))); + Registration r = deserializeReg(connection.get(toEndpointKey(endpoint))); if (!r.isAlive(gracePeriod)) { - Deregistration dereg = removeRegistration(j, r.getId(), true); + Deregistration dereg = removeRegistration(connection, r.getId(), true); if (dereg != null) expirationListener.registrationExpired(dereg.getRegistration(), dereg.getObservations()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java index 47b13bb72b..4cfe2a6829 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java @@ -20,13 +20,15 @@ import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityStoreListener; +import org.springframework.data.redis.connection.RedisClusterConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.ScanParams; -import redis.clients.jedis.ScanResult; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.ScanOptions; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; +import java.util.List; public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { private static final String SEC_EP = "SEC#EP#"; @@ -42,8 +44,8 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { @Override public SecurityInfo getByEndpoint(String endpoint) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - byte[] data = j.get((SEC_EP + endpoint).getBytes()); + try (var connection = connectionFactory.getConnection()) { + byte[] data = connection.get((SEC_EP + endpoint).getBytes()); if (data == null) { return null; } else { @@ -54,12 +56,12 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - String ep = j.hget(PSKID_SEC, identity); + try (var connection = connectionFactory.getConnection()) { + byte[] ep = connection.hGet(PSKID_SEC.getBytes(), identity.getBytes()); if (ep == null) { return null; } else { - byte[] data = j.get((SEC_EP + ep).getBytes()); + byte[] data = connection.get((SEC_EP + new String(ep)).getBytes()); if (data == null) { return null; } else { @@ -71,18 +73,24 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { @Override public Collection getAll() { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - ScanParams params = new ScanParams().match(SEC_EP + "*").count(100); + try (var connection = connectionFactory.getConnection()) { Collection list = new LinkedList<>(); - String cursor = "0"; - do { - ScanResult res = j.scan(cursor.getBytes(), params); - for (byte[] key : res.getResult()) { - byte[] element = j.get(key); + ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(SEC_EP + "*").build(); + List> scans = new ArrayList<>(); + if (connection instanceof RedisClusterConnection) { + ((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> { + scans.add(((RedisClusterConnection) connection).scan(node, scanOptions)); + }); + } else { + scans.add(connection.scan(scanOptions)); + } + + scans.forEach(scan -> { + scan.forEachRemaining(key -> { + byte[] element = connection.get(key); list.add(deserialize(element)); - } - cursor = res.getCursor(); - } while (!"0".equals(cursor)); + }); + }); return list; } } @@ -90,21 +98,21 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { @Override public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { byte[] data = serialize(info); - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { + try (var connection = connectionFactory.getConnection()) { if (info.getIdentity() != null) { // populate the secondary index (security info by PSK id) - String oldEndpoint = j.hget(PSKID_SEC, info.getIdentity()); - if (oldEndpoint != null && !oldEndpoint.equals(info.getEndpoint())) { + String oldEndpoint = new String(connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes())); + if (!oldEndpoint.equals(info.getEndpoint())) { throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); } - j.hset(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); + connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); } - byte[] previousData = j.getSet((SEC_EP + info.getEndpoint()).getBytes(), data); + byte[] previousData = connection.getSet((SEC_EP + info.getEndpoint()).getBytes(), data); SecurityInfo previous = previousData == null ? null : deserialize(previousData); String previousIdentity = previous == null ? null : previous.getIdentity(); if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { - j.hdel(PSKID_SEC, previousIdentity); + connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes()); } return previous; @@ -113,15 +121,15 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { @Override public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { - try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { - byte[] data = j.get((SEC_EP + endpoint).getBytes()); + try (var connection = connectionFactory.getConnection()) { + byte[] data = connection.get((SEC_EP + endpoint).getBytes()); if (data != null) { SecurityInfo info = deserialize(data); if (info.getIdentity() != null) { - j.hdel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); + connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); } - j.del((SEC_EP + endpoint).getBytes()); + connection.del((SEC_EP + endpoint).getBytes()); if (listener != null) { listener.securityInfoRemoved(infosAreCompromised, info); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java index 1fcbd15e33..164a78b0a3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java @@ -26,6 +26,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import java.util.Optional; @@ -37,6 +38,9 @@ public class TbLwM2mStoreFactory { @Autowired(required = false) private Optional redisConfiguration; + @Autowired + private LwM2MTransportServerConfig config; + @Autowired @Lazy private LwM2mClientContext clientContext; @@ -47,7 +51,7 @@ public class TbLwM2mStoreFactory { @Bean private CaliforniumRegistrationStore registrationStore() { return redisConfiguration.isPresent() && useRedis ? - new TbLwM2mRedisRegistrationStore(redisConfiguration.get().redisConnectionFactory()) : new InMemoryRegistrationStore(); + new TbLwM2mRedisRegistrationStore(redisConfiguration.get().redisConnectionFactory()) : new InMemoryRegistrationStore(config.getCleanPeriodInSec()); } @Bean diff --git a/pom.xml b/pom.xml index 4073fe824e..29a373f2e4 100755 --- a/pom.xml +++ b/pom.xml @@ -1430,6 +1430,11 @@ spring-data-redis ${spring-data-redis.version} + + org.springframework.integration + spring-integration-redis + ${spring.version} + redis.clients jedis diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 1de3ffb630..39acda4590 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -98,7 +98,6 @@ transport: # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property) api_enabled: "${TB_TRANSPORT_API_ENABLED:true}" # Local LwM2M transport parameters - # Local LwM2M transport parameters lwm2m: # Enable/disable lvm2m transport protocol. enabled: "${LWM2M_ENABLED:true}" @@ -144,6 +143,8 @@ transport: recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}" registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}" + registration_store_pool_size: "${LWM2M_REGISTRATION_STORE_POOL_SIZE:100}" + clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}" update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}" un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}" log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}" From 4cd59674ee3ae844754e38398a5ba048d43b612e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 18 May 2021 17:30:01 +0300 Subject: [PATCH 18/40] refactored LwM2M client credentials for the new UI --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 2 +- .../lwm2m/NoSecLwM2MIntegrationTest.java | 8 +-- .../lwm2m/X509LwM2MIntegrationTest.java | 22 ++++---- .../AbstractLwM2MClientCredentials.java} | 21 +++---- .../device/credentials/lwm2m}/HasKey.java | 8 ++- .../lwm2m/LwM2MClientCredentials.java} | 19 +++---- .../credentials/lwm2m/LwM2MSecurityMode.java | 20 +++++++ .../lwm2m/NoSecClientCredentials.java | 24 ++++++++ .../lwm2m/PSKClientCredentials.java} | 16 +++--- .../lwm2m/RPKClientCredentials.java | 24 ++++++++ .../lwm2m/X509ClientCredentials.java} | 16 +++--- ...LwM2mCredentialsSecurityInfoValidator.java | 22 ++++---- .../TbLwM2MDtlsCertificateVerifier.java | 9 ++- .../secure/credentials/LwM2MCredentials.java | 3 +- .../X509ClientCredentialsConfig.java | 36 ------------ .../device/DeviceCredentialsServiceImpl.java | 55 +++++++++++++++---- 16 files changed, 185 insertions(+), 120 deletions(-) rename common/{transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java => data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java} (60%) rename common/{transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials => data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m}/HasKey.java (78%) rename common/{transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java => data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java} (58%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MSecurityMode.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java rename common/{transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java => data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java} (65%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java rename common/{transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java => data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java} (65%) delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 46ad3c1c34..624a1d6a72 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -151,7 +151,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { parameterSpec); KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); -// // Get keys + // Get keys serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index 72f4d041f3..c82845f20c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -36,7 +36,7 @@ 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 org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.transport.lwm2m.secure.credentials.NoSecClientCredentialsConfig; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; import java.util.Collections; import java.util.List; @@ -112,10 +112,10 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); - deviceCredentials.setCredentialsId(deviceAEndpoint); - LwM2MCredentials noSecCredentials = new LwM2MCredentials(); - noSecCredentials.setClient(new NoSecClientCredentialsConfig()); + NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); + clientCredentials.setEndpoint(deviceAEndpoint); + noSecCredentials.setClient(clientCredentials); deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials)); doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); return device; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java index 06541e5c56..18749cfee5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java @@ -22,6 +22,7 @@ import org.junit.Assert; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; @@ -37,7 +38,6 @@ 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 org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.transport.lwm2m.secure.credentials.X509ClientCredentialsConfig; import java.util.Collections; import java.util.List; @@ -101,7 +101,7 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { private final String serverUri = "coaps://localhost:" + port; @NotNull - private Device createDevice(String credentialsId, X509ClientCredentialsConfig credentialsConfig) throws Exception { + private Device createDevice(X509ClientCredentials clientCredentials) throws Exception { Device device = new Device(); device.setName("Device A"); device.setDeviceProfileId(deviceProfile.getId()); @@ -114,13 +114,11 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); - deviceCredentials.setCredentialsId(credentialsId); + LwM2MCredentials credentials = new LwM2MCredentials(); - LwM2MCredentials X509Credentials = new LwM2MCredentials(); + credentials.setClient(clientCredentials); - X509Credentials.setClient(credentialsConfig); - - deviceCredentials.setCredentialsValue(JacksonUtil.toString(X509Credentials)); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); return device; } @@ -128,8 +126,9 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @Test public void testConnectAndObserveTelemetry() throws Exception { createDeviceProfile(TRANSPORT_CONFIGURATION); - - Device device = createDevice(endpoint, new X509ClientCredentialsConfig(null, null)); + X509ClientCredentials credentials = new X509ClientCredentials(); + credentials.setEndpoint(endpoint); + Device device = createDevice(credentials); SingleEntityFilter sef = new SingleEntityFilter(); sef.setSingleEntity(device.getId()); @@ -166,7 +165,10 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @Test public void testConnectWithCertAndObserveTelemetry() throws Exception { createDeviceProfile(TRANSPORT_CONFIGURATION); - Device device = createDevice(null, new X509ClientCredentialsConfig(SslUtil.getCertificateString(clientX509CertNotTrusted), endpoint)); + X509ClientCredentials credentials = new X509ClientCredentials(); + credentials.setEndpoint(endpoint); + credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted)); + Device device = createDevice(credentials); SingleEntityFilter sef = new SingleEntityFilter(); sef.setSingleEntity(device.getId()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java similarity index 60% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java index 8de85ce72d..66eb523209 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java @@ -13,20 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.secure.credentials; +package org.thingsboard.server.common.data.device.credentials.lwm2m; -import lombok.Data; -import org.eclipse.leshan.core.SecurityMode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; -import static org.eclipse.leshan.core.SecurityMode.PSK; - -@Data -public class PSKClientCredentialsConfig extends HasKey implements LwM2MClientCredentialsConfig { - private String identity; +@Getter +@Setter +@NoArgsConstructor +public abstract class AbstractLwM2MClientCredentials implements LwM2MClientCredentials { private String endpoint; - - @Override - public SecurityMode getSecurityConfigClientMode() { - return PSK; - } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java similarity index 78% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java index 65be16bfd6..ec62765298 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.secure.credentials; +package org.thingsboard.server.common.data.device.credentials.lwm2m; -import org.eclipse.leshan.core.util.Hex; +import lombok.SneakyThrows; +import org.apache.commons.codec.binary.Hex; -public class HasKey { +public abstract class HasKey extends AbstractLwM2MClientCredentials { private byte[] key; + @SneakyThrows public void setKey(String key) { if (key != null) { this.key = Hex.decodeHex(key.toLowerCase().toCharArray()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java similarity index 58% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java index 65f027a849..adf0c2ae62 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java @@ -13,25 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.secure.credentials; +package org.thingsboard.server.common.data.device.credentials.lwm2m; 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.eclipse.leshan.core.SecurityMode; -@JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, property = "securityConfigClientMode") @JsonSubTypes({ - @JsonSubTypes.Type(value = NoSecClientCredentialsConfig.class, name = "NO_SEC"), - @JsonSubTypes.Type(value = PSKClientCredentialsConfig.class, name = "PSK"), - @JsonSubTypes.Type(value = RPKClientCredentialsConfig.class, name = "RPK"), - @JsonSubTypes.Type(value = X509ClientCredentialsConfig.class, name = "X509")}) -public interface LwM2MClientCredentialsConfig { + @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")}) +public interface LwM2MClientCredentials { @JsonIgnore - SecurityMode getSecurityConfigClientMode(); + LwM2MSecurityMode getSecurityConfigClientMode(); + + String getEndpoint(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MSecurityMode.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MSecurityMode.java new file mode 100644 index 0000000000..802fcd7efe --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MSecurityMode.java @@ -0,0 +1,20 @@ +/** + * 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; + +public enum LwM2MSecurityMode { + PSK, RPK, X509, NO_SEC; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java new file mode 100644 index 0000000000..7e54a9b63d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.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.common.data.device.credentials.lwm2m; + +public class NoSecClientCredentials extends AbstractLwM2MClientCredentials { + + @Override + public LwM2MSecurityMode getSecurityConfigClientMode() { + return LwM2MSecurityMode.NO_SEC; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java similarity index 65% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java index 03933972c3..2566af7da8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java @@ -13,16 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.secure.credentials; +package org.thingsboard.server.common.data.device.credentials.lwm2m; -import org.eclipse.leshan.core.SecurityMode; +import lombok.Getter; +import lombok.Setter; -import static org.eclipse.leshan.core.SecurityMode.NO_SEC; - -public class NoSecClientCredentialsConfig implements LwM2MClientCredentialsConfig { +@Getter +@Setter +public class PSKClientCredentials extends HasKey { + private String identity; @Override - public SecurityMode getSecurityConfigClientMode() { - return NO_SEC; + public LwM2MSecurityMode getSecurityConfigClientMode() { + return LwM2MSecurityMode.PSK; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java new file mode 100644 index 0000000000..fe329558f8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.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.common.data.device.credentials.lwm2m; + +public class RPKClientCredentials extends HasKey { + + @Override + public LwM2MSecurityMode getSecurityConfigClientMode() { + return LwM2MSecurityMode.RPK; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java similarity index 65% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java index 025c8b3b10..712dcab5eb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java @@ -13,16 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.secure.credentials; +package org.thingsboard.server.common.data.device.credentials.lwm2m; -import org.eclipse.leshan.core.SecurityMode; +import lombok.Getter; +import lombok.Setter; -import static org.eclipse.leshan.core.SecurityMode.RPK; - -public class RPKClientCredentialsConfig extends HasKey implements LwM2MClientCredentialsConfig { +@Getter +@Setter +public class X509ClientCredentials extends AbstractLwM2MClientCredentials { + private String cert; @Override - public SecurityMode getSecurityConfigClientMode() { - return RPK; + public LwM2MSecurityMode getSecurityConfigClientMode() { + return LwM2MSecurityMode.X509; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index cd78cd4c1e..8d90b2a86b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -17,21 +17,21 @@ 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.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.LwM2MSecurityMode; 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.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MClientCredentialsConfig; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.transport.lwm2m.secure.credentials.PSKClientCredentialsConfig; -import org.thingsboard.server.transport.lwm2m.secure.credentials.RPKClientCredentialsConfig; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -97,8 +97,8 @@ public class LwM2mCredentialsSecurityInfoValidator { if (credentials != null) { if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { result.setBootstrapCredentialConfig(credentials.getBootstrap()); - if (SecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) { - PSKClientCredentialsConfig pskClientConfig = (PSKClientCredentialsConfig) credentials.getClient(); + if (LwM2MSecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) { + PSKClientCredentials pskClientConfig = (PSKClientCredentials) credentials.getClient(); endpoint = StringUtils.isNotEmpty(pskClientConfig.getEndpoint()) ? pskClientConfig.getEndpoint() : endpoint; } result.setEndpoint(endpoint); @@ -130,8 +130,8 @@ public class LwM2mCredentialsSecurityInfoValidator { result.setSecurityMode(NO_SEC); } - private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { - PSKClientCredentialsConfig pskConfig = (PSKClientCredentialsConfig) clientCredentialsConfig; + private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { + PSKClientCredentials pskConfig = (PSKClientCredentials) clientCredentialsConfig; if (StringUtils.isNotEmpty(pskConfig.getIdentity())) { try { if (pskConfig.getKey() != null && pskConfig.getKey().length > 0) { @@ -149,8 +149,8 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { - RPKClientCredentialsConfig rpkConfig = (RPKClientCredentialsConfig) clientCredentialsConfig; + private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { + RPKClientCredentials rpkConfig = (RPKClientCredentials) clientCredentialsConfig; try { if (rpkConfig.getKey() != null) { PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getKey()); @@ -164,7 +164,7 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { + private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); result.setSecurityMode(X509); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index d2542192bf..e83532d2cc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.secure; -import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.util.CertPathUtil; @@ -30,12 +29,12 @@ 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.SecurityMode; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; @@ -44,7 +43,7 @@ import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; 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.X509ClientCredentialsConfig; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import javax.annotation.PostConstruct; @@ -140,10 +139,10 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class); - if(!credentials.getClient().getSecurityConfigClientMode().equals(SecurityMode.X509)){ + if(!credentials.getClient().getSecurityConfigClientMode().equals(LwM2MSecurityMode.X509)){ continue; } - X509ClientCredentialsConfig config = (X509ClientCredentialsConfig) credentials.getClient(); + X509ClientCredentials config = (X509ClientCredentials) credentials.getClient(); String certBody = config.getCert(); String endpoint = config.getEndpoint(); if (strCert.equals(certBody)) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java index 09c27f0e42..bbc733b40b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java @@ -16,10 +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.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; @Data public class LwM2MCredentials { - private LwM2MClientCredentialsConfig client; + private LwM2MClientCredentials client; private LwM2MBootstrapConfig bootstrap; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java deleted file mode 100644 index 563224fed2..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java +++ /dev/null @@ -1,36 +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.secure.credentials; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.eclipse.leshan.core.SecurityMode; - -import static org.eclipse.leshan.core.SecurityMode.X509; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class X509ClientCredentialsConfig implements LwM2MClientCredentialsConfig { - private String cert; - private String endpoint; - - @Override - public SecurityMode getSecurityConfigClientMode() { - return X509; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 426e7025c0..5349df1fc7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -16,7 +16,6 @@ package org.thingsboard.server.dao.device; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; @@ -28,6 +27,9 @@ import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; 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.PSKClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -133,7 +135,6 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); } - private void formatCertData(DeviceCredentials deviceCredentials) { String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); String sha3Hash = EncryptionUtil.getSha3Hash(cert); @@ -142,18 +143,48 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen } private void formatSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) { - ObjectNode json = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), ObjectNode.class); - JsonNode client = json.get("client"); - if (client != null && client.get("securityConfigClientMode").asText().equals("X509") && client.has("cert")) { - JsonNode certJson = client.get("cert"); - if (!certJson.isNull()) { - String cert = EncryptionUtil.trimNewLines(certJson.asText()); - String sha3Hash = EncryptionUtil.getSha3Hash(cert); - deviceCredentials.setCredentialsId(sha3Hash); - ((ObjectNode) client).put("cert", cert); - deviceCredentials.setCredentialsValue(JacksonUtil.toString(json)); + LwM2MClientCredentials clientCredentials; + ObjectNode json; + try { + json = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), ObjectNode.class); + if (json == null) { + throw new IllegalArgumentException(); } + clientCredentials = JacksonUtil.convertValue(json.get("client"), LwM2MClientCredentials.class); + if (clientCredentials == null) { + throw new IllegalArgumentException(); + } + } catch (IllegalArgumentException e) { + throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); + } + + String credentialsId; + + switch (clientCredentials.getSecurityConfigClientMode()) { + case NO_SEC: + case RPK: + credentialsId = clientCredentials.getEndpoint(); + break; + case PSK: + credentialsId = ((PSKClientCredentials) clientCredentials).getIdentity(); + break; + case X509: + X509ClientCredentials x509Config = (X509ClientCredentials) clientCredentials; + if (x509Config.getCert() != null) { + String cert = EncryptionUtil.trimNewLines(x509Config.getCert()); + String sha3Hash = EncryptionUtil.getSha3Hash(cert); + x509Config.setCert(cert); + ((ObjectNode) json.get("client")).put("cert", cert); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(json)); + credentialsId = sha3Hash; + } else { + credentialsId = x509Config.getEndpoint(); + } + break; + default: + throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); } + deviceCredentials.setCredentialsId(credentialsId); } @Override From ab5c803a68f50c669a42221d3558ec3476d61e1a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 18 May 2021 17:40:00 +0300 Subject: [PATCH 19/40] refactored --- .../server/dao/device/DeviceCredentialsServiceImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 5349df1fc7..3cce5e1506 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -158,7 +158,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); } - String credentialsId; + String credentialsId = null; switch (clientCredentials.getSecurityConfigClientMode()) { case NO_SEC: @@ -181,8 +181,9 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen credentialsId = x509Config.getEndpoint(); } break; - default: - throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); + } + if (credentialsId == null) { + throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); } deviceCredentials.setCredentialsId(credentialsId); } From 25143378c20073cea3813d120b0ae055e7e84011 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 26 May 2021 16:46:36 +0300 Subject: [PATCH 20/40] Fix message order for Gateway and LwM2M transports in the core consumer --- .../service/queue/DefaultTbCoreConsumerService.java | 13 +++++++++---- .../AbstractTbRuleEngineSubmitStrategy.java | 8 ++++---- .../server/service/queue/processing/IdMsgPair.java | 10 ++++++---- ...uentialByEntityIdTbRuleEngineSubmitStrategy.java | 6 +++--- .../server/DefaultLwM2MTransportMsgHandler.java | 10 +++++++--- .../mqtt/session/GatewaySessionHandler.java | 9 ++++++--- .../server/common/transport/TransportService.java | 3 +++ .../transport/service/DefaultTransportService.java | 9 +++++++++ 8 files changed, 47 insertions(+), 21 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 4742403230..b8c8698d52 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -63,6 +63,7 @@ import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.firmware.FirmwareStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; +import org.thingsboard.server.service.queue.processing.IdMsgPair; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; @@ -74,6 +75,7 @@ import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWra import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -198,14 +200,17 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> pendingMap = msgs.stream().collect( - Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); + List> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).collect(Collectors.toList()); + ConcurrentMap> pendingMap = orderedMsgList.stream().collect( + Collectors.toConcurrentMap(IdMsgPair::getUuid, IdMsgPair::getMsg)); CountDownLatch processingTimeoutLatch = new CountDownLatch(1); TbPackProcessingContext> ctx = new TbPackProcessingContext<>( processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>()); PendingMsgHolder pendingMsgHolder = new PendingMsgHolder(); Future packSubmitFuture = consumersExecutor.submit(() -> { - pendingMap.forEach((id, msg) -> { + orderedMsgList.forEach((element) -> { + UUID id = element.getUuid(); + TbProtoQueueMsg msg = element.getMsg(); log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); TbCallback callback = new TbPackCallback<>(id, ctx); try { @@ -223,7 +228,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); if (actorMsg.isPresent()) { TbActorMsg tbActorMsg = actorMsg.get(); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java index b09b905900..80a4a48fd7 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java @@ -27,7 +27,7 @@ import java.util.stream.Collectors; public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngineSubmitStrategy { protected final String queueName; - protected List orderedMsgList; + protected List> orderedMsgList; private volatile boolean stopped; public AbstractTbRuleEngineSubmitStrategy(String queueName) { @@ -38,7 +38,7 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine @Override public void init(List> msgs) { - orderedMsgList = msgs.stream().map(msg -> new IdMsgPair(UUID.randomUUID(), msg)).collect(Collectors.toList()); + orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).collect(Collectors.toList()); } @Override @@ -48,8 +48,8 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine @Override public void update(ConcurrentMap> reprocessMap) { - List newOrderedMsgList = new ArrayList<>(reprocessMap.size()); - for (IdMsgPair pair : orderedMsgList) { + List> newOrderedMsgList = new ArrayList<>(reprocessMap.size()); + for (IdMsgPair pair : orderedMsgList) { if (reprocessMap.containsKey(pair.uuid)) { newOrderedMsgList.add(pair); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/IdMsgPair.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/IdMsgPair.java index fe1b119d9e..ec4ccce831 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/IdMsgPair.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/IdMsgPair.java @@ -15,16 +15,18 @@ */ package org.thingsboard.server.service.queue.processing; -import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import lombok.Getter; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import java.util.UUID; -public class IdMsgPair { +public class IdMsgPair { + @Getter final UUID uuid; - final TbProtoQueueMsg msg; + @Getter + final TbProtoQueueMsg msg; - public IdMsgPair(UUID uuid, TbProtoQueueMsg msg) { + public IdMsgPair(UUID uuid, TbProtoQueueMsg msg) { this.uuid = uuid; this.msg = msg; } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByEntityIdTbRuleEngineSubmitStrategy.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByEntityIdTbRuleEngineSubmitStrategy.java index ebfe798aee..26c5825286 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByEntityIdTbRuleEngineSubmitStrategy.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByEntityIdTbRuleEngineSubmitStrategy.java @@ -33,7 +33,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs private volatile BiConsumer> msgConsumer; private volatile ConcurrentMap msgToEntityIdMap = new ConcurrentHashMap<>(); - private volatile ConcurrentMap> entityIdToListMap = new ConcurrentHashMap<>(); + private volatile ConcurrentMap>> entityIdToListMap = new ConcurrentHashMap<>(); public SequentialByEntityIdTbRuleEngineSubmitStrategy(String queueName) { super(queueName); @@ -66,7 +66,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs protected void doOnSuccess(UUID id) { EntityId entityId = msgToEntityIdMap.get(id); if (entityId != null) { - Queue queue = entityIdToListMap.get(entityId); + Queue> queue = entityIdToListMap.get(entityId); if (queue != null) { IdMsgPair next = null; synchronized (queue) { @@ -86,7 +86,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs private void initMaps() { msgToEntityIdMap.clear(); entityIdToListMap.clear(); - for (IdMsgPair pair : orderedMsgList) { + for (IdMsgPair pair : orderedMsgList) { EntityId entityId = getEntityId(pair.msg.getValue()); if (entityId != null) { msgToEntityIdMap.put(pair.uuid, entityId); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index bf4b4cefee..8f5c531de3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -182,9 +182,13 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient); if (sessionInfo != null) { transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo)); - transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null); - transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); - transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null); + TransportProtos.TransportToDeviceActorMsg msg = TransportProtos.TransportToDeviceActorMsg.newBuilder() + .setSessionInfo(sessionInfo) + .setSessionEvent(DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN)) + .setSubscribeToAttributes(TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build()) + .setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder().build()) + .build(); + transportService.process(msg, null); this.getInfoFirmwareUpdate(lwM2MClient); this.getInfoSoftwareUpdate(lwM2MClient); this.initLwM2mFromClientValue(registration, lwM2MClient); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java index 73c7347039..7882ad2410 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java @@ -256,9 +256,12 @@ public class GatewaySessionHandler { log.trace("[{}] First got or created device [{}], type [{}] for the gateway session", sessionId, deviceName, deviceType); SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo(); transportService.registerAsyncSession(deviceSessionInfo, deviceSessionCtx); - transportService.process(deviceSessionInfo, DefaultTransportService.getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); - transportService.process(deviceSessionInfo, TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), null); - transportService.process(deviceSessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), null); + transportService.process(TransportProtos.TransportToDeviceActorMsg.newBuilder() + .setSessionInfo(deviceSessionInfo) + .setSessionEvent(DefaultTransportService.getSessionEventMsg(TransportProtos.SessionEvent.OPEN)) + .setSubscribeToAttributes(TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build()) + .setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder().build()) + .build(), null); } futureToSet.set(devices.get(deviceName)); deviceFutures.remove(deviceName); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 4cdf6246e1..91e6be67c5 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.service.SessionMetaData; +import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg; @@ -112,6 +113,8 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback); + void process(TransportToDeviceActorMsg msg, TransportServiceCallback callback); + void process(SessionInfoProto sessionInfoProto, GetFirmwareRequestMsg msg, TransportServiceCallback callback); SessionMetaData registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 69de33c8c0..ac920c816c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -456,6 +456,15 @@ public class DefaultTransportService implements TransportService { } } + @Override + public void process(TransportToDeviceActorMsg msg, TransportServiceCallback callback) { + TransportProtos.SessionInfoProto sessionInfo = msg.getSessionInfo(); + if (checkLimits(sessionInfo, msg, callback)) { + reportActivityInternal(sessionInfo); + sendToDeviceActor(sessionInfo, msg, callback); + } + } + @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback callback) { int dataPoints = 0; From e06faae3ed91eef1f5694e2addaa6598b5b2f6d5 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 20 May 2021 18:03:46 +0300 Subject: [PATCH 21/40] Fix some typos in rus/ukr translations --- ui-ngx/src/assets/locale/locale.constant-ru_RU.json | 6 +++--- ui-ngx/src/assets/locale/locale.constant-uk_UA.json | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json index f015d01b18..ba1a8e1a7b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json +++ b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json @@ -1488,7 +1488,7 @@ "resend-activation": "Повторить отправку активационного письма", "email": "Эл. адрес", "email-required": "Эл. адрес обязателен.", - "invalid-email-format": "Не правильный формат письма.", + "invalid-email-format": "Неправильный формат эл. адреса'.", "first-name": "Имя", "last-name": "Фамилия", "description": "Описание", @@ -1496,14 +1496,14 @@ "always-fullscreen": "Всегда в полноэкранном режиме", "select-user": "Выбрать пользователя", "no-users-matching": "Пользователи, соответствующие '{{entity}}', не найдены.", - "user-required": "Пользователь обязателен", + "user-required": "Необходимо указать пользователя", "activation-method": "Метод активации", "display-activation-link": "Отобразить ссылку для активации", "send-activation-mail": "Отправить активационное письмо", "activation-link": "Активационная ссылка для пользователя", "activation-link-text": "Для активации пользователя используйте ссылку :", "copy-activation-link": "Копировать активационную ссылку", - "activation-link-copied-message": "Ссылка для активации пользователя скопировано в буфер обмена", + "activation-link-copied-message": "Ссылка для активации пользователя скопирована в буфер обмена", "details": "Подробности", "login-as-tenant-admin": "Войти как администратор владельца", "login-as-customer-user": "Войти как пользователь клиента", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index c45b19ad5f..5d4a917335 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -2050,15 +2050,15 @@ "delete-users-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 користувача} other {# користувачів} }?", "delete-users-action-title": "Видалити { count, plural, 1 {1 користувача} other {# користувачів} }", "delete-users-text": "Будьте обережні, після підтвердження, усіх виділених користувачів буде видалено, і всі пов'язані з ними дані стануть недоступними.", - "activation-email-sent-message": "Повідомлення про активацію успішно надіслано!", - "resend-activation": "Повторно надіслати активацію", + "activation-email-sent-message": "Активаційний лист успішно надіслано!", + "resend-activation": "Повторно надіслати активаційного листа", "email": "Електронна пошта", "email-required": "Необхідно вказати електронну пошту.", "invalid-email-format": "Недійсний формат електронної пошти.", "first-name": "Ім'я", "last-name": "Прізвище", "description": "Опис", - "default-dashboard": "Стандартна панель візуалізації", + "default-dashboard": "Панель візуалізації за замовчуванням", "always-fullscreen": "Завжди в повноекранному режимі", "select-user": "Вибрати користувача", "no-users-matching": "Не знайдено жодного користувача, що відповідає '{{entity}}'.", @@ -2067,7 +2067,7 @@ "display-activation-link": "Показати посилання для активації", "send-activation-mail": "Надіслати активаційного листа", "activation-link": "Активаційне посилання для користувача", - "activation-link-text": "Для активувації користувача, скористайтеся наступним activation link :", + "activation-link-text": "Для активації користувача, скористайтеся наступним посиланням :", "copy-activation-link": "Скопіювати активаційне посилання ", "activation-link-copied-message": "Посилання на активацію користувача було скопійовано в буфер обміну", "selected-users": "{ count, plural, 1 {1 користувач} other {# користувачі} } вибрано", @@ -2145,7 +2145,7 @@ "widget-template-load-failed-error": "Не вдалося завантажити шаблон віджета!", "add": "Додати віджет", "undo": "Скасувати зміни віджета", - "export": "Експртувати віджет", + "export": "Експортувати віджет", "export-data": "Експортувати дані віджетів", "export-to-csv": "Експортувати дані в CSV...", "export-to-excel": "Експортувати дані в XLS..." From 9d8a1f0dc5b231e6e879352c612c29ef1c2e2d1c Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 26 May 2021 19:01:21 +0300 Subject: [PATCH 22/40] Merge with master --- application/src/test/resources/application-test.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 6638504b2f..cd9a981aed 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,2 +1,3 @@ transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks -transport.lwm2m.security.key_store_password=server \ No newline at end of file +transport.lwm2m.security.key_store_password=server +edges.enabled=true \ No newline at end of file From e3fa441d6bc874b8471431327698f247c2952499 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Wed, 26 May 2021 10:58:47 +0300 Subject: [PATCH 23/40] Added redis session store --- .../TbL2M2MDtlsSessionInMemoryStore.java | 4 -- .../store/TbLwM2MDtlsSessionRedisStore.java | 66 +++++++++++++++++++ .../server/store/TbLwM2MDtlsSessionStore.java | 3 - .../server/store/TbLwM2mStoreFactory.java | 6 ++ 4 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java index af9d9ee89a..f5aa7d2e5c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java @@ -15,14 +15,10 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; import java.util.concurrent.ConcurrentHashMap; -@Component -@TbLwM2mTransportComponent public class TbL2M2MDtlsSessionInMemoryStore implements TbLwM2MDtlsSessionStore { private final ConcurrentHashMap store = new ConcurrentHashMap<>(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java new file mode 100644 index 0000000000..b1c4b85e2a --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java @@ -0,0 +1,66 @@ +/** + * 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.server.store; + +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; + +public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore { + + private static final String SESSION_EP = "SESSION#EP#"; + RedisConnectionFactory connectionFactory; + + public TbLwM2MDtlsSessionRedisStore(RedisConnectionFactory redisConnectionFactory) { + this.connectionFactory = redisConnectionFactory; + } + + @Override + public void put(String endpoint, TbX509DtlsSessionInfo msg) { + try (var c = connectionFactory.getConnection()) { + var msgJson = JacksonUtil.convertValue(msg, JsonNode.class); + if (msgJson != null) { + c.set(getKey(endpoint), msgJson.toString().getBytes()); + } else { + throw new RuntimeException("Problem with serialization of message: " + msg.toString()); + } + } + } + + @Override + public TbX509DtlsSessionInfo get(String endpoint) { + try (var c = connectionFactory.getConnection()) { + var data = c.get(getKey(endpoint)); + if (data != null) { + return JacksonUtil.fromString(new String(data), TbX509DtlsSessionInfo.class); + } else { + return null; + } + } + } + + @Override + public void remove(String endpoint) { + try (var c = connectionFactory.getConnection()) { + c.del(getKey(endpoint)); + } + } + + private byte[] getKey(String endpoint) { + return (SESSION_EP + endpoint).getBytes(); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java index 0c543d1025..3d5181232f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java @@ -24,9 +24,6 @@ public interface TbLwM2MDtlsSessionStore { TbX509DtlsSessionInfo get(String endpoint); - void remove(String endpoint); - //TODO: add way to delete the session by endpoint. - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java index 164a78b0a3..2c0c96212f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java @@ -60,4 +60,10 @@ public class TbLwM2mStoreFactory { new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new InMemorySecurityStore()); } + @Bean + private TbLwM2MDtlsSessionStore sessionStore() { + return redisConfiguration.isPresent() && useRedis ? + new TbLwM2MDtlsSessionRedisStore(redisConfiguration.get().redisConnectionFactory()) : new TbL2M2MDtlsSessionInMemoryStore(); + } + } From 17ae095d32a4f63cdb812607fcdc2fa534f0498e Mon Sep 17 00:00:00 2001 From: Hamza Slama Date: Wed, 12 May 2021 22:20:55 +0100 Subject: [PATCH 24/40] Remove Unused Variable --- .../java/org/thingsboard/server/controller/BaseController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 505416f4d6..e6384c010d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -160,8 +160,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId; public abstract class BaseController { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; - protected static final String DEFAULT_DASHBOARD = "defaultDashboardId"; protected static final String HOME_DASHBOARD = "homeDashboardId"; From 65091dd82bd7c0ae63e5d84c4a8fc25f457b7590 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 26 Apr 2021 09:49:16 +0300 Subject: [PATCH 25/40] Removed redundant lock on device creating for provisioning feature, because of it may cause stucking on high loaded systems --- .../device/DeviceProvisionServiceImpl.java | 42 +++++++------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 4ba4080a74..053b5150bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -65,7 +65,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; -import java.util.concurrent.locks.ReentrantLock; @Service @@ -78,8 +77,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private static final String DEVICE_PROVISION_STATE = "provisionState"; private static final String PROVISIONED_STATE = "provisioned"; - private final ReentrantLock deviceCreationLock = new ReentrantLock(); - @Autowired DeviceDao deviceDao; @@ -177,12 +174,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } private ProvisionResponse createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { - deviceCreationLock.lock(); - try { - return processCreateDevice(provisionRequest, profile); - } finally { - deviceCreationLock.unlock(); - } + return processCreateDevice(provisionRequest, profile); } private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) { @@ -191,28 +183,22 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { - Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); try { - if (device == null) { - if (StringUtils.isEmpty(provisionRequest.getDeviceName())) { - String newDeviceName = RandomStringUtils.randomAlphanumeric(20); - log.info("Device name not found in provision request. Generated name is: {}", newDeviceName); - provisionRequest.setDeviceName(newDeviceName); - } - Device savedDevice = deviceService.saveDevice(provisionRequest, profile); + if (StringUtils.isEmpty(provisionRequest.getDeviceName())) { + String newDeviceName = RandomStringUtils.randomAlphanumeric(20); + log.info("Device name not found in provision request. Generated name is: {}", newDeviceName); + provisionRequest.setDeviceName(newDeviceName); + } + Device savedDevice = deviceService.saveDevice(provisionRequest, profile); - deviceStateService.onDeviceAdded(savedDevice); - saveProvisionStateAttribute(savedDevice).get(); - pushDeviceCreatedEventToRuleEngine(savedDevice); - notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + deviceStateService.onDeviceAdded(savedDevice); + saveProvisionStateAttribute(savedDevice).get(); + pushDeviceCreatedEventToRuleEngine(savedDevice); + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); - return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS); - } else { - log.warn("[{}] The device is already provisioned!", device.getName()); - notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); - throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); - } - } catch (InterruptedException | ExecutionException e) { + return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS); + } catch (Exception e) { + log.warn("[{}] Error during device creation from provision request: [{}]", provisionRequest.getDeviceName(), provisionRequest, e); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } From 4d2a9ea1a7dd8ca074d9ad01329609bd6eba0993 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 30 Apr 2021 08:50:51 +0300 Subject: [PATCH 26/40] Added notification about device creating failed if device already exists --- .../server/service/device/DeviceProvisionServiceImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 053b5150bd..36f4ea06af 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -199,6 +199,10 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS); } catch (Exception e) { log.warn("[{}] Error during device creation from provision request: [{}]", provisionRequest.getDeviceName(), provisionRequest, e); + Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); + if (device != null) { + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + } throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } From dac9dda9b60a45f920da6f23e296e4b2a3ff3a8b Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 29 Apr 2021 17:29:58 +0300 Subject: [PATCH 27/40] Make remote js processing async --- .../service/script/RemoteJsInvokeService.java | 13 +++++++++---- application/src/main/resources/thingsboard.yml | 3 +++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 8c4b45cdf3..334a471973 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; @@ -39,6 +40,8 @@ import javax.annotation.PreDestroy; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; @@ -69,6 +72,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { private final AtomicInteger queueEvalMsgs = new AtomicInteger(0); private final AtomicInteger queueFailedMsgs = new AtomicInteger(0); private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0); + private final ExecutorService callbackExecutor = Executors.newFixedThreadPool( + Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("js-executor-remote-callback")); public RemoteJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { super(apiUsageStateService, apiUsageClient); @@ -139,7 +144,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { } queueFailedMsgs.incrementAndGet(); } - }, MoreExecutors.directExecutor()); + }, callbackExecutor); return Futures.transform(future, response -> { JsInvokeProtos.JsCompileResponse compilationResult = response.getValue().getCompileResponse(); UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB()); @@ -151,7 +156,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails()); throw new RuntimeException(compilationResult.getErrorDetails()); } - }, MoreExecutors.directExecutor()); + }, callbackExecutor); } @Override @@ -194,7 +199,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { } queueFailedMsgs.incrementAndGet(); } - }, MoreExecutors.directExecutor()); + }, callbackExecutor); return Futures.transform(future, response -> { JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse(); if (invokeResult.getSuccess()) { @@ -204,7 +209,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); throw new RuntimeException(invokeResult.getErrorDetails()); } - }, MoreExecutors.directExecutor()); + }, callbackExecutor); } @Override diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 7fde122cf6..6dce5c2d48 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -19,6 +19,9 @@ server: address: "${HTTP_BIND_ADDRESS:0.0.0.0}" # Server bind port port: "${HTTP_BIND_PORT:8080}" + tomcat: + # Maximum size of data that could be send over HTTP form POST request + max-http-form-post-size: "${MAX_HTTP_FORM_POST_SIZE:10000000}" # 10Mb # Server SSL configuration ssl: # Enable/disable SSL support From addfaa60a43624a4197e7cad5c3fd88fe645d2ea Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 11 May 2021 14:04:42 +0300 Subject: [PATCH 28/40] Remove config from main file --- application/src/main/resources/thingsboard.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 6dce5c2d48..7fde122cf6 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -19,9 +19,6 @@ server: address: "${HTTP_BIND_ADDRESS:0.0.0.0}" # Server bind port port: "${HTTP_BIND_PORT:8080}" - tomcat: - # Maximum size of data that could be send over HTTP form POST request - max-http-form-post-size: "${MAX_HTTP_FORM_POST_SIZE:10000000}" # 10Mb # Server SSL configuration ssl: # Enable/disable SSL support From acc9257af70651257db3245c7adb4df0a0051de7 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 6 May 2021 18:05:31 +0300 Subject: [PATCH 29/40] Fix for org.apache.kafka.common.KafkaException: javax.security.auth.login.LoginException: unable to find LoginModule class --- .../server/queue/kafka/TbKafkaProducerTemplate.java | 4 ++++ .../java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java index e22aad3de6..754d95cb1f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java @@ -55,6 +55,10 @@ public class TbKafkaProducerTemplate implements TbQueuePro @Builder private TbKafkaProducerTemplate(TbKafkaSettings settings, String defaultTopic, String clientId, TbQueueAdmin admin) { + // Ugly workaround to fix org.apache.kafka.common.KafkaException: javax.security.auth.login.LoginException: unable to find LoginModule class + // details: https://stackoverflow.com/questions/57574901/kafka-java-client-classloader-doesnt-find-sasl-scram-login-class + Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); + Properties props = settings.toProducerProps(); if (!StringUtils.isEmpty(clientId)) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 0ac03e1afb..55d61b1031 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -71,6 +71,10 @@ public class TbKafkaNode implements TbNode { @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + // Ugly workaround to fix org.apache.kafka.common.KafkaException: javax.security.auth.login.LoginException: unable to find LoginModule class + // details: https://stackoverflow.com/questions/57574901/kafka-java-client-classloader-doesnt-find-sasl-scram-login-class + Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); + this.config = TbNodeUtils.convert(configuration, TbKafkaNodeConfiguration.class); Properties properties = new Properties(); properties.put(ProducerConfig.CLIENT_ID_CONFIG, "producer-tb-kafka-node-" + ctx.getSelfId().getId().toString() + "-" + ctx.getServiceId()); From eae41d112f7cef82c4cc07dce4c4188f0c4f2e35 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 6 May 2021 18:10:51 +0300 Subject: [PATCH 30/40] Refactored --- .../server/queue/kafka/TbKafkaProducerTemplate.java | 8 ++++---- .../org/thingsboard/rule/engine/kafka/TbKafkaNode.java | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java index 754d95cb1f..8704631bc6 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java @@ -55,16 +55,16 @@ public class TbKafkaProducerTemplate implements TbQueuePro @Builder private TbKafkaProducerTemplate(TbKafkaSettings settings, String defaultTopic, String clientId, TbQueueAdmin admin) { - // Ugly workaround to fix org.apache.kafka.common.KafkaException: javax.security.auth.login.LoginException: unable to find LoginModule class - // details: https://stackoverflow.com/questions/57574901/kafka-java-client-classloader-doesnt-find-sasl-scram-login-class - Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); - Properties props = settings.toProducerProps(); if (!StringUtils.isEmpty(clientId)) { props.put(ProducerConfig.CLIENT_ID_CONFIG, clientId); } this.settings = settings; + + // Ugly workaround to fix org.apache.kafka.common.KafkaException: javax.security.auth.login.LoginException: unable to find LoginModule class + // details: https://stackoverflow.com/questions/57574901/kafka-java-client-classloader-doesnt-find-sasl-scram-login-class + Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); this.producer = new KafkaProducer<>(props); this.defaultTopic = defaultTopic; this.admin = admin; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 55d61b1031..df273dc8f8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -71,10 +71,6 @@ public class TbKafkaNode implements TbNode { @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { - // Ugly workaround to fix org.apache.kafka.common.KafkaException: javax.security.auth.login.LoginException: unable to find LoginModule class - // details: https://stackoverflow.com/questions/57574901/kafka-java-client-classloader-doesnt-find-sasl-scram-login-class - Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); - this.config = TbNodeUtils.convert(configuration, TbKafkaNodeConfiguration.class); Properties properties = new Properties(); properties.put(ProducerConfig.CLIENT_ID_CONFIG, "producer-tb-kafka-node-" + ctx.getSelfId().getId().toString() + "-" + ctx.getServiceId()); @@ -92,6 +88,9 @@ public class TbKafkaNode implements TbNode { addMetadataKeyValuesAsKafkaHeaders = BooleanUtils.toBooleanDefaultIfNull(config.isAddMetadataKeyValuesAsKafkaHeaders(), false); toBytesCharset = config.getKafkaHeadersCharset() != null ? Charset.forName(config.getKafkaHeadersCharset()) : StandardCharsets.UTF_8; try { + // Ugly workaround to fix org.apache.kafka.common.KafkaException: javax.security.auth.login.LoginException: unable to find LoginModule class + // details: https://stackoverflow.com/questions/57574901/kafka-java-client-classloader-doesnt-find-sasl-scram-login-class + Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); this.producer = new KafkaProducer<>(properties); } catch (Exception e) { throw new TbNodeException(e); From fbb8cb5985b5a495d449df3b6bc79a7321c68c48 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Wed, 12 May 2021 16:30:56 +0300 Subject: [PATCH 31/40] Rest Api Call Node fixes: throws null data for next node in message, added default header Content-Type --- .../java/org/thingsboard/rule/engine/rest/TbHttpClient.java | 3 ++- .../rule/engine/rest/TbRestApiCallNodeConfiguration.java | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index c485489954..7e6ba50a77 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -207,7 +207,8 @@ public class TbHttpClient { metaData.putValue(STATUS_CODE, response.getStatusCode().value() + ""); metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); response.getHeaders().toSingleValueMap().forEach(metaData::putValue); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, response.getBody()); + String body = response.getBody() == null ? "{}" : response.getBody(); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, body); } private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java index b3eb982287..a357efec31 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -17,6 +17,8 @@ package org.thingsboard.rule.engine.rest; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.credentials.AnonymousCredentials; import org.thingsboard.rule.engine.credentials.ClientCredentials; @@ -51,7 +53,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration Date: Fri, 21 May 2021 08:37:32 +0300 Subject: [PATCH 32/40] postgresql.driver.version upgrade to 42.2.20 to fix connection issue with Postgres 11 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 29a373f2e4..928dcfae83 100755 --- a/pom.xml +++ b/pom.xml @@ -94,7 +94,7 @@ 2.5.0 2.5.3 1.2.1 - 42.2.16 + 42.2.20 org/thingsboard/server/gen/**/*, org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* From ae010b78f24a7a810060bbc9f9f89839c0ed22e1 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 7 May 2021 09:55:56 +0300 Subject: [PATCH 33/40] added hamcrest dependency to the test scope. The purpose is assertThat and various Matchers with advanced output --- common/queue/pom.xml | 6 +++++- pom.xml | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 31ec44e8f7..2b1d87fc7b 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -120,12 +120,16 @@ org.apache.curator curator-recipes - junit junit test + + org.hamcrest + hamcrest + test + org.mockito mockito-core diff --git a/pom.xml b/pom.xml index 928dcfae83..e09dedf49f 100755 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,7 @@ 2.2.0 4.12 5.7.1 + 2.2 1.7.7 1.2.3 3.3.3 @@ -1371,6 +1372,12 @@ ${junit.version} test + + org.hamcrest + hamcrest + ${hamcrest.version} + test + org.junit.jupiter junit-jupiter-params From 2086bd2d2a9d11f984bda6ba5c326aeb236ed6a5 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 25 May 2021 08:27:11 +0300 Subject: [PATCH 34/40] fixed drop partition function in install script and added fix to upgrade script --- .../2.4.3/schema_update_psql_drop_partitions.sql | 2 +- .../server/install/ThingsboardInstallService.java | 3 +++ .../install/PsqlTsDatabaseUpgradeService.java | 6 ++++++ dao/src/main/resources/sql/schema-ts-psql.sql | 13 +++++++------ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql index 3c2d43e197..fcc5c6f232 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql @@ -43,7 +43,7 @@ BEGIN into max_customer_ttl; max_ttl := GREATEST(system_ttl, max_customer_ttl, max_tenant_ttl); if max_ttl IS NOT NULL AND max_ttl > 0 THEN - date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - (max_ttl / 1000)); + date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; IF partition_by_max_ttl_date IS NOT NULL THEN diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 8ea8b85017..267fb31ad7 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -193,6 +193,9 @@ public class ThingsboardInstallService { databaseEntitiesUpgradeService.upgradeDatabase("3.2.1"); case "3.2.2": log.info("Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ..."); + if (databaseTsUpgradeService != null) { + databaseTsUpgradeService.upgradeDatabase("3.2.2"); + } databaseEntitiesUpgradeService.upgradeDatabase("3.2.2"); dataUpdateService.updateData("3.2.2"); diff --git a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java index 8e6f4859e5..835b27b71c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java @@ -209,6 +209,12 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe executeQuery(conn, "DROP FUNCTION IF EXISTS delete_customer_records_from_ts_kv(character varying, character varying, bigint);"); } break; + case "3.2.2": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Load Drop Partitions functions ..."); + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3"); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql index da4ea0748b..5683cc0a17 100644 --- a/dao/src/main/resources/sql/schema-ts-psql.sql +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -63,7 +63,7 @@ BEGIN into max_customer_ttl; max_ttl := GREATEST(system_ttl, max_customer_ttl, max_tenant_ttl); if max_ttl IS NOT NULL AND max_ttl > 0 THEN - date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - (max_ttl / 1000)); + date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; IF partition_by_max_ttl_date IS NOT NULL THEN @@ -104,11 +104,12 @@ BEGIN END IF; END IF; END IF; - END IF; - IF partition_to_delete IS NOT NULL THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete; - EXECUTE format('DROP TABLE %I', partition_to_delete); - deleted := deleted + 1; + IF partition_to_delete IS NOT NULL THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete; + EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete); + partition_to_delete := NULL; + deleted := deleted + 1; + END IF; END IF; END LOOP; END IF; From c83c232166f4ec19e250eea7f1146a7e02ad31d0 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 27 May 2021 11:53:49 +0300 Subject: [PATCH 35/40] JsonConverter improvements (correct value conversion) --- .../TbLwM2MDtlsCertificateVerifier.java | 2 + .../transport/adaptor/JsonConverter.java | 67 +++++++++---------- .../src/test/java/JsonConverterTest.java | 36 ++++++++++ 3 files changed, 69 insertions(+), 36 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index e83532d2cc..7b81e733bc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -41,6 +41,7 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; +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.common.data.device.credentials.lwm2m.X509ClientCredentials; @@ -61,6 +62,7 @@ import java.util.concurrent.TimeUnit; @Slf4j @Component +@TbLwM2mTransportComponent @RequiredArgsConstructor public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVerifier { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 4db4aa9bd8..1abc513b23 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -228,29 +228,27 @@ public class JsonConverter { private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { String valueAsString = value.getAsString(); KeyValueProto.Builder builder = KeyValueProto.newBuilder().setKey(key); - if (valueAsString.contains("e") || valueAsString.contains("E")) { - //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String - var bd = new BigDecimal(valueAsString); - if (bd.stripTrailingZeros().scale() <= 0) { - try { - return builder.setType(KeyValueType.LONG_V).setLongV(bd.longValueExact()).build(); - } catch (ArithmeticException e) { - return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build(); + var bd = new BigDecimal(valueAsString); + if (bd.stripTrailingZeros().scale() <= 0) { + try { + return builder.setType(KeyValueType.LONG_V).setLongV(bd.longValueExact()).build(); + } catch (ArithmeticException e) { + if (isTypeCastEnabled) { + return builder.setType(KeyValueType.STRING_V).setStringV(bd.toPlainString()).build(); + } else { + throw new JsonSyntaxException("Big integer values are not supported!"); } - } else { - return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build(); } - } else if (valueAsString.contains(".")) { - return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(value.getAsDouble()).build(); } else { - try { - long longValue = Long.parseLong(value.getAsString()); - return builder.setType(KeyValueType.LONG_V).setLongV(longValue).build(); - } catch (NumberFormatException e) { - //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String - return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(new BigDecimal(valueAsString).doubleValue()).build(); + if (bd.scale() <= 16) { + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build(); + } else if (isTypeCastEnabled) { + return builder.setType(KeyValueType.STRING_V).setStringV(bd.toPlainString()).build(); + } else { + throw new JsonSyntaxException("Big integer values are not supported!"); } } + } public static TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(JsonElement json, int requestId) throws JsonSyntaxException { @@ -261,27 +259,24 @@ public class JsonConverter { private static void parseNumericValue(List result, Entry valueEntry, JsonPrimitive value) { String valueAsString = value.getAsString(); String key = valueEntry.getKey(); - if (valueAsString.contains("e") || valueAsString.contains("E")) { - //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String - var bd = new BigDecimal(valueAsString); - if (bd.stripTrailingZeros().scale() <= 0) { - try { - result.add(new LongDataEntry(key, bd.longValueExact())); - } catch (ArithmeticException e) { - result.add(new DoubleDataEntry(key, bd.doubleValue())); + var bd = new BigDecimal(valueAsString); + if (bd.stripTrailingZeros().scale() <= 0) { + try { + result.add(new LongDataEntry(key, bd.longValueExact())); + } catch (ArithmeticException e) { + if (isTypeCastEnabled) { + result.add(new StringDataEntry(key, bd.toPlainString())); + } else { + throw new JsonSyntaxException("Big integer values are not supported!"); } - } else { - result.add(new DoubleDataEntry(key, bd.doubleValue())); } - } else if (valueAsString.contains(".")) { - result.add(new DoubleDataEntry(key, value.getAsDouble())); } else { - try { - long longValue = Long.parseLong(value.getAsString()); - result.add(new LongDataEntry(key, longValue)); - } catch (NumberFormatException e) { - //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String - result.add(new DoubleDataEntry(key, new BigDecimal(valueAsString).doubleValue())); + if (bd.scale() <= 16) { + result.add(new DoubleDataEntry(key, bd.doubleValue())); + } else if (isTypeCastEnabled) { + result.add(new StringDataEntry(key, bd.toPlainString())); + } else { + throw new JsonSyntaxException("Big integer values are not supported!"); } } } diff --git a/common/transport/transport-api/src/test/java/JsonConverterTest.java b/common/transport/transport-api/src/test/java/JsonConverterTest.java index dc28b268f5..5aa5c28c0e 100644 --- a/common/transport/transport-api/src/test/java/JsonConverterTest.java +++ b/common/transport/transport-api/src/test/java/JsonConverterTest.java @@ -15,7 +15,9 @@ */ import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -28,6 +30,11 @@ public class JsonConverterTest { private static final JsonParser JSON_PARSER = new JsonParser(); + @Before + public void before() { + JsonConverter.setTypeCastEnabled(true); + } + @Test public void testParseBigDecimalAsLong() { var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1E+1}"), 0L); @@ -58,4 +65,33 @@ public class JsonConverterTest { Assert.assertEquals(11L, result.get(0L).get(0).getLongValue().get().longValue()); } + @Test + public void testParseBigDecimalAsStringOutOfLongRange() { + var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 9.9701010061400066E19}"), 0L); + Assert.assertEquals("99701010061400066000", result.get(0L).get(0).getStrValue().get()); + } + + @Test + public void testParseBigDecimalAsStringOutOfLongRange2() { + var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 99701010061400066001}"), 0L); + Assert.assertEquals("99701010061400066001", result.get(0L).get(0).getStrValue().get()); + } + + @Test + public void testParseBigDecimalAsStringOutOfLongRange3() { + var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1E19}"), 0L); + Assert.assertEquals("10000000000000000000", result.get(0L).get(0).getStrValue().get()); + } + + @Test(expected = JsonSyntaxException.class) + public void testParseBigDecimalOutOfLongRangeWithoutParsing() { + JsonConverter.setTypeCastEnabled(false); + JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 89701010051400054084}"), 0L); + } + + @Test(expected = JsonSyntaxException.class) + public void testParseBigDecimalOutOfLongRangeWithoutParsing2() { + JsonConverter.setTypeCastEnabled(false); + JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 9.9701010061400066E19}"), 0L); + } } From 40a994038e6d4532a37251a0c77ca80ce602e1b6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 27 May 2021 12:34:27 +0300 Subject: [PATCH 36/40] Restore DeviceProfileEntity fields --- .../thingsboard/server/dao/model/sql/DeviceProfileEntity.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index f2124ecbab..ec440526a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -117,6 +117,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.setCreatedTime(deviceProfile.getCreatedTime()); this.name = deviceProfile.getName(); this.type = deviceProfile.getType(); + this.image = deviceProfile.getImage(); this.transportType = deviceProfile.getTransportType(); this.provisionType = deviceProfile.getProvisionType(); this.description = deviceProfile.getDescription(); @@ -125,6 +126,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl if (deviceProfile.getDefaultRuleChainId() != null) { this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId(); } + if (deviceProfile.getDefaultDashboardId() != null) { + this.defaultDashboardId = deviceProfile.getDefaultDashboardId().getId(); + } this.defaultQueueName = deviceProfile.getDefaultQueueName(); this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); if (deviceProfile.getFirmwareId() != null) { From 78ae90da7a78486dbb78846be015cf85a6b95824 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 27 May 2021 12:53:36 +0300 Subject: [PATCH 37/40] Restore WARN log level --- application/src/test/resources/logback.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/test/resources/logback.xml b/application/src/test/resources/logback.xml index 81d213b42d..69e5d98193 100644 --- a/application/src/test/resources/logback.xml +++ b/application/src/test/resources/logback.xml @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@ - + From f994f41186b4a7421a587219b114271a09027adf Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 27 May 2021 12:38:03 +0300 Subject: [PATCH 38/40] added missing 3.2.2 switch cases to database ts upgrade services --- .../service/install/CassandraTsDatabaseUpgradeService.java | 1 + .../service/install/TimescaleTsDatabaseUpgradeService.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java index 0a64a59a08..87af6155e1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java @@ -51,6 +51,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase case "2.5.0": case "3.1.1": case "3.2.1": + case "3.2.2": break; default: throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java index 417e3f8f1a..40e714d411 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -184,6 +184,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.2.1"); } break; + case "3.2.2": + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } From 4f6ef2f2446b4b6ff1a8e41809bcebee53070346 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 27 May 2021 13:05:50 +0300 Subject: [PATCH 39/40] NullPointer exception fix --- .../thingsboard/server/service/edge/rpc/EdgeGrpcService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index 70033f10e1..ab0857f863 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -172,7 +172,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i @Override public void onEdgeEvent(EdgeId edgeId) { log.trace("[{}] onEdgeEvent", edgeId.getId()); - if (!sessionNewEvents.get(edgeId)) { + if (Boolean.FALSE.equals(sessionNewEvents.get(edgeId))) { log.trace("[{}] set session new events flag to true", edgeId.getId()); sessionNewEvents.put(edgeId, true); } @@ -204,7 +204,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i if (sessions.containsKey(edgeId)) { ScheduledFuture schedule = scheduler.schedule(() -> { try { - if (sessionNewEvents.get(edgeId)) { + if (Boolean.TRUE.equals(sessionNewEvents.get(edgeId))) { log.trace("[{}] Set session new events flag to false", edgeId.getId()); sessionNewEvents.put(edgeId, false); session.processEdgeEvents(); From 16f548d3dda5b943c4ccaf74d97468bbb567cf10 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 27 May 2021 14:27:31 +0300 Subject: [PATCH 40/40] Fix behavior for simple double values --- .../server/common/transport/adaptor/JsonConverter.java | 8 ++++++-- .../transport-api/src/test/java/JsonConverterTest.java | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 1abc513b23..473429d526 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -229,7 +229,7 @@ public class JsonConverter { String valueAsString = value.getAsString(); KeyValueProto.Builder builder = KeyValueProto.newBuilder().setKey(key); var bd = new BigDecimal(valueAsString); - if (bd.stripTrailingZeros().scale() <= 0) { + if (bd.stripTrailingZeros().scale() <= 0 && !isSimpleDouble(valueAsString)) { try { return builder.setType(KeyValueType.LONG_V).setLongV(bd.longValueExact()).build(); } catch (ArithmeticException e) { @@ -251,6 +251,10 @@ public class JsonConverter { } + private static boolean isSimpleDouble(String valueAsString) { + return valueAsString.contains(".") && !valueAsString.contains("E") && !valueAsString.contains("e"); + } + public static TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(JsonElement json, int requestId) throws JsonSyntaxException { JsonObject object = json.getAsJsonObject(); return TransportProtos.ToServerRpcRequestMsg.newBuilder().setRequestId(requestId).setMethodName(object.get("method").getAsString()).setParams(GSON.toJson(object.get("params"))).build(); @@ -260,7 +264,7 @@ public class JsonConverter { String valueAsString = value.getAsString(); String key = valueEntry.getKey(); var bd = new BigDecimal(valueAsString); - if (bd.stripTrailingZeros().scale() <= 0) { + if (bd.stripTrailingZeros().scale() <= 0 && !isSimpleDouble(valueAsString)) { try { result.add(new LongDataEntry(key, bd.longValueExact())); } catch (ArithmeticException e) { diff --git a/common/transport/transport-api/src/test/java/JsonConverterTest.java b/common/transport/transport-api/src/test/java/JsonConverterTest.java index 5aa5c28c0e..2c1a3c5551 100644 --- a/common/transport/transport-api/src/test/java/JsonConverterTest.java +++ b/common/transport/transport-api/src/test/java/JsonConverterTest.java @@ -53,6 +53,12 @@ public class JsonConverterTest { Assert.assertEquals(10L, result.get(0).getLongValue().get().longValue()); } + @Test + public void testParseAsDoubleWithZero() { + var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 42.0}"), 0L); + Assert.assertEquals(42.0, result.get(0L).get(0).getDoubleValue().get(), 0.0); + } + @Test public void testParseAsDouble() { var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1.1}"), 0L);