Commit 00ba087609dfcfb67986397dc186b5200c69f9a3

Authored by 云中非
1 parent fdb96a88

feat: 设备传输服务扩展协议TCP

Showing 36 changed files with 4702 additions and 1 deletions
... ... @@ -708,6 +708,59 @@ transport:
708 708 # Skip certificate validity check for client certificates.
709 709 skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
710 710 # Local CoAP transport parameters
  711 + tcp:
  712 + # Enable/disable tcp transport protocol.
  713 + enabled: "${TCP_ENABLED:true}"
  714 + bind_address: "${TCP_BIND_ADDRESS:0.0.0.0}"
  715 + bind_port: "${TCP_BIND_PORT:8088}"
  716 + # Enable proxy protocol support. Disabled by default. If enabled, supports both v1 and v2.
  717 + # Useful to get the real IP address of the client in the logs and for rate limits.
  718 + proxy_enabled: "${TCP_PROXY_PROTOCOL_ENABLED:false}"
  719 + timeout: "${TCP_TIMEOUT:10000}"
  720 + msg_queue_size_per_device_limit: "${TCP_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism
  721 + netty:
  722 + leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}"
  723 + boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
  724 + worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
  725 + max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
  726 + so_keep_alive: "${NETTY_SO_KEEPALIVE:false}"
  727 + # TCP SSL configuration
  728 + ssl:
  729 + # Enable/disable SSL support
  730 + enabled: "${TCP_SSL_ENABLED:false}"
  731 + # TCP SSL bind address
  732 + bind_address: "${TCP_SSL_BIND_ADDRESS:0.0.0.0}"
  733 + # TCP SSL bind port
  734 + bind_port: "${TCP_SSL_BIND_PORT:8888}"
  735 + # SSL protocol: See https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms
  736 + protocol: "${TCP_SSL_PROTOCOL:TLSv1.2}"
  737 + # Server SSL credentials
  738 + credentials:
  739 + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
  740 + type: "${TCP_SSL_CREDENTIALS_TYPE:PEM}"
  741 + # PEM server credentials
  742 + pem:
  743 + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key)
  744 + cert_file: "${TCP_SSL_PEM_CERT:tcpserver.pem}"
  745 + # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file;
  746 + key_file: "${TCP_SSL_PEM_KEY:tcpserver_key.pem}"
  747 + # Server certificate private key password (optional)
  748 + key_password: "${TCP_SSL_PEM_KEY_PASSWORD:server_key_password}"
  749 + # Keystore server credentials
  750 + keystore:
  751 + # Type of the key store
  752 + type: "${TCP_SSL_KEY_STORE_TYPE:JKS}"
  753 + # Path to the key store that holds the SSL certificate
  754 + store_file: "${TCP_SSL_KEY_STORE:tcpserver.jks}"
  755 + # Password used to access the key store
  756 + store_password: "${TCP_SSL_KEY_STORE_PASSWORD:server_ks_password}"
  757 + # Optional alias of the private key; If not set, the platform will load the first private key from the keystore;
  758 + key_alias: "${TCP_SSL_KEY_ALIAS:}"
  759 + # Optional password to access the private key. If not set, the platform will attempt to load the private keys that are not protected with the password;
  760 + key_password: "${TCP_SSL_KEY_PASSWORD:server_key_password}"
  761 + # Skip certificate validity check for client certificates.
  762 + skip_validity_check_for_client_cert: "${TCP_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
  763 + # Local CoAP transport parameters
711 764 coap:
712 765 # Enable/disable coap transport protocol.
713 766 enabled: "${COAP_ENABLED:true}"
... ...
... ... @@ -43,6 +43,8 @@ public class DataConstants {
43 43 public static final String COAP_TRANSPORT_NAME = "COAP";
44 44 public static final String LWM2M_TRANSPORT_NAME = "LWM2M";
45 45 public static final String MQTT_TRANSPORT_NAME = "MQTT";
  46 + //Thingskit function
  47 + public static final String TCP_TRANSPORT_NAME = "TCP";
46 48 public static final String HTTP_TRANSPORT_NAME = "HTTP";
47 49 public static final String SNMP_TRANSPORT_NAME = "SNMP";
48 50
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data;
18 18 public enum DeviceTransportType {
19 19 DEFAULT,
20 20 MQTT,
  21 + TCP,
21 22 COAP,
22 23 LWM2M,
23 24 SNMP
... ...
... ... @@ -37,6 +37,7 @@
37 37 <modules>
38 38 <module>transport-api</module>
39 39 <module>mqtt</module>
  40 + <module>tcp</module>
40 41 <module>http</module>
41 42 <module>coap</module>
42 43 <module>lwm2m</module>
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2022 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  20 + <modelVersion>4.0.0</modelVersion>
  21 + <parent>
  22 + <groupId>org.thingsboard.common</groupId>
  23 + <version>3.3.4-SNAPSHOT</version>
  24 + <artifactId>transport</artifactId>
  25 + </parent>
  26 + <groupId>org.thingsboard.common.transport</groupId>
  27 + <artifactId>tcp</artifactId>
  28 + <packaging>jar</packaging>
  29 +
  30 + <name>Thingsboard TPC Transport Common</name>
  31 + <url>https://thingsboard.io</url>
  32 +
  33 + <properties>
  34 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  35 + <main.dir>${basedir}/../../..</main.dir>
  36 + </properties>
  37 +
  38 + <dependencies>
  39 + <dependency>
  40 + <groupId>org.thingsboard.common.transport</groupId>
  41 + <artifactId>transport-api</artifactId>
  42 + </dependency>
  43 + <dependency>
  44 + <groupId>io.netty</groupId>
  45 + <artifactId>netty-all</artifactId>
  46 + </dependency>
  47 + <dependency>
  48 + <groupId>io.netty</groupId>
  49 + <artifactId>netty-tcnative-boringssl-static</artifactId>
  50 + </dependency>
  51 + <dependency>
  52 + <groupId>org.springframework</groupId>
  53 + <artifactId>spring-context-support</artifactId>
  54 + </dependency>
  55 + <dependency>
  56 + <groupId>org.springframework</groupId>
  57 + <artifactId>spring-context</artifactId>
  58 + </dependency>
  59 + <dependency>
  60 + <groupId>org.slf4j</groupId>
  61 + <artifactId>slf4j-api</artifactId>
  62 + </dependency>
  63 + <dependency>
  64 + <groupId>org.slf4j</groupId>
  65 + <artifactId>log4j-over-slf4j</artifactId>
  66 + </dependency>
  67 + <dependency>
  68 + <groupId>ch.qos.logback</groupId>
  69 + <artifactId>logback-core</artifactId>
  70 + </dependency>
  71 + <dependency>
  72 + <groupId>ch.qos.logback</groupId>
  73 + <artifactId>logback-classic</artifactId>
  74 + </dependency>
  75 + <dependency>
  76 + <groupId>com.google.guava</groupId>
  77 + <artifactId>guava</artifactId>
  78 + </dependency>
  79 + <dependency>
  80 + <groupId>com.google.code.findbugs</groupId>
  81 + <artifactId>jsr305</artifactId>
  82 + <version>3.0.1</version>
  83 + <optional>true</optional>
  84 + </dependency>
  85 + <dependency>
  86 + <groupId>org.springframework.boot</groupId>
  87 + <artifactId>spring-boot-starter-test</artifactId>
  88 + <scope>test</scope>
  89 + </dependency>
  90 + <dependency>
  91 + <groupId>org.junit.vintage</groupId>
  92 + <artifactId>junit-vintage-engine</artifactId>
  93 + <scope>test</scope>
  94 + </dependency>
  95 + <dependency>
  96 + <groupId>org.awaitility</groupId>
  97 + <artifactId>awaitility</artifactId>
  98 + <scope>test</scope>
  99 + </dependency>
  100 + </dependencies>
  101 +
  102 +</project>
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp;
  17 +
  18 +import io.netty.handler.ssl.SslHandler;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.beans.factory.annotation.Qualifier;
  22 +import org.springframework.beans.factory.annotation.Value;
  23 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  24 +import org.springframework.boot.context.properties.ConfigurationProperties;
  25 +import org.springframework.context.annotation.Bean;
  26 +import org.springframework.stereotype.Component;
  27 +import org.springframework.util.StringUtils;
  28 +import org.thingsboard.server.common.data.DeviceTransportType;
  29 +import org.thingsboard.server.common.msg.EncryptionUtil;
  30 +import org.thingsboard.server.common.transport.TransportService;
  31 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  32 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  33 +import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
  34 +import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig;
  35 +import org.thingsboard.server.common.transport.util.SslUtil;
  36 +import org.thingsboard.server.gen.transport.TransportProtos;
  37 +
  38 +import javax.net.ssl.KeyManager;
  39 +import javax.net.ssl.KeyManagerFactory;
  40 +import javax.net.ssl.SSLContext;
  41 +import javax.net.ssl.SSLEngine;
  42 +import javax.net.ssl.TrustManager;
  43 +import javax.net.ssl.TrustManagerFactory;
  44 +import javax.net.ssl.X509TrustManager;
  45 +import java.security.cert.CertificateEncodingException;
  46 +import java.security.cert.CertificateException;
  47 +import java.security.cert.X509Certificate;
  48 +import java.util.concurrent.CountDownLatch;
  49 +import java.util.concurrent.TimeUnit;
  50 +
  51 +/**
  52 + * Created by valerii.sosliuk on 11/6/16.
  53 + */
  54 +@Slf4j
  55 +@Component("TcpSslHandlerProvider")
  56 +@ConditionalOnProperty(prefix = "transport.tcp.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
  57 +public class TcpSslHandlerProvider {
  58 +
  59 + @Value("${transport.tcp.ssl.protocol}")
  60 + private String sslProtocol;
  61 +
  62 + @Autowired
  63 + private TransportService transportService;
  64 +
  65 + @Bean
  66 + @ConfigurationProperties(prefix = "transport.tcp.ssl.credentials")
  67 + public SslCredentialsConfig tcpSslCredentials() {
  68 + return new SslCredentialsConfig("tcp SSL Credentials", false);
  69 + }
  70 +
  71 + @Autowired
  72 + @Qualifier("tcpSslCredentials")
  73 + private SslCredentialsConfig tcpSslCredentialsConfig;
  74 +
  75 + private SSLContext sslContext;
  76 +
  77 + public SslHandler getSslHandler() {
  78 + if (sslContext == null) {
  79 + sslContext = createSslContext();
  80 + }
  81 + SSLEngine sslEngine = sslContext.createSSLEngine();
  82 + sslEngine.setUseClientMode(false);
  83 + sslEngine.setNeedClientAuth(false);
  84 + sslEngine.setWantClientAuth(true);
  85 + sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols());
  86 + sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
  87 + sslEngine.setEnableSessionCreation(true);
  88 + return new SslHandler(sslEngine);
  89 + }
  90 +
  91 + private SSLContext createSslContext() {
  92 + try {
  93 + SslCredentials sslCredentials = this.tcpSslCredentialsConfig.getCredentials();
  94 + TrustManagerFactory tmFactory = sslCredentials.createTrustManagerFactory();
  95 + KeyManagerFactory kmf = sslCredentials.createKeyManagerFactory();
  96 +
  97 + KeyManager[] km = kmf.getKeyManagers();
  98 + TrustManager x509wrapped = getX509TrustManager(tmFactory);
  99 + TrustManager[] tm = {x509wrapped};
  100 + if (StringUtils.isEmpty(sslProtocol)) {
  101 + sslProtocol = "TLS";
  102 + }
  103 + SSLContext sslContext = SSLContext.getInstance(sslProtocol);
  104 + sslContext.init(km, tm, null);
  105 + return sslContext;
  106 + } catch (Exception e) {
  107 + log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e);
  108 + throw new RuntimeException("Failed to get SSL context", e);
  109 + }
  110 + }
  111 +
  112 + private TrustManager getX509TrustManager(TrustManagerFactory tmf) throws Exception {
  113 + X509TrustManager x509Tm = null;
  114 + for (TrustManager tm : tmf.getTrustManagers()) {
  115 + if (tm instanceof X509TrustManager) {
  116 + x509Tm = (X509TrustManager) tm;
  117 + break;
  118 + }
  119 + }
  120 + return new ThingsboardTcpX509TrustManager(x509Tm, transportService);
  121 + }
  122 +
  123 + static class ThingsboardTcpX509TrustManager implements X509TrustManager {
  124 +
  125 + private final X509TrustManager trustManager;
  126 + private TransportService transportService;
  127 +
  128 + ThingsboardTcpX509TrustManager(X509TrustManager trustManager, TransportService transportService) {
  129 + this.trustManager = trustManager;
  130 + this.transportService = transportService;
  131 + }
  132 +
  133 + @Override
  134 + public X509Certificate[] getAcceptedIssuers() {
  135 + return trustManager.getAcceptedIssuers();
  136 + }
  137 +
  138 + @Override
  139 + public void checkServerTrusted(X509Certificate[] chain,
  140 + String authType) throws CertificateException {
  141 + trustManager.checkServerTrusted(chain, authType);
  142 + }
  143 +
  144 + @Override
  145 + public void checkClientTrusted(X509Certificate[] chain,
  146 + String authType) throws CertificateException {
  147 + String credentialsBody = null;
  148 + for (X509Certificate cert : chain) {
  149 + try {
  150 + String strCert = SslUtil.getCertificateString(cert);
  151 + String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
  152 + final String[] credentialsBodyHolder = new String[1];
  153 + CountDownLatch latch = new CountDownLatch(1);
  154 + transportService.process(DeviceTransportType.TCP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
  155 + new TransportServiceCallback<ValidateDeviceCredentialsResponse>() {
  156 + @Override
  157 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  158 + if (!StringUtils.isEmpty(msg.getCredentials())) {
  159 + credentialsBodyHolder[0] = msg.getCredentials();
  160 + }
  161 + latch.countDown();
  162 + }
  163 +
  164 + @Override
  165 + public void onError(Throwable e) {
  166 + log.error(e.getMessage(), e);
  167 + latch.countDown();
  168 + }
  169 + });
  170 + latch.await(10, TimeUnit.SECONDS);
  171 + if (strCert.equals(credentialsBodyHolder[0])) {
  172 + credentialsBody = credentialsBodyHolder[0];
  173 + break;
  174 + }
  175 + } catch (InterruptedException | CertificateEncodingException e) {
  176 + log.error(e.getMessage(), e);
  177 + }
  178 + }
  179 + if (credentialsBody == null) {
  180 + throw new CertificateException("Invalid Device Certificate");
  181 + }
  182 + }
  183 + }
  184 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp;
  17 +
  18 +import io.netty.handler.ssl.SslHandler;
  19 +import lombok.Getter;
  20 +import lombok.Setter;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.springframework.beans.factory.annotation.Value;
  24 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  25 +import org.springframework.stereotype.Component;
  26 +import org.thingsboard.server.common.transport.TransportContext;
  27 +import org.thingsboard.server.transport.tcp.adaptors.JsonTcpAdaptor;
  28 +import org.thingsboard.server.transport.tcp.adaptors.ProtoTcpAdaptor;
  29 +
  30 +import javax.annotation.PostConstruct;
  31 +import java.net.InetSocketAddress;
  32 +import java.util.concurrent.atomic.AtomicInteger;
  33 +
  34 +/**
  35 + * Created by ashvayka on 04.10.18.
  36 + */
  37 +@Slf4j
  38 +@Component
  39 +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.tcp.enabled}'=='true')")
  40 +public class TcpTransportContext extends TransportContext {
  41 +
  42 + @Getter
  43 + @Autowired(required = false)
  44 + private TcpSslHandlerProvider sslHandlerProvider;
  45 +
  46 + @Getter
  47 + @Autowired
  48 + private JsonTcpAdaptor jsonMqttAdaptor;
  49 +
  50 + @Getter
  51 + @Autowired
  52 + private ProtoTcpAdaptor protoMqttAdaptor;
  53 +
  54 + @Getter
  55 + @Value("${transport.mqtt.netty.max_payload_size}")
  56 + private Integer maxPayloadSize;
  57 +
  58 + @Getter
  59 + @Value("${transport.mqtt.ssl.skip_validity_check_for_client_cert:false}")
  60 + private boolean skipValidityCheckForClientCert;
  61 +
  62 + @Getter
  63 + @Setter
  64 + private SslHandler sslHandler;
  65 +
  66 + @Getter
  67 + @Value("${transport.mqtt.msg_queue_size_per_device_limit:100}")
  68 + private int messageQueueSizePerDeviceLimit;
  69 +
  70 + @Getter
  71 + @Value("${transport.mqtt.timeout:10000}")
  72 + private long timeout;
  73 +
  74 + @Getter
  75 + @Value("${transport.mqtt.proxy_enabled:false}")
  76 + private boolean proxyEnabled;
  77 +
  78 + private final AtomicInteger connectionsCounter = new AtomicInteger();
  79 +
  80 + @PostConstruct
  81 + public void init() {
  82 + super.init();
  83 + transportService.createGaugeStats("openConnections", connectionsCounter);
  84 + }
  85 +
  86 + public void channelRegistered() {
  87 + connectionsCounter.incrementAndGet();
  88 + }
  89 +
  90 + public void channelUnregistered() {
  91 + connectionsCounter.decrementAndGet();
  92 + }
  93 +
  94 + public boolean checkAddress(InetSocketAddress address) {
  95 + return rateLimitService.checkAddress(address);
  96 + }
  97 +
  98 + public void onAuthSuccess(InetSocketAddress address) {
  99 + rateLimitService.onAuthSuccess(address);
  100 + }
  101 +
  102 + public void onAuthFailure(InetSocketAddress address) {
  103 + rateLimitService.onAuthFailure(address);
  104 + }
  105 +
  106 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.google.gson.JsonParseException;
  20 +import io.netty.channel.ChannelFuture;
  21 +import io.netty.channel.ChannelHandlerContext;
  22 +import io.netty.channel.ChannelInboundHandlerAdapter;
  23 +import io.netty.handler.codec.mqtt.MqttConnAckMessage;
  24 +import io.netty.handler.codec.mqtt.MqttConnAckVariableHeader;
  25 +import io.netty.handler.codec.mqtt.MqttConnectMessage;
  26 +import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
  27 +import io.netty.handler.codec.mqtt.MqttFixedHeader;
  28 +import io.netty.handler.codec.mqtt.MqttMessage;
  29 +import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader;
  30 +import io.netty.handler.codec.mqtt.MqttPubAckMessage;
  31 +import io.netty.handler.codec.mqtt.MqttPublishMessage;
  32 +import io.netty.handler.codec.mqtt.MqttQoS;
  33 +import io.netty.handler.codec.mqtt.MqttSubAckMessage;
  34 +import io.netty.handler.codec.mqtt.MqttSubAckPayload;
  35 +import io.netty.handler.codec.mqtt.MqttSubscribeMessage;
  36 +import io.netty.handler.codec.mqtt.MqttTopicSubscription;
  37 +import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage;
  38 +import io.netty.handler.ssl.SslHandler;
  39 +import io.netty.util.CharsetUtil;
  40 +import io.netty.util.ReferenceCountUtil;
  41 +import io.netty.util.concurrent.Future;
  42 +import io.netty.util.concurrent.GenericFutureListener;
  43 +import lombok.extern.slf4j.Slf4j;
  44 +import org.apache.commons.lang3.StringUtils;
  45 +import org.thingsboard.server.common.data.DataConstants;
  46 +import org.thingsboard.server.common.data.Device;
  47 +import org.thingsboard.server.common.data.DeviceProfile;
  48 +import org.thingsboard.server.common.data.DeviceTransportType;
  49 +import org.thingsboard.server.common.data.TransportPayloadType;
  50 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  51 +import org.thingsboard.server.common.data.id.DeviceId;
  52 +import org.thingsboard.server.common.data.id.OtaPackageId;
  53 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  54 +import org.thingsboard.server.common.data.rpc.RpcStatus;
  55 +import org.thingsboard.server.common.msg.EncryptionUtil;
  56 +import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
  57 +import org.thingsboard.server.common.transport.SessionMsgListener;
  58 +import org.thingsboard.server.common.transport.TransportService;
  59 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  60 +import org.thingsboard.server.common.transport.adaptor.AdaptorException;
  61 +import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
  62 +import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
  63 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  64 +import org.thingsboard.server.common.transport.service.DefaultTransportService;
  65 +import org.thingsboard.server.common.transport.service.SessionMetaData;
  66 +import org.thingsboard.server.common.transport.util.SslUtil;
  67 +import org.thingsboard.server.gen.transport.TransportProtos;
  68 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
  69 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
  70 +import org.thingsboard.server.queue.scheduler.SchedulerComponent;
  71 +import org.thingsboard.server.transport.tcp.adaptors.TcpTransportAdaptor;
  72 +import org.thingsboard.server.transport.tcp.session.DeviceSessionCtx;
  73 +import org.thingsboard.server.transport.tcp.session.GatewaySessionHandler;
  74 +import org.thingsboard.server.transport.tcp.session.MqttTopicMatcher;
  75 +
  76 +import javax.net.ssl.SSLPeerUnverifiedException;
  77 +import java.io.IOException;
  78 +import java.net.InetSocketAddress;
  79 +import java.security.cert.Certificate;
  80 +import java.security.cert.X509Certificate;
  81 +import java.util.ArrayList;
  82 +import java.util.List;
  83 +import java.util.Optional;
  84 +import java.util.UUID;
  85 +import java.util.concurrent.Callable;
  86 +import java.util.concurrent.ConcurrentHashMap;
  87 +import java.util.concurrent.ConcurrentMap;
  88 +import java.util.concurrent.TimeUnit;
  89 +import java.util.regex.Matcher;
  90 +import java.util.regex.Pattern;
  91 +
  92 +import static com.amazonaws.util.StringUtils.UTF8;
  93 +import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
  94 +import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
  95 +import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK;
  96 +import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT;
  97 +import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP;
  98 +import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK;
  99 +import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK;
  100 +import static io.netty.handler.codec.mqtt.MqttMessageType.UNSUBACK;
  101 +import static io.netty.handler.codec.mqtt.MqttQoS.AT_LEAST_ONCE;
  102 +import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
  103 +import static io.netty.handler.codec.mqtt.MqttQoS.FAILURE;
  104 +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED;
  105 +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN;
  106 +
  107 +/**
  108 + * @author Andrew Shvayka
  109 + */
  110 +@Slf4j
  111 +public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener<Future<? super Void>>, SessionMsgListener {
  112 +
  113 + private static final Pattern FW_REQUEST_PATTERN = Pattern.compile(MqttTopics.DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN);
  114 + private static final Pattern SW_REQUEST_PATTERN = Pattern.compile(MqttTopics.DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN);
  115 +
  116 +
  117 + private static final String PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE";
  118 +
  119 + private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE;
  120 +
  121 + private final UUID sessionId;
  122 + private final TcpTransportContext context;
  123 + private final TransportService transportService;
  124 + private final SchedulerComponent scheduler;
  125 + private final SslHandler sslHandler;
  126 + private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
  127 +
  128 + final DeviceSessionCtx deviceSessionCtx;
  129 + volatile InetSocketAddress address;
  130 + volatile GatewaySessionHandler gatewaySessionHandler;
  131 +
  132 + private final ConcurrentHashMap<String, String> otaPackSessions;
  133 + private final ConcurrentHashMap<String, Integer> chunkSizes;
  134 + private final ConcurrentMap<Integer, TransportProtos.ToDeviceRpcRequestMsg> rpcAwaitingAck;
  135 +
  136 + private TopicType attrSubTopicType;
  137 + private TopicType rpcSubTopicType;
  138 + private TopicType attrReqTopicType;
  139 + private TopicType toServerRpcSubTopicType;
  140 +
  141 + TcpTransportHandler(TcpTransportContext context, SslHandler sslHandler) {
  142 + this.sessionId = UUID.randomUUID();
  143 + this.context = context;
  144 + this.transportService = context.getTransportService();
  145 + this.scheduler = context.getScheduler();
  146 + this.sslHandler = sslHandler;
  147 + this.mqttQoSMap = new ConcurrentHashMap<>();
  148 + this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap, context);
  149 + this.otaPackSessions = new ConcurrentHashMap<>();
  150 + this.chunkSizes = new ConcurrentHashMap<>();
  151 + this.rpcAwaitingAck = new ConcurrentHashMap<>();
  152 + }
  153 +
  154 + @Override
  155 + public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
  156 + super.channelRegistered(ctx);
  157 + context.channelRegistered();
  158 + }
  159 +
  160 + @Override
  161 + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
  162 + super.channelUnregistered(ctx);
  163 + context.channelUnregistered();
  164 + }
  165 +
  166 + @Override
  167 + public void channelRead(ChannelHandlerContext ctx, Object msg) {
  168 + log.trace("[{}] Processing msg: {}", sessionId, msg);
  169 + if (address == null) {
  170 + address = getAddress(ctx);
  171 + }
  172 + try {
  173 + if (msg instanceof MqttMessage) {
  174 + MqttMessage message = (MqttMessage) msg;
  175 + if (message.decoderResult().isSuccess()) {
  176 + processMqttMsg(ctx, message);
  177 + } else {
  178 + log.error("[{}] Message decoding failed: {}", sessionId, message.decoderResult().cause().getMessage());
  179 + ctx.close();
  180 + }
  181 + } else {
  182 + log.debug("[{}] Received non mqtt message: {}", sessionId, msg.getClass().getSimpleName());
  183 + ctx.close();
  184 + }
  185 + } finally {
  186 + ReferenceCountUtil.safeRelease(msg);
  187 + }
  188 + }
  189 +
  190 + InetSocketAddress getAddress(ChannelHandlerContext ctx) {
  191 + var address = ctx.channel().attr(TcpTransportService.ADDRESS).get();
  192 + if (address == null) {
  193 + log.trace("[{}] Received empty address.", ctx.channel().id());
  194 + InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress();
  195 + log.trace("[{}] Going to use address: {}", ctx.channel().id(), remoteAddress);
  196 + return remoteAddress;
  197 + } else {
  198 + log.trace("[{}] Received address: {}", ctx.channel().id(), address);
  199 + }
  200 + return address;
  201 + }
  202 +
  203 + void processMqttMsg(ChannelHandlerContext ctx, MqttMessage msg) {
  204 + if (msg.fixedHeader() == null) {
  205 + log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort());
  206 + ctx.close();
  207 + return;
  208 + }
  209 + deviceSessionCtx.setChannel(ctx);
  210 + if (CONNECT.equals(msg.fixedHeader().messageType())) {
  211 + processConnect(ctx, (MqttConnectMessage) msg);
  212 + } else if (deviceSessionCtx.isProvisionOnly()) {
  213 + processProvisionSessionMsg(ctx, msg);
  214 + } else {
  215 + enqueueRegularSessionMsg(ctx, msg);
  216 + }
  217 + }
  218 +
  219 + private void processProvisionSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
  220 + switch (msg.fixedHeader().messageType()) {
  221 + case PUBLISH:
  222 + MqttPublishMessage mqttMsg = (MqttPublishMessage) msg;
  223 + String topicName = mqttMsg.variableHeader().topicName();
  224 + int msgId = mqttMsg.variableHeader().packetId();
  225 + try {
  226 + if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) {
  227 + try {
  228 + TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg);
  229 + transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg));
  230 + log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);
  231 + } catch (Exception e) {
  232 + if (e instanceof JsonParseException || (e.getCause() != null && e.getCause() instanceof JsonParseException)) {
  233 + TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg);
  234 + transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg));
  235 + deviceSessionCtx.setProvisionPayloadType(TransportPayloadType.PROTOBUF);
  236 + log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);
  237 + } else {
  238 + throw e;
  239 + }
  240 + }
  241 + } else {
  242 + log.debug("[{}] Unsupported topic for provisioning requests: {}!", sessionId, topicName);
  243 + ctx.close();
  244 + }
  245 + } catch (RuntimeException e) {
  246 + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
  247 + ctx.close();
  248 + } catch (AdaptorException e) {
  249 + log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
  250 + ctx.close();
  251 + }
  252 + break;
  253 + case PINGREQ:
  254 + ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
  255 + break;
  256 + case DISCONNECT:
  257 + ctx.close();
  258 + break;
  259 + }
  260 + }
  261 +
  262 + void enqueueRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
  263 + final int queueSize = deviceSessionCtx.getMsgQueueSize();
  264 + if (queueSize >= context.getMessageQueueSizePerDeviceLimit()) {
  265 + log.info("Closing current session because msq queue size for device {} exceed limit {} with msgQueueSize counter {} and actual queue size {}",
  266 + deviceSessionCtx.getDeviceId(), context.getMessageQueueSizePerDeviceLimit(), queueSize, deviceSessionCtx.getMsgQueueSize());
  267 + ctx.close();
  268 + return;
  269 + }
  270 +
  271 + deviceSessionCtx.addToQueue(msg);
  272 + processMsgQueue(ctx); //Under the normal conditions the msg queue will contain 0 messages. Many messages will be processed on device connect event in separate thread pool
  273 + }
  274 +
  275 + void processMsgQueue(ChannelHandlerContext ctx) {
  276 + if (!deviceSessionCtx.isConnected()) {
  277 + log.trace("[{}][{}] Postpone processing msg due to device is not connected. Msg queue size is {}", sessionId, deviceSessionCtx.getDeviceId(), deviceSessionCtx.getMsgQueueSize());
  278 + return;
  279 + }
  280 + deviceSessionCtx.tryProcessQueuedMsgs(msg -> processRegularSessionMsg(ctx, msg));
  281 + }
  282 +
  283 + void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
  284 + switch (msg.fixedHeader().messageType()) {
  285 + case PUBLISH:
  286 + processPublish(ctx, (MqttPublishMessage) msg);
  287 + break;
  288 + case SUBSCRIBE:
  289 + processSubscribe(ctx, (MqttSubscribeMessage) msg);
  290 + break;
  291 + case UNSUBSCRIBE:
  292 + processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg);
  293 + break;
  294 + case PINGREQ:
  295 + if (checkConnected(ctx, msg)) {
  296 + ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
  297 + transportService.reportActivity(deviceSessionCtx.getSessionInfo());
  298 + }
  299 + break;
  300 + case DISCONNECT:
  301 + ctx.close();
  302 + break;
  303 + case PUBACK:
  304 + int msgId = ((MqttPubAckMessage) msg).variableHeader().messageId();
  305 + TransportProtos.ToDeviceRpcRequestMsg rpcRequest = rpcAwaitingAck.remove(msgId);
  306 + if (rpcRequest != null) {
  307 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY);
  308 + }
  309 + break;
  310 + default:
  311 + break;
  312 + }
  313 + }
  314 +
  315 + private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) {
  316 + if (!checkConnected(ctx, mqttMsg)) {
  317 + return;
  318 + }
  319 + String topicName = mqttMsg.variableHeader().topicName();
  320 + int msgId = mqttMsg.variableHeader().packetId();
  321 + log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);
  322 +
  323 + if (topicName.startsWith(MqttTopics.BASE_GATEWAY_API_TOPIC)) {
  324 + if (gatewaySessionHandler != null) {
  325 + handleGatewayPublishMsg(ctx, topicName, msgId, mqttMsg);
  326 + transportService.reportActivity(deviceSessionCtx.getSessionInfo());
  327 + }
  328 + } else {
  329 + processDevicePublish(ctx, mqttMsg, topicName, msgId);
  330 + }
  331 + }
  332 +
  333 + private void handleGatewayPublishMsg(ChannelHandlerContext ctx, String topicName, int msgId, MqttPublishMessage mqttMsg) {
  334 + try {
  335 + switch (topicName) {
  336 + case MqttTopics.GATEWAY_TELEMETRY_TOPIC:
  337 + gatewaySessionHandler.onDeviceTelemetry(mqttMsg);
  338 + break;
  339 + case MqttTopics.GATEWAY_CLAIM_TOPIC:
  340 + gatewaySessionHandler.onDeviceClaim(mqttMsg);
  341 + break;
  342 + case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC:
  343 + gatewaySessionHandler.onDeviceAttributes(mqttMsg);
  344 + break;
  345 + case MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC:
  346 + gatewaySessionHandler.onDeviceAttributesRequest(mqttMsg);
  347 + break;
  348 + case MqttTopics.GATEWAY_RPC_TOPIC:
  349 + gatewaySessionHandler.onDeviceRpcResponse(mqttMsg);
  350 + break;
  351 + case MqttTopics.GATEWAY_CONNECT_TOPIC:
  352 + gatewaySessionHandler.onDeviceConnect(mqttMsg);
  353 + break;
  354 + case MqttTopics.GATEWAY_DISCONNECT_TOPIC:
  355 + gatewaySessionHandler.onDeviceDisconnect(mqttMsg);
  356 + break;
  357 + default:
  358 + ack(ctx, msgId);
  359 + }
  360 + } catch (RuntimeException e) {
  361 + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
  362 + ctx.close();
  363 + } catch (AdaptorException e) {
  364 + log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
  365 + ctx.close();
  366 + }
  367 + }
  368 +
  369 + private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) {
  370 + try {
  371 + Matcher fwMatcher;
  372 + TcpTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor();
  373 + if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) {
  374 + TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
  375 + transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg));
  376 + } else if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) {
  377 + TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg);
  378 + transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg));
  379 + } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) {
  380 + TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
  381 + transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));
  382 + attrReqTopicType = TopicType.V1;
  383 + } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC)) {
  384 + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC);
  385 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg));
  386 + } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC)) {
  387 + TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC);
  388 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg));
  389 + toServerRpcSubTopicType = TopicType.V1;
  390 + } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) {
  391 + TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg);
  392 + transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg));
  393 + } else if ((fwMatcher = FW_REQUEST_PATTERN.matcher(topicName)).find()) {
  394 + getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.FIRMWARE);
  395 + } else if ((fwMatcher = SW_REQUEST_PATTERN.matcher(topicName)).find()) {
  396 + getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.SOFTWARE);
  397 + } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC)) {
  398 + TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg);
  399 + transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg));
  400 + } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC)) {
  401 + TransportProtos.PostTelemetryMsg postTelemetryMsg = context.getJsonMqttAdaptor().convertToPostTelemetry(deviceSessionCtx, mqttMsg);
  402 + transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg));
  403 + } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_PROTO_TOPIC)) {
  404 + TransportProtos.PostTelemetryMsg postTelemetryMsg = context.getProtoMqttAdaptor().convertToPostTelemetry(deviceSessionCtx, mqttMsg);
  405 + transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg));
  406 + } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC)) {
  407 + TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
  408 + transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg));
  409 + } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC)) {
  410 + TransportProtos.PostAttributeMsg postAttributeMsg = context.getJsonMqttAdaptor().convertToPostAttributes(deviceSessionCtx, mqttMsg);
  411 + transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg));
  412 + } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC)) {
  413 + TransportProtos.PostAttributeMsg postAttributeMsg = context.getProtoMqttAdaptor().convertToPostAttributes(deviceSessionCtx, mqttMsg);
  414 + transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg));
  415 + } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC)) {
  416 + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = context.getJsonMqttAdaptor().convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC);
  417 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg));
  418 + } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_PROTO_TOPIC)) {
  419 + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = context.getProtoMqttAdaptor().convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_PROTO_TOPIC);
  420 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg));
  421 + } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_TOPIC)) {
  422 + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_TOPIC);
  423 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg));
  424 + } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_SHORT_JSON_TOPIC)) {
  425 + TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = context.getJsonMqttAdaptor().convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_JSON_TOPIC);
  426 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg));
  427 + toServerRpcSubTopicType = TopicType.V2_JSON;
  428 + } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_SHORT_PROTO_TOPIC)) {
  429 + TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = context.getProtoMqttAdaptor().convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_PROTO_TOPIC);
  430 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg));
  431 + toServerRpcSubTopicType = TopicType.V2_PROTO;
  432 + } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_SHORT_TOPIC)) {
  433 + TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_TOPIC);
  434 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg));
  435 + toServerRpcSubTopicType = TopicType.V2;
  436 + } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX)) {
  437 + TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getJsonMqttAdaptor().convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX);
  438 + transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));
  439 + attrReqTopicType = TopicType.V2_JSON;
  440 + } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX)) {
  441 + TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getProtoMqttAdaptor().convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX);
  442 + transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));
  443 + attrReqTopicType = TopicType.V2_PROTO;
  444 + } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX)) {
  445 + TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX);
  446 + transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));
  447 + attrReqTopicType = TopicType.V2;
  448 + } else {
  449 + transportService.reportActivity(deviceSessionCtx.getSessionInfo());
  450 + ack(ctx, msgId);
  451 + }
  452 + } catch (AdaptorException e) {
  453 + log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
  454 + log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId);
  455 + ctx.close();
  456 + }
  457 + }
  458 +
  459 + private void getOtaPackageCallback(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, int msgId, Matcher fwMatcher, OtaPackageType type) {
  460 + String payload = mqttMsg.content().toString(UTF8);
  461 + int chunkSize = StringUtils.isNotEmpty(payload) ? Integer.parseInt(payload) : 0;
  462 + String requestId = fwMatcher.group("requestId");
  463 + int chunk = Integer.parseInt(fwMatcher.group("chunk"));
  464 +
  465 + if (chunkSize > 0) {
  466 + this.chunkSizes.put(requestId, chunkSize);
  467 + } else {
  468 + chunkSize = chunkSizes.getOrDefault(requestId, 0);
  469 + }
  470 +
  471 + if (chunkSize > context.getMaxPayloadSize()) {
  472 + sendOtaPackageError(ctx, PAYLOAD_TOO_LARGE);
  473 + return;
  474 + }
  475 +
  476 + String otaPackageId = otaPackSessions.get(requestId);
  477 +
  478 + if (otaPackageId != null) {
  479 + sendOtaPackage(ctx, mqttMsg.variableHeader().packetId(), otaPackageId, requestId, chunkSize, chunk, type);
  480 + } else {
  481 + TransportProtos.SessionInfoProto sessionInfo = deviceSessionCtx.getSessionInfo();
  482 + TransportProtos.GetOtaPackageRequestMsg getOtaPackageRequestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder()
  483 + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
  484 + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
  485 + .setTenantIdMSB(sessionInfo.getTenantIdMSB())
  486 + .setTenantIdLSB(sessionInfo.getTenantIdLSB())
  487 + .setType(type.name())
  488 + .build();
  489 + transportService.process(deviceSessionCtx.getSessionInfo(), getOtaPackageRequestMsg,
  490 + new OtaPackageCallback(ctx, msgId, getOtaPackageRequestMsg, requestId, chunkSize, chunk));
  491 + }
  492 + }
  493 +
  494 + private void ack(ChannelHandlerContext ctx, int msgId) {
  495 + if (msgId > 0) {
  496 + ctx.writeAndFlush(createMqttPubAckMsg(msgId));
  497 + }
  498 + }
  499 +
  500 + private <T> TransportServiceCallback<Void> getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) {
  501 + return new TransportServiceCallback<>() {
  502 + @Override
  503 + public void onSuccess(Void dummy) {
  504 + log.trace("[{}] Published msg: {}", sessionId, msg);
  505 + ack(ctx, msgId);
  506 + }
  507 +
  508 + @Override
  509 + public void onError(Throwable e) {
  510 + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
  511 + ctx.close();
  512 + }
  513 + };
  514 + }
  515 +
  516 + private class DeviceProvisionCallback implements TransportServiceCallback<ProvisionDeviceResponseMsg> {
  517 + private final ChannelHandlerContext ctx;
  518 + private final int msgId;
  519 + private final TransportProtos.ProvisionDeviceRequestMsg msg;
  520 +
  521 + DeviceProvisionCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.ProvisionDeviceRequestMsg msg) {
  522 + this.ctx = ctx;
  523 + this.msgId = msgId;
  524 + this.msg = msg;
  525 + }
  526 +
  527 + @Override
  528 + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) {
  529 + log.trace("[{}] Published msg: {}", sessionId, msg);
  530 + ack(ctx, msgId);
  531 + try {
  532 + if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) {
  533 + deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  534 + } else {
  535 + deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  536 + }
  537 + scheduler.schedule((Callable<ChannelFuture>) ctx::close, 60, TimeUnit.SECONDS);
  538 + } catch (Exception e) {
  539 + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
  540 + }
  541 + }
  542 +
  543 + @Override
  544 + public void onError(Throwable e) {
  545 + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
  546 + ctx.close();
  547 + }
  548 + }
  549 +
  550 + private class OtaPackageCallback implements TransportServiceCallback<TransportProtos.GetOtaPackageResponseMsg> {
  551 + private final ChannelHandlerContext ctx;
  552 + private final int msgId;
  553 + private final TransportProtos.GetOtaPackageRequestMsg msg;
  554 + private final String requestId;
  555 + private final int chunkSize;
  556 + private final int chunk;
  557 +
  558 + OtaPackageCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.GetOtaPackageRequestMsg msg, String requestId, int chunkSize, int chunk) {
  559 + this.ctx = ctx;
  560 + this.msgId = msgId;
  561 + this.msg = msg;
  562 + this.requestId = requestId;
  563 + this.chunkSize = chunkSize;
  564 + this.chunk = chunk;
  565 + }
  566 +
  567 + @Override
  568 + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) {
  569 + if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) {
  570 + OtaPackageId firmwareId = new OtaPackageId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()));
  571 + otaPackSessions.put(requestId, firmwareId.toString());
  572 + sendOtaPackage(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk, OtaPackageType.valueOf(response.getType()));
  573 + } else {
  574 + sendOtaPackageError(ctx, response.getResponseStatus().toString());
  575 + }
  576 + }
  577 +
  578 + @Override
  579 + public void onError(Throwable e) {
  580 + log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e);
  581 + ctx.close();
  582 + }
  583 + }
  584 +
  585 + private void sendOtaPackage(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk, OtaPackageType type) {
  586 + log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId);
  587 + ack(ctx, msgId);
  588 + try {
  589 + byte[] firmwareChunk = context.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk);
  590 + deviceSessionCtx.getPayloadAdaptor()
  591 + .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk, type)
  592 + .ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  593 + } catch (Exception e) {
  594 + log.trace("[{}] Failed to send firmware response!", sessionId, e);
  595 + }
  596 + }
  597 +
  598 + private void sendOtaPackageError(ChannelHandlerContext ctx, String error) {
  599 + log.warn("[{}] {}", sessionId, error);
  600 + deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx
  601 + .getPayloadAdaptor()
  602 + .createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC, error.getBytes()));
  603 + ctx.close();
  604 + }
  605 +
  606 + private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
  607 + if (!checkConnected(ctx, mqttMsg)) {
  608 + return;
  609 + }
  610 + log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId());
  611 + List<Integer> grantedQoSList = new ArrayList<>();
  612 + boolean activityReported = false;
  613 + for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) {
  614 + String topic = subscription.topicName();
  615 + MqttQoS reqQoS = subscription.qualityOfService();
  616 + try {
  617 + switch (topic) {
  618 + case MqttTopics.DEVICE_ATTRIBUTES_TOPIC: {
  619 + processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V1);
  620 + activityReported = true;
  621 + break;
  622 + }
  623 + case MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC: {
  624 + processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2);
  625 + activityReported = true;
  626 + break;
  627 + }
  628 + case MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC: {
  629 + processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_JSON);
  630 + activityReported = true;
  631 + break;
  632 + }
  633 + case MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC: {
  634 + processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_PROTO);
  635 + activityReported = true;
  636 + break;
  637 + }
  638 + case MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC: {
  639 + processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V1);
  640 + activityReported = true;
  641 + break;
  642 + }
  643 + case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC: {
  644 + processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2);
  645 + activityReported = true;
  646 + break;
  647 + }
  648 + case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC: {
  649 + processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_JSON);
  650 + activityReported = true;
  651 + break;
  652 + }
  653 + case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC: {
  654 + processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_PROTO);
  655 + activityReported = true;
  656 + break;
  657 + }
  658 + case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC:
  659 + case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_TOPIC:
  660 + case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_JSON_TOPIC:
  661 + case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_PROTO_TOPIC:
  662 + case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
  663 + case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC:
  664 + case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC:
  665 + case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC:
  666 + case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC:
  667 + case MqttTopics.GATEWAY_RPC_TOPIC:
  668 + case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
  669 + case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
  670 + case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC:
  671 + case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC:
  672 + case MqttTopics.DEVICE_SOFTWARE_RESPONSES_TOPIC:
  673 + case MqttTopics.DEVICE_SOFTWARE_ERROR_TOPIC:
  674 + registerSubQoS(topic, grantedQoSList, reqQoS);
  675 + break;
  676 + default:
  677 + log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS);
  678 + grantedQoSList.add(FAILURE.value());
  679 + break;
  680 + }
  681 + } catch (Exception e) {
  682 + log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS, e);
  683 + grantedQoSList.add(FAILURE.value());
  684 + }
  685 + }
  686 + if (!activityReported) {
  687 + transportService.reportActivity(deviceSessionCtx.getSessionInfo());
  688 + }
  689 + ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList));
  690 + }
  691 +
  692 + private void processRpcSubscribe(List<Integer> grantedQoSList, String topic, MqttQoS reqQoS, TopicType topicType) {
  693 + transportService.process(deviceSessionCtx.getSessionInfo(), TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null);
  694 + rpcSubTopicType = topicType;
  695 + registerSubQoS(topic, grantedQoSList, reqQoS);
  696 + }
  697 +
  698 + private void processAttributesSubscribe(List<Integer> grantedQoSList, String topic, MqttQoS reqQoS, TopicType topicType) {
  699 + transportService.process(deviceSessionCtx.getSessionInfo(), TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null);
  700 + attrSubTopicType = topicType;
  701 + registerSubQoS(topic, grantedQoSList, reqQoS);
  702 + }
  703 +
  704 + private void registerSubQoS(String topic, List<Integer> grantedQoSList, MqttQoS reqQoS) {
  705 + grantedQoSList.add(getMinSupportedQos(reqQoS));
  706 + mqttQoSMap.put(new MqttTopicMatcher(topic), getMinSupportedQos(reqQoS));
  707 + }
  708 +
  709 + private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) {
  710 + if (!checkConnected(ctx, mqttMsg)) {
  711 + return;
  712 + }
  713 + boolean activityReported = false;
  714 + log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId());
  715 + for (String topicName : mqttMsg.payload().topics()) {
  716 + mqttQoSMap.remove(new MqttTopicMatcher(topicName));
  717 + try {
  718 + switch (topicName) {
  719 + case MqttTopics.DEVICE_ATTRIBUTES_TOPIC:
  720 + case MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC:
  721 + case MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC:
  722 + case MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC: {
  723 + transportService.process(deviceSessionCtx.getSessionInfo(),
  724 + TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), null);
  725 + activityReported = true;
  726 + break;
  727 + }
  728 + case MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC:
  729 + case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC:
  730 + case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC:
  731 + case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC: {
  732 + transportService.process(deviceSessionCtx.getSessionInfo(),
  733 + TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), null);
  734 + activityReported = true;
  735 + break;
  736 + }
  737 + case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC:
  738 + case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_TOPIC:
  739 + case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_JSON_TOPIC:
  740 + case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_PROTO_TOPIC:
  741 + case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
  742 + case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC:
  743 + case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC:
  744 + case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC:
  745 + case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC:
  746 + case MqttTopics.GATEWAY_RPC_TOPIC:
  747 + case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
  748 + case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
  749 + case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC:
  750 + case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC:
  751 + case MqttTopics.DEVICE_SOFTWARE_RESPONSES_TOPIC:
  752 + case MqttTopics.DEVICE_SOFTWARE_ERROR_TOPIC: {
  753 + activityReported = true;
  754 + break;
  755 + }
  756 + }
  757 + } catch (Exception e) {
  758 + log.debug("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName);
  759 + }
  760 + }
  761 + if (!activityReported) {
  762 + transportService.reportActivity(deviceSessionCtx.getSessionInfo());
  763 + }
  764 + ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId()));
  765 + }
  766 +
  767 + private MqttMessage createUnSubAckMessage(int msgId) {
  768 + MqttFixedHeader mqttFixedHeader =
  769 + new MqttFixedHeader(UNSUBACK, false, AT_MOST_ONCE, false, 0);
  770 + MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId);
  771 + return new MqttMessage(mqttFixedHeader, mqttMessageIdVariableHeader);
  772 + }
  773 +
  774 + void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) {
  775 + log.debug("[{}][{}] Processing connect msg for client: {}!", address, sessionId, msg.payload().clientIdentifier());
  776 + String userName = msg.payload().userName();
  777 + String clientId = msg.payload().clientIdentifier();
  778 + if (DataConstants.PROVISION.equals(userName) || DataConstants.PROVISION.equals(clientId)) {
  779 + deviceSessionCtx.setProvisionOnly(true);
  780 + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED, msg));
  781 + } else {
  782 + X509Certificate cert;
  783 + if (sslHandler != null && (cert = getX509Certificate()) != null) {
  784 + processX509CertConnect(ctx, cert, msg);
  785 + } else {
  786 + processAuthTokenConnect(ctx, msg);
  787 + }
  788 + }
  789 + }
  790 +
  791 + private void processAuthTokenConnect(ChannelHandlerContext ctx, MqttConnectMessage connectMessage) {
  792 + String userName = connectMessage.payload().userName();
  793 + log.debug("[{}][{}] Processing connect msg for client with user name: {}!", address, sessionId, userName);
  794 + TransportProtos.ValidateBasicMqttCredRequestMsg.Builder request = TransportProtos.ValidateBasicMqttCredRequestMsg.newBuilder()
  795 + .setClientId(connectMessage.payload().clientIdentifier());
  796 + if (userName != null) {
  797 + request.setUserName(userName);
  798 + }
  799 + byte[] passwordBytes = connectMessage.payload().passwordInBytes();
  800 + if (passwordBytes != null) {
  801 + String password = new String(passwordBytes, CharsetUtil.UTF_8);
  802 + request.setPassword(password);
  803 + }
  804 + transportService.process(DeviceTransportType.MQTT, request.build(),
  805 + new TransportServiceCallback<>() {
  806 + @Override
  807 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  808 + onValidateDeviceResponse(msg, ctx, connectMessage);
  809 + }
  810 +
  811 + @Override
  812 + public void onError(Throwable e) {
  813 + log.trace("[{}] Failed to process credentials: {}", address, userName, e);
  814 + ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE, connectMessage));
  815 + ctx.close();
  816 + }
  817 + });
  818 + }
  819 +
  820 + private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert, MqttConnectMessage connectMessage) {
  821 + try {
  822 + if (!context.isSkipValidityCheckForClientCert()) {
  823 + cert.checkValidity();
  824 + }
  825 + String strCert = SslUtil.getCertificateString(cert);
  826 + String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
  827 + transportService.process(DeviceTransportType.MQTT, ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
  828 + new TransportServiceCallback<>() {
  829 + @Override
  830 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  831 + onValidateDeviceResponse(msg, ctx, connectMessage);
  832 + }
  833 +
  834 + @Override
  835 + public void onError(Throwable e) {
  836 + log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e);
  837 + ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE, connectMessage));
  838 + ctx.close();
  839 + }
  840 + });
  841 + } catch (Exception e) {
  842 + context.onAuthFailure(address);
  843 + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED, connectMessage));
  844 + log.trace("[{}] X509 auth failure: {}", sessionId, address, e);
  845 + ctx.close();
  846 + }
  847 + }
  848 +
  849 + private X509Certificate getX509Certificate() {
  850 + try {
  851 + Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates();
  852 + if (certChain.length > 0) {
  853 + return (X509Certificate) certChain[0];
  854 + }
  855 + } catch (SSLPeerUnverifiedException e) {
  856 + log.warn(e.getMessage());
  857 + return null;
  858 + }
  859 + return null;
  860 + }
  861 +
  862 + private MqttConnAckMessage createMqttConnAckMsg(MqttConnectReturnCode returnCode, MqttConnectMessage msg) {
  863 + MqttFixedHeader mqttFixedHeader =
  864 + new MqttFixedHeader(CONNACK, false, AT_MOST_ONCE, false, 0);
  865 + MqttConnAckVariableHeader mqttConnAckVariableHeader =
  866 + new MqttConnAckVariableHeader(returnCode, !msg.variableHeader().isCleanSession());
  867 + return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader);
  868 + }
  869 +
  870 + @Override
  871 + public void channelReadComplete(ChannelHandlerContext ctx) {
  872 + ctx.flush();
  873 + }
  874 +
  875 + @Override
  876 + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
  877 + log.error("[{}] Unexpected Exception", sessionId, cause);
  878 + ctx.close();
  879 + if (cause instanceof OutOfMemoryError) {
  880 + log.error("Received critical error. Going to shutdown the service.");
  881 + System.exit(1);
  882 + }
  883 + }
  884 +
  885 + private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) {
  886 + MqttFixedHeader mqttFixedHeader =
  887 + new MqttFixedHeader(SUBACK, false, AT_MOST_ONCE, false, 0);
  888 + MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId);
  889 + MqttSubAckPayload mqttSubAckPayload = new MqttSubAckPayload(grantedQoSList);
  890 + return new MqttSubAckMessage(mqttFixedHeader, mqttMessageIdVariableHeader, mqttSubAckPayload);
  891 + }
  892 +
  893 + private static int getMinSupportedQos(MqttQoS reqQoS) {
  894 + return Math.min(reqQoS.value(), MAX_SUPPORTED_QOS_LVL.value());
  895 + }
  896 +
  897 + public static MqttPubAckMessage createMqttPubAckMsg(int requestId) {
  898 + MqttFixedHeader mqttFixedHeader =
  899 + new MqttFixedHeader(PUBACK, false, AT_MOST_ONCE, false, 0);
  900 + MqttMessageIdVariableHeader mqttMsgIdVariableHeader =
  901 + MqttMessageIdVariableHeader.from(requestId);
  902 + return new MqttPubAckMessage(mqttFixedHeader, mqttMsgIdVariableHeader);
  903 + }
  904 +
  905 + private boolean checkConnected(ChannelHandlerContext ctx, MqttMessage msg) {
  906 + if (deviceSessionCtx.isConnected()) {
  907 + return true;
  908 + } else {
  909 + log.info("[{}] Closing current session due to invalid msg order: {}", sessionId, msg);
  910 + return false;
  911 + }
  912 + }
  913 +
  914 + private void checkGatewaySession(SessionMetaData sessionMetaData) {
  915 + TransportDeviceInfo device = deviceSessionCtx.getDeviceInfo();
  916 + try {
  917 + JsonNode infoNode = context.getMapper().readTree(device.getAdditionalInfo());
  918 + if (infoNode != null) {
  919 + JsonNode gatewayNode = infoNode.get("gateway");
  920 + if (gatewayNode != null && gatewayNode.asBoolean()) {
  921 + gatewaySessionHandler = new GatewaySessionHandler(deviceSessionCtx, sessionId);
  922 + if (infoNode.has(DefaultTransportService.OVERWRITE_ACTIVITY_TIME) && infoNode.get(DefaultTransportService.OVERWRITE_ACTIVITY_TIME).isBoolean()) {
  923 + sessionMetaData.setOverwriteActivityTime(infoNode.get(DefaultTransportService.OVERWRITE_ACTIVITY_TIME).asBoolean());
  924 + }
  925 + }
  926 + }
  927 + } catch (IOException e) {
  928 + log.trace("[{}][{}] Failed to fetch device additional info", sessionId, device.getDeviceName(), e);
  929 + }
  930 + }
  931 +
  932 + @Override
  933 + public void operationComplete(Future<? super Void> future) throws Exception {
  934 + log.trace("[{}] Channel closed!", sessionId);
  935 + doDisconnect();
  936 + }
  937 +
  938 + public void doDisconnect() {
  939 + if (deviceSessionCtx.isConnected()) {
  940 + log.debug("[{}] Client disconnected!", sessionId);
  941 + transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null);
  942 + transportService.deregisterSession(deviceSessionCtx.getSessionInfo());
  943 + if (gatewaySessionHandler != null) {
  944 + gatewaySessionHandler.onGatewayDisconnect();
  945 + }
  946 + deviceSessionCtx.setDisconnected();
  947 + }
  948 + deviceSessionCtx.release();
  949 + }
  950 +
  951 +
  952 + private void onValidateDeviceResponse(ValidateDeviceCredentialsResponse msg, ChannelHandlerContext ctx, MqttConnectMessage connectMessage) {
  953 + if (!msg.hasDeviceInfo()) {
  954 + context.onAuthFailure(address);
  955 + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED, connectMessage));
  956 + ctx.close();
  957 + } else {
  958 + context.onAuthSuccess(address);
  959 + deviceSessionCtx.setDeviceInfo(msg.getDeviceInfo());
  960 + deviceSessionCtx.setDeviceProfile(msg.getDeviceProfile());
  961 + deviceSessionCtx.setSessionInfo(SessionInfoCreator.create(msg, context, sessionId));
  962 + transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_OPEN, new TransportServiceCallback<Void>() {
  963 + @Override
  964 + public void onSuccess(Void msg) {
  965 + SessionMetaData sessionMetaData = transportService.registerAsyncSession(deviceSessionCtx.getSessionInfo(), TcpTransportHandler.this);
  966 + checkGatewaySession(sessionMetaData);
  967 + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED, connectMessage));
  968 + deviceSessionCtx.setConnected(true);
  969 + log.debug("[{}] Client connected!", sessionId);
  970 + transportService.getCallbackExecutor().execute(() -> processMsgQueue(ctx)); //this callback will execute in Producer worker thread and hard or blocking work have to be submitted to the separate thread.
  971 + }
  972 +
  973 + @Override
  974 + public void onError(Throwable e) {
  975 + if (e instanceof TbRateLimitsException) {
  976 + log.trace("[{}] Failed to submit session event: {}", sessionId, e.getMessage());
  977 + } else {
  978 + log.warn("[{}] Failed to submit session event", sessionId, e);
  979 + }
  980 + ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE, connectMessage));
  981 + ctx.close();
  982 + }
  983 + });
  984 + }
  985 + }
  986 +
  987 + @Override
  988 + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) {
  989 + log.trace("[{}] Received get attributes response", sessionId);
  990 + String topicBase = attrReqTopicType.getAttributesResponseTopicBase();
  991 + TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(attrReqTopicType);
  992 + try {
  993 + adaptor.convertToPublish(deviceSessionCtx, response, topicBase).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  994 + } catch (Exception e) {
  995 + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
  996 + }
  997 + }
  998 +
  999 + @Override
  1000 + public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg notification) {
  1001 + log.trace("[{}] Received attributes update notification to device", sessionId);
  1002 + String topic = attrSubTopicType.getAttributesSubTopic();
  1003 + TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(attrSubTopicType);
  1004 + try {
  1005 + adaptor.convertToPublish(deviceSessionCtx, notification, topic).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  1006 + } catch (Exception e) {
  1007 + log.trace("[{}] Failed to convert device attributes update to MQTT msg", sessionId, e);
  1008 + }
  1009 + }
  1010 +
  1011 + @Override
  1012 + public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) {
  1013 + log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage());
  1014 + deviceSessionCtx.getChannel().close();
  1015 + }
  1016 +
  1017 + @Override
  1018 + public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
  1019 + log.trace("[{}] Received RPC command to device", sessionId);
  1020 + String baseTopic = rpcSubTopicType.getRpcRequestTopicBase();
  1021 + TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(rpcSubTopicType);
  1022 + try {
  1023 + adaptor.convertToPublish(deviceSessionCtx, rpcRequest, baseTopic).ifPresent(payload -> {
  1024 + int msgId = ((MqttPublishMessage) payload).variableHeader().packetId();
  1025 + if (isAckExpected(payload)) {
  1026 + rpcAwaitingAck.put(msgId, rpcRequest);
  1027 + context.getScheduler().schedule(() -> {
  1028 + TransportProtos.ToDeviceRpcRequestMsg msg = rpcAwaitingAck.remove(msgId);
  1029 + if (msg != null) {
  1030 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.TIMEOUT, TransportServiceCallback.EMPTY);
  1031 + }
  1032 + }, Math.max(0, Math.min(deviceSessionCtx.getContext().getTimeout(), rpcRequest.getExpirationTime() - System.currentTimeMillis())), TimeUnit.MILLISECONDS);
  1033 + }
  1034 + var cf = publish(payload, deviceSessionCtx);
  1035 + cf.addListener(result -> {
  1036 + if (result.cause() == null) {
  1037 + if (!isAckExpected(payload)) {
  1038 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY);
  1039 + } else if (rpcRequest.getPersisted()) {
  1040 + transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.SENT, TransportServiceCallback.EMPTY);
  1041 + }
  1042 + } else {
  1043 + // TODO: send error
  1044 + }
  1045 + });
  1046 + });
  1047 + } catch (Exception e) {
  1048 + transportService.process(deviceSessionCtx.getSessionInfo(),
  1049 + TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
  1050 + .setRequestId(rpcRequest.getRequestId()).setError("Failed to convert device RPC command to MQTT msg").build(), TransportServiceCallback.EMPTY);
  1051 + log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e);
  1052 + }
  1053 + }
  1054 +
  1055 + @Override
  1056 + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg rpcResponse) {
  1057 + log.trace("[{}] Received RPC response from server", sessionId);
  1058 + String baseTopic = toServerRpcSubTopicType.getRpcResponseTopicBase();
  1059 + TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(toServerRpcSubTopicType);
  1060 + try {
  1061 + adaptor.convertToPublish(deviceSessionCtx, rpcResponse, baseTopic).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  1062 + } catch (Exception e) {
  1063 + log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e);
  1064 + }
  1065 + }
  1066 +
  1067 + private ChannelFuture publish(MqttMessage message, DeviceSessionCtx deviceSessionCtx) {
  1068 + return deviceSessionCtx.getChannel().writeAndFlush(message);
  1069 + }
  1070 +
  1071 + private boolean isAckExpected(MqttMessage message) {
  1072 + return message.fixedHeader().qosLevel().value() > 0;
  1073 + }
  1074 +
  1075 + @Override
  1076 + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
  1077 + deviceSessionCtx.onDeviceProfileUpdate(sessionInfo, deviceProfile);
  1078 + }
  1079 +
  1080 + @Override
  1081 + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) {
  1082 + deviceSessionCtx.onDeviceUpdate(sessionInfo, device, deviceProfileOpt);
  1083 + }
  1084 +
  1085 + @Override
  1086 + public void onDeviceDeleted(DeviceId deviceId) {
  1087 + context.onAuthFailure(address);
  1088 + ChannelHandlerContext ctx = deviceSessionCtx.getChannel();
  1089 + ctx.close();
  1090 + }
  1091 +
  1092 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp;
  17 +
  18 +import io.netty.channel.ChannelInitializer;
  19 +import io.netty.channel.ChannelPipeline;
  20 +import io.netty.channel.socket.SocketChannel;
  21 +import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
  22 +import io.netty.handler.codec.mqtt.MqttDecoder;
  23 +import io.netty.handler.codec.mqtt.MqttEncoder;
  24 +import io.netty.handler.ssl.SslHandler;
  25 +import org.thingsboard.server.transport.tcp.limits.IpFilter;
  26 +import org.thingsboard.server.transport.tcp.limits.ProxyIpFilter;
  27 +
  28 +/**
  29 + * @author Andrew Shvayka
  30 + */
  31 +public class TcpTransportServerInitializer extends ChannelInitializer<SocketChannel> {
  32 +
  33 + private final TcpTransportContext context;
  34 + private final boolean sslEnabled;
  35 +
  36 + public TcpTransportServerInitializer(TcpTransportContext context, boolean sslEnabled) {
  37 + this.context = context;
  38 + this.sslEnabled = sslEnabled;
  39 + }
  40 +
  41 + @Override
  42 + public void initChannel(SocketChannel ch) {
  43 + ChannelPipeline pipeline = ch.pipeline();
  44 + SslHandler sslHandler = null;
  45 + if (context.isProxyEnabled()) {
  46 + pipeline.addLast("proxy", new HAProxyMessageDecoder());
  47 + pipeline.addLast("ipFilter", new ProxyIpFilter(context));
  48 + } else {
  49 + pipeline.addLast("ipFilter", new IpFilter(context));
  50 + }
  51 + if (sslEnabled && context.getSslHandlerProvider() != null) {
  52 + sslHandler = context.getSslHandlerProvider().getSslHandler();
  53 + pipeline.addLast(sslHandler);
  54 + }
  55 + pipeline.addLast("decoder", new MqttDecoder(context.getMaxPayloadSize()));
  56 + pipeline.addLast("encoder", MqttEncoder.INSTANCE);
  57 +
  58 + TcpTransportHandler handler = new TcpTransportHandler(context, sslHandler);
  59 +
  60 + pipeline.addLast(handler);
  61 + ch.closeFuture().addListener(handler);
  62 + }
  63 +
  64 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp;
  17 +
  18 +import io.netty.bootstrap.ServerBootstrap;
  19 +import io.netty.channel.Channel;
  20 +import io.netty.channel.ChannelOption;
  21 +import io.netty.channel.EventLoopGroup;
  22 +import io.netty.channel.nio.NioEventLoopGroup;
  23 +import io.netty.channel.socket.nio.NioServerSocketChannel;
  24 +import io.netty.util.AttributeKey;
  25 +import io.netty.util.ResourceLeakDetector;
  26 +import lombok.extern.slf4j.Slf4j;
  27 +import org.springframework.beans.factory.annotation.Autowired;
  28 +import org.springframework.beans.factory.annotation.Value;
  29 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  30 +import org.springframework.stereotype.Service;
  31 +import org.thingsboard.server.common.data.DataConstants;
  32 +import org.thingsboard.server.common.data.TbTransportService;
  33 +
  34 +import javax.annotation.PostConstruct;
  35 +import javax.annotation.PreDestroy;
  36 +import java.net.InetSocketAddress;
  37 +
  38 +/**
  39 + * @author Andrew Shvayka
  40 + */
  41 +@Service("TcpTransportService")
  42 +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.tcp.enabled}'=='true')")
  43 +@Slf4j
  44 +public class TcpTransportService implements TbTransportService {
  45 +
  46 + public static AttributeKey<InetSocketAddress> ADDRESS = AttributeKey.newInstance("SRC_ADDRESS");
  47 +
  48 + @Value("${transport.tcp.bind_address}")
  49 + private String host;
  50 + @Value("${transport.tcp.bind_port}")
  51 + private Integer port;
  52 +
  53 + @Value("${transport.tcp.ssl.enabled}")
  54 + private boolean sslEnabled;
  55 +
  56 + @Value("${transport.tcp.ssl.bind_address}")
  57 + private String sslHost;
  58 + @Value("${transport.tcp.ssl.bind_port}")
  59 + private Integer sslPort;
  60 +
  61 + @Value("${transport.tcp.netty.leak_detector_level}")
  62 + private String leakDetectorLevel;
  63 + @Value("${transport.tcp.netty.boss_group_thread_count}")
  64 + private Integer bossGroupThreadCount;
  65 + @Value("${transport.tcp.netty.worker_group_thread_count}")
  66 + private Integer workerGroupThreadCount;
  67 + @Value("${transport.tcp.netty.so_keep_alive}")
  68 + private boolean keepAlive;
  69 +
  70 + @Autowired
  71 + private TcpTransportContext context;
  72 +
  73 + private Channel serverChannel;
  74 + private Channel sslServerChannel;
  75 + private EventLoopGroup bossGroup;
  76 + private EventLoopGroup workerGroup;
  77 +
  78 + @PostConstruct
  79 + public void init() throws Exception {
  80 + log.info("Setting resource leak detector level to {}", leakDetectorLevel);
  81 + ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.valueOf(leakDetectorLevel.toUpperCase()));
  82 +
  83 + log.info("Starting TCP transport...");
  84 + bossGroup = new NioEventLoopGroup(bossGroupThreadCount);
  85 + workerGroup = new NioEventLoopGroup(workerGroupThreadCount);
  86 + ServerBootstrap b = new ServerBootstrap();
  87 + b.group(bossGroup, workerGroup)
  88 + .channel(NioServerSocketChannel.class)
  89 + .childHandler(new TcpTransportServerInitializer(context, false))
  90 + .childOption(ChannelOption.SO_KEEPALIVE, keepAlive);
  91 +
  92 + serverChannel = b.bind(host, port).sync().channel();
  93 + if (sslEnabled) {
  94 + b = new ServerBootstrap();
  95 + b.group(bossGroup, workerGroup)
  96 + .channel(NioServerSocketChannel.class)
  97 + .childHandler(new TcpTransportServerInitializer(context, true))
  98 + .childOption(ChannelOption.SO_KEEPALIVE, keepAlive);
  99 + sslServerChannel = b.bind(sslHost, sslPort).sync().channel();
  100 + }
  101 + log.info("TCP transport started!");
  102 + }
  103 +
  104 + @PreDestroy
  105 + public void shutdown() throws InterruptedException {
  106 + log.info("Stopping TCP transport!");
  107 + try {
  108 + serverChannel.close().sync();
  109 + if (sslEnabled) {
  110 + sslServerChannel.close().sync();
  111 + }
  112 + } finally {
  113 + workerGroup.shutdownGracefully();
  114 + bossGroup.shutdownGracefully();
  115 + }
  116 + log.info("TCP transport stopped!");
  117 + }
  118 +
  119 + @Override
  120 + public String getName() {
  121 + return DataConstants.TCP_TRANSPORT_NAME;
  122 + }
  123 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp;
  17 +
  18 +import lombok.Getter;
  19 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  20 +
  21 +public enum TopicType {
  22 +
  23 + V1(MqttTopics.DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX, MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC),
  24 + V2(MqttTopics.DEVICE_ATTRIBUTES_RESPONSE_SHORT_TOPIC_PREFIX, MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_TOPIC, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_TOPIC),
  25 + V2_JSON(MqttTopics.DEVICE_ATTRIBUTES_RESPONSE_SHORT_JSON_TOPIC_PREFIX, MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_JSON_TOPIC, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC),
  26 + V2_PROTO(MqttTopics.DEVICE_ATTRIBUTES_RESPONSE_SHORT_PROTO_TOPIC_PREFIX, MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_PROTO_TOPIC);
  27 +
  28 + @Getter
  29 + private final String attributesResponseTopicBase;
  30 +
  31 + @Getter
  32 + private final String attributesSubTopic;
  33 +
  34 + @Getter
  35 + private final String rpcRequestTopicBase;
  36 +
  37 + @Getter
  38 + private final String rpcResponseTopicBase;
  39 +
  40 + TopicType(String attributesRequestTopicBase, String attributesSubTopic, String rpcRequestTopicBase, String rpcResponseTopicBase) {
  41 + this.attributesResponseTopicBase = attributesRequestTopicBase;
  42 + this.attributesSubTopic = attributesSubTopic;
  43 + this.rpcRequestTopicBase = rpcRequestTopicBase;
  44 + this.rpcResponseTopicBase = rpcResponseTopicBase;
  45 + }
  46 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.adaptors;
  17 +
  18 +import io.netty.handler.codec.mqtt.MqttMessage;
  19 +import io.netty.handler.codec.mqtt.MqttPublishMessage;
  20 +import lombok.AllArgsConstructor;
  21 +import lombok.Data;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  24 +import org.thingsboard.server.common.transport.adaptor.AdaptorException;
  25 +import org.thingsboard.server.gen.transport.TransportProtos;
  26 +import org.thingsboard.server.transport.tcp.session.MqttDeviceAwareSessionContext;
  27 +
  28 +import java.util.Optional;
  29 +
  30 +@Data
  31 +@AllArgsConstructor
  32 +@Slf4j
  33 +public class BackwardCompatibilityAdaptor implements TcpTransportAdaptor {
  34 +
  35 + private TcpTransportAdaptor protoAdaptor;
  36 + private TcpTransportAdaptor jsonAdaptor;
  37 +
  38 + @Override
  39 + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  40 + try {
  41 + return protoAdaptor.convertToPostTelemetry(ctx, inbound);
  42 + } catch (AdaptorException e) {
  43 + log.trace("[{}] failed to process post telemetry request msg: {} due to: ", ctx.getSessionId(), inbound, e);
  44 + return jsonAdaptor.convertToPostTelemetry(ctx, inbound);
  45 + }
  46 + }
  47 +
  48 + @Override
  49 + public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  50 + try {
  51 + return protoAdaptor.convertToPostAttributes(ctx, inbound);
  52 + } catch (AdaptorException e) {
  53 + log.trace("[{}] failed to process post attributes request msg: {} due to: ", ctx.getSessionId(), inbound, e);
  54 + return jsonAdaptor.convertToPostAttributes(ctx, inbound);
  55 + }
  56 + }
  57 +
  58 + @Override
  59 + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  60 + try {
  61 + return protoAdaptor.convertToGetAttributes(ctx, inbound, topicBase);
  62 + } catch (AdaptorException e) {
  63 + log.trace("[{}] failed to process get attributes request msg: {} due to: ", ctx.getSessionId(), inbound, e);
  64 + return jsonAdaptor.convertToGetAttributes(ctx, inbound, topicBase);
  65 + }
  66 + }
  67 +
  68 + @Override
  69 + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg, String topicBase) throws AdaptorException {
  70 + try {
  71 + return protoAdaptor.convertToDeviceRpcResponse(ctx, mqttMsg, topicBase);
  72 + } catch (AdaptorException e) {
  73 + log.trace("[{}] failed to process to device rpc response msg: {} due to: ", ctx.getSessionId(), mqttMsg, e);
  74 + return jsonAdaptor.convertToDeviceRpcResponse(ctx, mqttMsg, topicBase);
  75 + }
  76 + }
  77 +
  78 + @Override
  79 + public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg, String topicBase) throws AdaptorException {
  80 + try {
  81 + return protoAdaptor.convertToServerRpcRequest(ctx, mqttMsg, topicBase);
  82 + } catch (AdaptorException e) {
  83 + log.trace("[{}] failed to process to server rpc request msg: {} due to: ", ctx.getSessionId(), mqttMsg, e);
  84 + return jsonAdaptor.convertToServerRpcRequest(ctx, mqttMsg, topicBase);
  85 + }
  86 + }
  87 +
  88 + @Override
  89 + public TransportProtos.ClaimDeviceMsg convertToClaimDevice(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  90 + try {
  91 + return protoAdaptor.convertToClaimDevice(ctx, inbound);
  92 + } catch (AdaptorException e) {
  93 + log.trace("[{}] failed to process claim device request msg: {} due to: ", ctx.getSessionId(), inbound, e);
  94 + return jsonAdaptor.convertToClaimDevice(ctx, inbound);
  95 + }
  96 + }
  97 +
  98 + @Override
  99 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException {
  100 + log.warn("[{}] invoked not implemented adaptor method! GetAttributeResponseMsg: {} TopicBase: {}", ctx.getSessionId(), responseMsg, topicBase);
  101 + return Optional.empty();
  102 + }
  103 +
  104 + @Override
  105 + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
  106 + return protoAdaptor.convertToGatewayPublish(ctx, deviceName, responseMsg);
  107 + }
  108 +
  109 + @Override
  110 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.AttributeUpdateNotificationMsg notificationMsg, String topic) throws AdaptorException {
  111 + log.warn("[{}] invoked not implemented adaptor method! AttributeUpdateNotificationMsg: {} Topic: {}", ctx.getSessionId(), notificationMsg, topic);
  112 + return Optional.empty();
  113 + }
  114 +
  115 + @Override
  116 + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException {
  117 + return protoAdaptor.convertToGatewayPublish(ctx, deviceName, notificationMsg);
  118 + }
  119 +
  120 + @Override
  121 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest, String topicBase) throws AdaptorException {
  122 + log.warn("[{}] invoked not implemented adaptor method! ToDeviceRpcRequestMsg: {} TopicBase: {}", ctx.getSessionId(), rpcRequest, topicBase);
  123 + return Optional.empty();
  124 + }
  125 +
  126 + @Override
  127 + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException {
  128 + return protoAdaptor.convertToGatewayPublish(ctx, deviceName, rpcRequest);
  129 + }
  130 +
  131 + @Override
  132 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToServerRpcResponseMsg rpcResponse, String topicBase) throws AdaptorException {
  133 + log.warn("[{}] invoked not implemented adaptor method! ToServerRpcResponseMsg: {} TopicBase: {}", ctx.getSessionId(), rpcResponse, topicBase);
  134 + return Optional.empty();
  135 + }
  136 +
  137 + @Override
  138 + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  139 + log.warn("[{}] invoked not implemented adaptor method! MqttPublishMessage: {}", ctx.getSessionId(), inbound);
  140 + return null;
  141 + }
  142 +
  143 + @Override
  144 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException {
  145 + log.warn("[{}] invoked not implemented adaptor method! ProvisionDeviceResponseMsg: {}", ctx.getSessionId(), provisionResponse);
  146 + return Optional.empty();
  147 + }
  148 +
  149 + @Override
  150 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException {
  151 + return protoAdaptor.convertToPublish(ctx, firmwareChunk, requestId, chunk, firmwareType);
  152 + }
  153 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.adaptors;
  17 +
  18 +import com.google.gson.JsonElement;
  19 +import com.google.gson.JsonObject;
  20 +import com.google.gson.JsonParser;
  21 +import com.google.gson.JsonSyntaxException;
  22 +import io.netty.buffer.ByteBuf;
  23 +import io.netty.handler.codec.mqtt.MqttFixedHeader;
  24 +import io.netty.handler.codec.mqtt.MqttMessage;
  25 +import io.netty.handler.codec.mqtt.MqttMessageType;
  26 +import io.netty.handler.codec.mqtt.MqttPublishMessage;
  27 +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
  28 +import lombok.extern.slf4j.Slf4j;
  29 +import org.springframework.stereotype.Component;
  30 +import org.springframework.util.StringUtils;
  31 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  32 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  33 +import org.thingsboard.server.common.transport.adaptor.AdaptorException;
  34 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  35 +import org.thingsboard.server.gen.transport.TransportProtos;
  36 +import org.thingsboard.server.transport.tcp.session.MqttDeviceAwareSessionContext;
  37 +
  38 +import java.nio.charset.Charset;
  39 +import java.nio.charset.StandardCharsets;
  40 +import java.util.Arrays;
  41 +import java.util.HashSet;
  42 +import java.util.Optional;
  43 +import java.util.Set;
  44 +import java.util.UUID;
  45 +
  46 +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT;
  47 +
  48 +
  49 +/**
  50 + * @author Andrew Shvayka
  51 + */
  52 +@Component
  53 +@Slf4j
  54 +public class JsonTcpAdaptor implements TcpTransportAdaptor {
  55 +
  56 + protected static final Charset UTF8 = StandardCharsets.UTF_8;
  57 +
  58 + @Override
  59 + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  60 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
  61 + try {
  62 + return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload));
  63 + } catch (IllegalStateException | JsonSyntaxException ex) {
  64 + log.debug("Failed to decode post telemetry request", ex);
  65 + throw new AdaptorException(ex);
  66 + }
  67 + }
  68 +
  69 + @Override
  70 + public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  71 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
  72 + try {
  73 + return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload));
  74 + } catch (IllegalStateException | JsonSyntaxException ex) {
  75 + log.debug("Failed to decode post attributes request", ex);
  76 + throw new AdaptorException(ex);
  77 + }
  78 + }
  79 +
  80 + @Override
  81 + public TransportProtos.ClaimDeviceMsg convertToClaimDevice(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  82 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), true);
  83 + try {
  84 + return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload);
  85 + } catch (IllegalStateException | JsonSyntaxException ex) {
  86 + log.debug("Failed to decode claim device request", ex);
  87 + throw new AdaptorException(ex);
  88 + }
  89 + }
  90 +
  91 + @Override
  92 + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  93 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
  94 + try {
  95 + return JsonConverter.convertToProvisionRequestMsg(payload);
  96 + } catch (IllegalStateException | JsonSyntaxException ex) {
  97 + throw new AdaptorException(ex);
  98 + }
  99 + }
  100 +
  101 + @Override
  102 + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  103 + return processGetAttributeRequestMsg(inbound, topicBase);
  104 + }
  105 +
  106 + @Override
  107 + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  108 + return processToDeviceRpcResponseMsg(inbound, topicBase);
  109 + }
  110 +
  111 + @Override
  112 + public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  113 + return processToServerRpcRequestMsg(ctx, inbound, topicBase);
  114 + }
  115 +
  116 + @Override
  117 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException {
  118 + return processConvertFromAttributeResponseMsg(ctx, responseMsg, topicBase);
  119 + }
  120 +
  121 + @Override
  122 + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
  123 + return processConvertFromGatewayAttributeResponseMsg(ctx, deviceName, responseMsg);
  124 + }
  125 +
  126 + @Override
  127 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.AttributeUpdateNotificationMsg notificationMsg, String topic) {
  128 + return Optional.of(createMqttPublishMsg(ctx, topic, JsonConverter.toJson(notificationMsg)));
  129 + }
  130 +
  131 + @Override
  132 + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) {
  133 + JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, notificationMsg);
  134 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, result));
  135 + }
  136 +
  137 + @Override
  138 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest, String topicBase) {
  139 + return Optional.of(createMqttPublishMsg(ctx, topicBase + rpcRequest.getRequestId(), JsonConverter.toJson(rpcRequest, false)));
  140 + }
  141 +
  142 + @Override
  143 + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
  144 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, JsonConverter.toGatewayJson(deviceName, rpcRequest)));
  145 + }
  146 +
  147 + @Override
  148 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToServerRpcResponseMsg rpcResponse, String topicBase) {
  149 + return Optional.of(createMqttPublishMsg(ctx, topicBase + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse)));
  150 + }
  151 +
  152 + @Override
  153 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) {
  154 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse)));
  155 + }
  156 +
  157 + @Override
  158 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) {
  159 + return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
  160 + }
  161 +
  162 + public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
  163 + String payload = validatePayload(sessionId, payloadData, false);
  164 + try {
  165 + return new JsonParser().parse(payload);
  166 + } catch (JsonSyntaxException ex) {
  167 + log.debug("Payload is in incorrect format: {}", payload);
  168 + throw new AdaptorException(ex);
  169 + }
  170 + }
  171 +
  172 + private TransportProtos.GetAttributeRequestMsg processGetAttributeRequestMsg(MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  173 + String topicName = inbound.variableHeader().topicName();
  174 + try {
  175 + TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
  176 + result.setRequestId(getRequestId(topicName, topicBase));
  177 + String payload = inbound.payload().toString(UTF8);
  178 + JsonElement requestBody = new JsonParser().parse(payload);
  179 + Set<String> clientKeys = toStringSet(requestBody, "clientKeys");
  180 + Set<String> sharedKeys = toStringSet(requestBody, "sharedKeys");
  181 + if (clientKeys != null) {
  182 + result.addAllClientAttributeNames(clientKeys);
  183 + }
  184 + if (sharedKeys != null) {
  185 + result.addAllSharedAttributeNames(sharedKeys);
  186 + }
  187 + return result.build();
  188 + } catch (RuntimeException e) {
  189 + log.debug("Failed to decode get attributes request", e);
  190 + throw new AdaptorException(e);
  191 + }
  192 + }
  193 +
  194 + private TransportProtos.ToDeviceRpcResponseMsg processToDeviceRpcResponseMsg(MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  195 + String topicName = inbound.variableHeader().topicName();
  196 + try {
  197 + int requestId = getRequestId(topicName, topicBase);
  198 + String payload = inbound.payload().toString(UTF8);
  199 + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build();
  200 + } catch (RuntimeException e) {
  201 + log.debug("Failed to decode rpc response", e);
  202 + throw new AdaptorException(e);
  203 + }
  204 + }
  205 +
  206 + private TransportProtos.ToServerRpcRequestMsg processToServerRpcRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  207 + String topicName = inbound.variableHeader().topicName();
  208 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
  209 + try {
  210 + int requestId = getRequestId(topicName, topicBase);
  211 + return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId);
  212 + } catch (IllegalStateException | JsonSyntaxException ex) {
  213 + log.debug("Failed to decode to server rpc request", ex);
  214 + throw new AdaptorException(ex);
  215 + }
  216 + }
  217 +
  218 + private Optional<MqttMessage> processConvertFromAttributeResponseMsg(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException {
  219 + if (!StringUtils.isEmpty(responseMsg.getError())) {
  220 + throw new AdaptorException(responseMsg.getError());
  221 + } else {
  222 + int requestId = responseMsg.getRequestId();
  223 + if (requestId >= 0) {
  224 + return Optional.of(createMqttPublishMsg(ctx,
  225 + topicBase + requestId,
  226 + JsonConverter.toJson(responseMsg)));
  227 + }
  228 + return Optional.empty();
  229 + }
  230 + }
  231 +
  232 + private Optional<MqttMessage> processConvertFromGatewayAttributeResponseMsg(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
  233 + if (!StringUtils.isEmpty(responseMsg.getError())) {
  234 + throw new AdaptorException(responseMsg.getError());
  235 + } else {
  236 + JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, responseMsg);
  237 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, result));
  238 + }
  239 + }
  240 +
  241 + protected MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) {
  242 + MqttFixedHeader mqttFixedHeader =
  243 + new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
  244 + MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
  245 + ByteBuf payload = ALLOCATOR.buffer();
  246 + payload.writeBytes(json.toString().getBytes(UTF8));
  247 + return new MqttPublishMessage(mqttFixedHeader, header, payload);
  248 + }
  249 +
  250 + private Set<String> toStringSet(JsonElement requestBody, String name) {
  251 + JsonElement element = requestBody.getAsJsonObject().get(name);
  252 + if (element != null) {
  253 + return new HashSet<>(Arrays.asList(element.getAsString().split(",")));
  254 + } else {
  255 + return null;
  256 + }
  257 + }
  258 +
  259 + private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException {
  260 + String payload = payloadData.toString(UTF8);
  261 + if (payload == null) {
  262 + log.debug("[{}] Payload is empty!", sessionId);
  263 + if (!isEmptyPayloadAllowed) {
  264 + throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
  265 + }
  266 + }
  267 + return payload;
  268 + }
  269 +
  270 + private int getRequestId(String topicName, String topic) {
  271 + return Integer.parseInt(topicName.substring(topic.length()));
  272 + }
  273 +
  274 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.adaptors;
  17 +
  18 +import com.google.gson.JsonElement;
  19 +import com.google.gson.JsonParser;
  20 +import com.google.protobuf.Descriptors;
  21 +import com.google.protobuf.DynamicMessage;
  22 +import com.google.protobuf.InvalidProtocolBufferException;
  23 +import io.netty.buffer.ByteBuf;
  24 +import io.netty.handler.codec.mqtt.MqttMessage;
  25 +import io.netty.handler.codec.mqtt.MqttPublishMessage;
  26 +import lombok.extern.slf4j.Slf4j;
  27 +import org.springframework.stereotype.Component;
  28 +import org.springframework.util.StringUtils;
  29 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  30 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  31 +import org.thingsboard.server.common.transport.adaptor.AdaptorException;
  32 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  33 +import org.thingsboard.server.common.transport.adaptor.ProtoConverter;
  34 +import org.thingsboard.server.gen.transport.TransportApiProtos;
  35 +import org.thingsboard.server.gen.transport.TransportProtos;
  36 +import org.thingsboard.server.transport.tcp.session.DeviceSessionCtx;
  37 +import org.thingsboard.server.transport.tcp.session.MqttDeviceAwareSessionContext;
  38 +
  39 +import java.util.Optional;
  40 +
  41 +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT;
  42 +
  43 +@Component
  44 +@Slf4j
  45 +public class ProtoTcpAdaptor implements TcpTransportAdaptor {
  46 +
  47 + @Override
  48 + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  49 + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
  50 + byte[] bytes = toBytes(inbound.payload());
  51 + Descriptors.Descriptor telemetryDynamicMsgDescriptor = ProtoConverter.validateDescriptor(deviceSessionCtx.getTelemetryDynamicMsgDescriptor());
  52 + try {
  53 + return JsonConverter.convertToTelemetryProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor)));
  54 + } catch (Exception e) {
  55 + log.debug("Failed to decode post telemetry request", e);
  56 + throw new AdaptorException(e);
  57 + }
  58 + }
  59 +
  60 + @Override
  61 + public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  62 + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
  63 + byte[] bytes = toBytes(inbound.payload());
  64 + Descriptors.Descriptor attributesDynamicMessageDescriptor = ProtoConverter.validateDescriptor(deviceSessionCtx.getAttributesDynamicMessageDescriptor());
  65 + try {
  66 + return JsonConverter.convertToAttributesProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor)));
  67 + } catch (Exception e) {
  68 + log.debug("Failed to decode post attributes request", e);
  69 + throw new AdaptorException(e);
  70 + }
  71 + }
  72 +
  73 + @Override
  74 + public TransportProtos.ClaimDeviceMsg convertToClaimDevice(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  75 + byte[] bytes = toBytes(inbound.payload());
  76 + try {
  77 + return ProtoConverter.convertToClaimDeviceProto(ctx.getDeviceId(), bytes);
  78 + } catch (InvalidProtocolBufferException e) {
  79 + log.debug("Failed to decode claim device request", e);
  80 + throw new AdaptorException(e);
  81 + }
  82 + }
  83 +
  84 + @Override
  85 + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  86 + byte[] bytes = toBytes(inbound.payload());
  87 + String topicName = inbound.variableHeader().topicName();
  88 + try {
  89 + int requestId = getRequestId(topicName, topicBase);
  90 + return ProtoConverter.convertToGetAttributeRequestMessage(bytes, requestId);
  91 + } catch (InvalidProtocolBufferException e) {
  92 + log.debug("Failed to decode get attributes request", e);
  93 + throw new AdaptorException(e);
  94 + }
  95 + }
  96 +
  97 + @Override
  98 + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg, String topicBase) throws AdaptorException {
  99 + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
  100 + String topicName = mqttMsg.variableHeader().topicName();
  101 + byte[] bytes = toBytes(mqttMsg.payload());
  102 + Descriptors.Descriptor rpcResponseDynamicMessageDescriptor = ProtoConverter.validateDescriptor(deviceSessionCtx.getRpcResponseDynamicMessageDescriptor());
  103 + try {
  104 + int requestId = getRequestId(topicName, topicBase);
  105 + JsonElement response = new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor));
  106 + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(response.toString()).build();
  107 + } catch (Exception e) {
  108 + log.debug("Failed to decode rpc response", e);
  109 + throw new AdaptorException(e);
  110 + }
  111 + }
  112 +
  113 + @Override
  114 + public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg, String topicBase) throws AdaptorException {
  115 + byte[] bytes = toBytes(mqttMsg.payload());
  116 + String topicName = mqttMsg.variableHeader().topicName();
  117 + try {
  118 + int requestId = getRequestId(topicName, topicBase);
  119 + return ProtoConverter.convertToServerRpcRequest(bytes, requestId);
  120 + } catch (InvalidProtocolBufferException e) {
  121 + log.debug("Failed to decode to server rpc request", e);
  122 + throw new AdaptorException(e);
  123 + }
  124 + }
  125 +
  126 + @Override
  127 + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException {
  128 + byte[] bytes = toBytes(mqttMsg.payload());
  129 + try {
  130 + return ProtoConverter.convertToProvisionRequestMsg(bytes);
  131 + } catch (InvalidProtocolBufferException ex) {
  132 + log.debug("Failed to decode provision request", ex);
  133 + throw new AdaptorException(ex);
  134 + }
  135 + }
  136 +
  137 + @Override
  138 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException {
  139 + if (!StringUtils.isEmpty(responseMsg.getError())) {
  140 + throw new AdaptorException(responseMsg.getError());
  141 + } else {
  142 + int requestId = responseMsg.getRequestId();
  143 + if (requestId >= 0) {
  144 + return Optional.of(createMqttPublishMsg(ctx, topicBase + requestId, responseMsg.toByteArray()));
  145 + }
  146 + return Optional.empty();
  147 + }
  148 + }
  149 +
  150 + @Override
  151 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest, String topicBase) throws AdaptorException {
  152 + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
  153 + DynamicMessage.Builder rpcRequestDynamicMessageBuilder = deviceSessionCtx.getRpcRequestDynamicMessageBuilder();
  154 + if (rpcRequestDynamicMessageBuilder == null) {
  155 + throw new AdaptorException("Failed to get rpcRequestDynamicMessageBuilder!");
  156 + } else {
  157 + return Optional.of(createMqttPublishMsg(ctx, topicBase + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest, rpcRequestDynamicMessageBuilder)));
  158 + }
  159 + }
  160 +
  161 + @Override
  162 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToServerRpcResponseMsg rpcResponse, String topicBase) {
  163 + return Optional.of(createMqttPublishMsg(ctx, topicBase + rpcResponse.getRequestId(), rpcResponse.toByteArray()));
  164 + }
  165 +
  166 + @Override
  167 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.AttributeUpdateNotificationMsg notificationMsg, String topic) {
  168 + return Optional.of(createMqttPublishMsg(ctx, topic, notificationMsg.toByteArray()));
  169 + }
  170 +
  171 + @Override
  172 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) {
  173 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, provisionResponse.toByteArray()));
  174 + }
  175 +
  176 + @Override
  177 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException {
  178 + return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
  179 + }
  180 +
  181 + @Override
  182 + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
  183 + if (!StringUtils.isEmpty(responseMsg.getError())) {
  184 + throw new AdaptorException(responseMsg.getError());
  185 + } else {
  186 + TransportApiProtos.GatewayAttributeResponseMsg.Builder responseMsgBuilder = TransportApiProtos.GatewayAttributeResponseMsg.newBuilder();
  187 + responseMsgBuilder.setDeviceName(deviceName);
  188 + responseMsgBuilder.setResponseMsg(responseMsg);
  189 + byte[] payloadBytes = responseMsgBuilder.build().toByteArray();
  190 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, payloadBytes));
  191 + }
  192 + }
  193 +
  194 + @Override
  195 + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) {
  196 + TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder builder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
  197 + builder.setDeviceName(deviceName);
  198 + builder.setNotificationMsg(notificationMsg);
  199 + byte[] payloadBytes = builder.build().toByteArray();
  200 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, payloadBytes));
  201 + }
  202 +
  203 + @Override
  204 + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
  205 + TransportApiProtos.GatewayDeviceRpcRequestMsg.Builder builder = TransportApiProtos.GatewayDeviceRpcRequestMsg.newBuilder();
  206 + builder.setDeviceName(deviceName);
  207 + builder.setRpcRequestMsg(rpcRequest);
  208 + byte[] payloadBytes = builder.build().toByteArray();
  209 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, payloadBytes));
  210 + }
  211 +
  212 + public static byte[] toBytes(ByteBuf inbound) {
  213 + byte[] bytes = new byte[inbound.readableBytes()];
  214 + int readerIndex = inbound.readerIndex();
  215 + inbound.getBytes(readerIndex, bytes);
  216 + return bytes;
  217 + }
  218 +
  219 + private int getRequestId(String topicName, String topic) {
  220 + return Integer.parseInt(topicName.substring(topic.length()));
  221 + }
  222 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.adaptors;
  17 +
  18 +import io.netty.buffer.ByteBuf;
  19 +import io.netty.buffer.ByteBufAllocator;
  20 +import io.netty.buffer.UnpooledByteBufAllocator;
  21 +import io.netty.handler.codec.mqtt.MqttFixedHeader;
  22 +import io.netty.handler.codec.mqtt.MqttMessage;
  23 +import io.netty.handler.codec.mqtt.MqttMessageType;
  24 +import io.netty.handler.codec.mqtt.MqttPublishMessage;
  25 +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
  26 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  27 +import org.thingsboard.server.common.transport.adaptor.AdaptorException;
  28 +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
  29 +import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
  30 +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
  31 +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
  32 +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
  33 +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
  34 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
  35 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
  36 +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
  37 +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
  38 +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
  39 +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
  40 +import org.thingsboard.server.transport.tcp.session.MqttDeviceAwareSessionContext;
  41 +
  42 +import java.util.Optional;
  43 +
  44 +/**
  45 + * @author Andrew Shvayka
  46 + */
  47 +public interface TcpTransportAdaptor {
  48 +
  49 + ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
  50 +
  51 + PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
  52 +
  53 + PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
  54 +
  55 + GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException;
  56 +
  57 + ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg, String topicBase) throws AdaptorException;
  58 +
  59 + ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg, String topicBase) throws AdaptorException;
  60 +
  61 + ClaimDeviceMsg convertToClaimDevice(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
  62 +
  63 + Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException;
  64 +
  65 + Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, GetAttributeResponseMsg responseMsg) throws AdaptorException;
  66 +
  67 + Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, AttributeUpdateNotificationMsg notificationMsg, String topic) throws AdaptorException;
  68 +
  69 + Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException;
  70 +
  71 + Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ToDeviceRpcRequestMsg rpcRequest, String topicBase) throws AdaptorException;
  72 +
  73 + Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException;
  74 +
  75 + Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse, String topicBase) throws AdaptorException;
  76 +
  77 + ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
  78 +
  79 + Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException;
  80 +
  81 + Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException;
  82 +
  83 + default MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadInBytes) {
  84 + MqttFixedHeader mqttFixedHeader =
  85 + new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
  86 + MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
  87 + ByteBuf payload = ALLOCATOR.buffer();
  88 + payload.writeBytes(payloadInBytes);
  89 + return new MqttPublishMessage(mqttFixedHeader, header, payload);
  90 + }
  91 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.limits;
  17 +
  18 +import io.netty.channel.ChannelHandlerContext;
  19 +import io.netty.handler.ipfilter.AbstractRemoteAddressFilter;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.thingsboard.server.transport.tcp.TcpTransportContext;
  22 +import org.thingsboard.server.transport.tcp.TcpTransportService;
  23 +
  24 +import java.net.InetSocketAddress;
  25 +
  26 +@Slf4j
  27 +public class IpFilter extends AbstractRemoteAddressFilter<InetSocketAddress> {
  28 +
  29 + private TcpTransportContext context;
  30 +
  31 + public IpFilter(TcpTransportContext context) {
  32 + this.context = context;
  33 + }
  34 +
  35 + @Override
  36 + protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception {
  37 + log.trace("[{}] Received msg: {}", ctx.channel().id(), remoteAddress);
  38 + if(context.checkAddress(remoteAddress)){
  39 + log.trace("[{}] Setting address: {}", ctx.channel().id(), remoteAddress);
  40 + ctx.channel().attr(TcpTransportService.ADDRESS).set(remoteAddress);
  41 + return true;
  42 + } else {
  43 + return false;
  44 + }
  45 + }
  46 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.limits;
  17 +
  18 +import io.netty.channel.ChannelHandler;
  19 +import io.netty.channel.ChannelHandlerContext;
  20 +import io.netty.channel.ChannelInboundHandlerAdapter;
  21 +import io.netty.handler.codec.haproxy.HAProxyMessage;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.thingsboard.server.transport.tcp.TcpTransportContext;
  24 +import org.thingsboard.server.transport.tcp.TcpTransportService;
  25 +
  26 +import java.net.InetSocketAddress;
  27 +
  28 +@Slf4j
  29 +public class ProxyIpFilter extends ChannelInboundHandlerAdapter {
  30 +
  31 +
  32 + private TcpTransportContext context;
  33 +
  34 + public ProxyIpFilter(TcpTransportContext context) {
  35 + this.context = context;
  36 + }
  37 +
  38 + @Override
  39 + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  40 + log.trace("[{}] Received msg: {}", ctx.channel().id(), msg);
  41 + if (msg instanceof HAProxyMessage) {
  42 + HAProxyMessage proxyMsg = (HAProxyMessage) msg;
  43 + if (proxyMsg.sourceAddress() != null && proxyMsg.sourcePort() > 0) {
  44 + InetSocketAddress address = new InetSocketAddress(proxyMsg.sourceAddress(), proxyMsg.sourcePort());
  45 + if (!context.checkAddress(address)) {
  46 + closeChannel(ctx);
  47 + } else {
  48 + log.trace("[{}] Setting address: {}", ctx.channel().id(), address);
  49 + ctx.channel().attr(TcpTransportService.ADDRESS).set(address);
  50 + // We no longer need this channel in the pipeline. Similar to HAProxyMessageDecoder
  51 + ctx.pipeline().remove(this);
  52 + }
  53 + } else {
  54 + log.trace("Received local health-check connection message: {}", proxyMsg);
  55 + closeChannel(ctx);
  56 + }
  57 + }
  58 + }
  59 +
  60 + private void closeChannel(ChannelHandlerContext ctx) {
  61 + while (ctx.pipeline().last() != this) {
  62 + ChannelHandler handler = ctx.pipeline().removeLast();
  63 + if (handler instanceof ChannelInboundHandlerAdapter) {
  64 + try {
  65 + ((ChannelInboundHandlerAdapter) handler).channelUnregistered(ctx);
  66 + } catch (Exception e) {
  67 + log.error("Failed to unregister channel: [{}]", ctx, e);
  68 + }
  69 + }
  70 +
  71 + }
  72 + ctx.pipeline().remove(this);
  73 + ctx.close();
  74 + }
  75 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.session;
  17 +
  18 +import com.google.protobuf.Descriptors;
  19 +import com.google.protobuf.DynamicMessage;
  20 +import io.netty.channel.ChannelHandlerContext;
  21 +import io.netty.handler.codec.mqtt.MqttMessage;
  22 +import io.netty.util.ReferenceCountUtil;
  23 +import lombok.Getter;
  24 +import lombok.Setter;
  25 +import lombok.extern.slf4j.Slf4j;
  26 +import org.thingsboard.server.common.data.DeviceProfile;
  27 +import org.thingsboard.server.common.data.DeviceTransportType;
  28 +import org.thingsboard.server.common.data.TransportPayloadType;
  29 +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
  30 +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
  31 +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
  32 +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
  33 +import org.thingsboard.server.gen.transport.TransportProtos;
  34 +import org.thingsboard.server.transport.tcp.TcpTransportContext;
  35 +import org.thingsboard.server.transport.tcp.TopicType;
  36 +import org.thingsboard.server.transport.tcp.adaptors.BackwardCompatibilityAdaptor;
  37 +import org.thingsboard.server.transport.tcp.adaptors.TcpTransportAdaptor;
  38 +import org.thingsboard.server.transport.tcp.util.TcpTopicFilter;
  39 +import org.thingsboard.server.transport.tcp.util.MqttTopicFilterFactory;
  40 +
  41 +import java.util.Collection;
  42 +import java.util.Collections;
  43 +import java.util.UUID;
  44 +import java.util.concurrent.ConcurrentLinkedQueue;
  45 +import java.util.concurrent.ConcurrentMap;
  46 +import java.util.concurrent.atomic.AtomicInteger;
  47 +import java.util.concurrent.locks.Lock;
  48 +import java.util.concurrent.locks.ReentrantLock;
  49 +import java.util.function.Consumer;
  50 +
  51 +/**
  52 + * @author Andrew Shvayka
  53 + */
  54 +@Slf4j
  55 +public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
  56 +
  57 + @Getter
  58 + @Setter
  59 + private ChannelHandlerContext channel;
  60 +
  61 + @Getter
  62 + private final TcpTransportContext context;
  63 +
  64 + private final AtomicInteger msgIdSeq = new AtomicInteger(0);
  65 +
  66 + private final ConcurrentLinkedQueue<MqttMessage> msgQueue = new ConcurrentLinkedQueue<>();
  67 +
  68 + @Getter
  69 + private final Lock msgQueueProcessorLock = new ReentrantLock();
  70 +
  71 + private final AtomicInteger msgQueueSize = new AtomicInteger(0);
  72 +
  73 + @Getter
  74 + @Setter
  75 + private boolean provisionOnly = false;
  76 +
  77 + private volatile TcpTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
  78 + private volatile TcpTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
  79 + private volatile TransportPayloadType payloadType = TransportPayloadType.JSON;
  80 + private volatile Descriptors.Descriptor attributesDynamicMessageDescriptor;
  81 + private volatile Descriptors.Descriptor telemetryDynamicMessageDescriptor;
  82 + private volatile Descriptors.Descriptor rpcResponseDynamicMessageDescriptor;
  83 + private volatile DynamicMessage.Builder rpcRequestDynamicMessageBuilder;
  84 + private volatile TcpTransportAdaptor adaptor;
  85 + private volatile boolean jsonPayloadFormatCompatibilityEnabled;
  86 + private volatile boolean useJsonPayloadFormatForDefaultDownlinkTopics;
  87 +
  88 + @Getter
  89 + @Setter
  90 + private TransportPayloadType provisionPayloadType = payloadType;
  91 +
  92 + public DeviceSessionCtx(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap, TcpTransportContext context) {
  93 + super(sessionId, mqttQoSMap);
  94 + this.context = context;
  95 + this.adaptor = context.getJsonMqttAdaptor();
  96 + }
  97 +
  98 + public int nextMsgId() {
  99 + return msgIdSeq.incrementAndGet();
  100 + }
  101 +
  102 + public boolean isDeviceTelemetryTopic(String topicName) {
  103 + return telemetryTopicFilter.filter(topicName);
  104 + }
  105 +
  106 + public boolean isDeviceAttributesTopic(String topicName) {
  107 + return attributesTopicFilter.filter(topicName);
  108 + }
  109 +
  110 + public TcpTransportAdaptor getPayloadAdaptor() {
  111 + return adaptor;
  112 + }
  113 +
  114 + public boolean isJsonPayloadType() {
  115 + return payloadType.equals(TransportPayloadType.JSON);
  116 + }
  117 +
  118 + public Descriptors.Descriptor getTelemetryDynamicMsgDescriptor() {
  119 + return telemetryDynamicMessageDescriptor;
  120 + }
  121 +
  122 + public Descriptors.Descriptor getAttributesDynamicMessageDescriptor() {
  123 + return attributesDynamicMessageDescriptor;
  124 + }
  125 +
  126 + public Descriptors.Descriptor getRpcResponseDynamicMessageDescriptor() {
  127 + return rpcResponseDynamicMessageDescriptor;
  128 + }
  129 +
  130 + public DynamicMessage.Builder getRpcRequestDynamicMessageBuilder() {
  131 + return rpcRequestDynamicMessageBuilder;
  132 + }
  133 +
  134 + @Override
  135 + public void setDeviceProfile(DeviceProfile deviceProfile) {
  136 + super.setDeviceProfile(deviceProfile);
  137 + updateDeviceSessionConfiguration(deviceProfile);
  138 + }
  139 +
  140 + @Override
  141 + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
  142 + super.onDeviceProfileUpdate(sessionInfo, deviceProfile);
  143 + updateDeviceSessionConfiguration(deviceProfile);
  144 + }
  145 +
  146 + private void updateDeviceSessionConfiguration(DeviceProfile deviceProfile) {
  147 + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
  148 + if (transportConfiguration.getType().equals(DeviceTransportType.MQTT) &&
  149 + transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) {
  150 + MqttDeviceProfileTransportConfiguration mqttConfig = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
  151 + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttConfig.getTransportPayloadTypeConfiguration();
  152 + payloadType = transportPayloadTypeConfiguration.getTransportPayloadType();
  153 + telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic());
  154 + attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic());
  155 + if (TransportPayloadType.PROTOBUF.equals(payloadType)) {
  156 + ProtoTransportPayloadConfiguration protoTransportPayloadConfig = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
  157 + updateDynamicMessageDescriptors(protoTransportPayloadConfig);
  158 + jsonPayloadFormatCompatibilityEnabled = protoTransportPayloadConfig.isEnableCompatibilityWithJsonPayloadFormat();
  159 + useJsonPayloadFormatForDefaultDownlinkTopics = jsonPayloadFormatCompatibilityEnabled && protoTransportPayloadConfig.isUseJsonPayloadFormatForDefaultDownlinkTopics();
  160 + }
  161 + } else {
  162 + telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
  163 + attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
  164 + payloadType = TransportPayloadType.JSON;
  165 + }
  166 + updateAdaptor();
  167 + }
  168 +
  169 + private void updateDynamicMessageDescriptors(ProtoTransportPayloadConfiguration protoTransportPayloadConfig) {
  170 + telemetryDynamicMessageDescriptor = protoTransportPayloadConfig.getTelemetryDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceTelemetryProtoSchema());
  171 + attributesDynamicMessageDescriptor = protoTransportPayloadConfig.getAttributesDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceAttributesProtoSchema());
  172 + rpcResponseDynamicMessageDescriptor = protoTransportPayloadConfig.getRpcResponseDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceRpcResponseProtoSchema());
  173 + rpcRequestDynamicMessageBuilder = protoTransportPayloadConfig.getRpcRequestDynamicMessageBuilder(protoTransportPayloadConfig.getDeviceRpcRequestProtoSchema());
  174 + }
  175 +
  176 + public TcpTransportAdaptor getAdaptor(TopicType topicType) {
  177 + switch (topicType) {
  178 + case V2:
  179 + return getDefaultAdaptor();
  180 + case V2_JSON:
  181 + return context.getJsonMqttAdaptor();
  182 + case V2_PROTO:
  183 + return context.getProtoMqttAdaptor();
  184 + default:
  185 + return useJsonPayloadFormatForDefaultDownlinkTopics ? context.getJsonMqttAdaptor() : getDefaultAdaptor();
  186 + }
  187 + }
  188 +
  189 + private TcpTransportAdaptor getDefaultAdaptor() {
  190 + return isJsonPayloadType() ? context.getJsonMqttAdaptor() : context.getProtoMqttAdaptor();
  191 + }
  192 +
  193 + private void updateAdaptor() {
  194 + if (isJsonPayloadType()) {
  195 + adaptor = context.getJsonMqttAdaptor();
  196 + jsonPayloadFormatCompatibilityEnabled = false;
  197 + useJsonPayloadFormatForDefaultDownlinkTopics = false;
  198 + } else {
  199 + if (jsonPayloadFormatCompatibilityEnabled) {
  200 + adaptor = new BackwardCompatibilityAdaptor(context.getProtoMqttAdaptor(), context.getJsonMqttAdaptor());
  201 + } else {
  202 + adaptor = context.getProtoMqttAdaptor();
  203 + }
  204 + }
  205 + }
  206 +
  207 + public void addToQueue(MqttMessage msg) {
  208 + msgQueueSize.incrementAndGet();
  209 + ReferenceCountUtil.retain(msg);
  210 + msgQueue.add(msg);
  211 + }
  212 +
  213 + public void tryProcessQueuedMsgs(Consumer<MqttMessage> msgProcessor) {
  214 + while (!msgQueue.isEmpty()) {
  215 + if (msgQueueProcessorLock.tryLock()) {
  216 + try {
  217 + MqttMessage msg;
  218 + while ((msg = msgQueue.poll()) != null) {
  219 + try {
  220 + msgQueueSize.decrementAndGet();
  221 + msgProcessor.accept(msg);
  222 + } finally {
  223 + ReferenceCountUtil.safeRelease(msg);
  224 + }
  225 + }
  226 + } finally {
  227 + msgQueueProcessorLock.unlock();
  228 + }
  229 + } else {
  230 + return;
  231 + }
  232 + }
  233 + }
  234 +
  235 + public int getMsgQueueSize() {
  236 + return msgQueueSize.get();
  237 + }
  238 +
  239 + public void release() {
  240 + if (!msgQueue.isEmpty()) {
  241 + log.warn("doDisconnect for device {} but unprocessed messages {} left in the msg queue", getDeviceId(), msgQueue.size());
  242 + msgQueue.forEach(ReferenceCountUtil::safeRelease);
  243 + msgQueue.clear();
  244 + }
  245 + }
  246 +
  247 + public Collection<MqttMessage> getMsgQueueSnapshot(){
  248 + return Collections.unmodifiableCollection(msgQueue);
  249 + }
  250 +
  251 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.session;
  17 +
  18 +import io.netty.channel.ChannelFuture;
  19 +import io.netty.handler.codec.mqtt.MqttMessage;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.thingsboard.server.common.data.DeviceProfile;
  22 +import org.thingsboard.server.common.data.id.DeviceId;
  23 +import org.thingsboard.server.common.data.rpc.RpcStatus;
  24 +import org.thingsboard.server.common.transport.SessionMsgListener;
  25 +import org.thingsboard.server.common.transport.TransportService;
  26 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  27 +import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
  28 +import org.thingsboard.server.gen.transport.TransportProtos;
  29 +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
  30 +
  31 +import java.util.UUID;
  32 +import java.util.concurrent.ConcurrentMap;
  33 +
  34 +/**
  35 + * Created by ashvayka on 19.01.17.
  36 + */
  37 +@Slf4j
  38 +public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext implements SessionMsgListener {
  39 +
  40 + private final GatewaySessionHandler parent;
  41 + private final TransportService transportService;
  42 +
  43 + public GatewayDeviceSessionCtx(GatewaySessionHandler parent, TransportDeviceInfo deviceInfo,
  44 + DeviceProfile deviceProfile, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap,
  45 + TransportService transportService) {
  46 + super(UUID.randomUUID(), mqttQoSMap);
  47 + this.parent = parent;
  48 + setSessionInfo(SessionInfoProto.newBuilder()
  49 + .setNodeId(parent.getNodeId())
  50 + .setSessionIdMSB(sessionId.getMostSignificantBits())
  51 + .setSessionIdLSB(sessionId.getLeastSignificantBits())
  52 + .setDeviceIdMSB(deviceInfo.getDeviceId().getId().getMostSignificantBits())
  53 + .setDeviceIdLSB(deviceInfo.getDeviceId().getId().getLeastSignificantBits())
  54 + .setTenantIdMSB(deviceInfo.getTenantId().getId().getMostSignificantBits())
  55 + .setTenantIdLSB(deviceInfo.getTenantId().getId().getLeastSignificantBits())
  56 + .setCustomerIdMSB(deviceInfo.getCustomerId().getId().getMostSignificantBits())
  57 + .setCustomerIdLSB(deviceInfo.getCustomerId().getId().getLeastSignificantBits())
  58 + .setDeviceName(deviceInfo.getDeviceName())
  59 + .setDeviceType(deviceInfo.getDeviceType())
  60 + .setGwSessionIdMSB(parent.getSessionId().getMostSignificantBits())
  61 + .setGwSessionIdLSB(parent.getSessionId().getLeastSignificantBits())
  62 + .setDeviceProfileIdMSB(deviceInfo.getDeviceProfileId().getId().getMostSignificantBits())
  63 + .setDeviceProfileIdLSB(deviceInfo.getDeviceProfileId().getId().getLeastSignificantBits())
  64 + .build());
  65 + setDeviceInfo(deviceInfo);
  66 + setConnected(true);
  67 + setDeviceProfile(deviceProfile);
  68 + this.transportService = transportService;
  69 + }
  70 +
  71 + @Override
  72 + public UUID getSessionId() {
  73 + return sessionId;
  74 + }
  75 +
  76 + @Override
  77 + public int nextMsgId() {
  78 + return parent.nextMsgId();
  79 + }
  80 +
  81 + @Override
  82 + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) {
  83 + try {
  84 + parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), response).ifPresent(parent::writeAndFlush);
  85 + } catch (Exception e) {
  86 + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
  87 + }
  88 + }
  89 +
  90 + @Override
  91 + public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg notification) {
  92 + log.trace("[{}] Received attributes update notification to device", sessionId);
  93 + try {
  94 + parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), notification).ifPresent(parent::writeAndFlush);
  95 + } catch (Exception e) {
  96 + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
  97 + }
  98 + }
  99 +
  100 + @Override
  101 + public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg request) {
  102 + log.trace("[{}] Received RPC command to device", sessionId);
  103 + try {
  104 + parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent(
  105 + payload -> {
  106 + ChannelFuture channelFuture = parent.writeAndFlush(payload);
  107 + if (request.getPersisted()) {
  108 + channelFuture.addListener(result -> {
  109 + if (result.cause() == null) {
  110 + if (!isAckExpected(payload)) {
  111 + transportService.process(getSessionInfo(), request, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY);
  112 + } else if (request.getPersisted()) {
  113 + transportService.process(getSessionInfo(), request, RpcStatus.SENT, TransportServiceCallback.EMPTY);
  114 +
  115 + }
  116 + }
  117 + });
  118 + }
  119 + }
  120 + );
  121 + } catch (Exception e) {
  122 + transportService.process(getSessionInfo(),
  123 + TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
  124 + .setRequestId(request.getRequestId()).setError("Failed to convert device RPC command to MQTT msg").build(), TransportServiceCallback.EMPTY);
  125 + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
  126 + }
  127 + }
  128 +
  129 + @Override
  130 + public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) {
  131 + log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage());
  132 + parent.deregisterSession(getDeviceInfo().getDeviceName());
  133 + }
  134 +
  135 + @Override
  136 + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) {
  137 + // This feature is not supported in the TB IoT Gateway yet.
  138 + }
  139 +
  140 + @Override
  141 + public void onDeviceDeleted(DeviceId deviceId) {
  142 + parent.onDeviceDeleted(this.getSessionInfo().getDeviceName());
  143 + }
  144 +
  145 + private boolean isAckExpected(MqttMessage message) {
  146 + return message.fixedHeader().qosLevel().value() > 0;
  147 + }
  148 +
  149 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.session;
  17 +
  18 +
  19 +import com.google.common.util.concurrent.FutureCallback;
  20 +import com.google.common.util.concurrent.Futures;
  21 +import com.google.common.util.concurrent.ListenableFuture;
  22 +import com.google.common.util.concurrent.SettableFuture;
  23 +import com.google.gson.JsonArray;
  24 +import com.google.gson.JsonElement;
  25 +import com.google.gson.JsonNull;
  26 +import com.google.gson.JsonObject;
  27 +import com.google.gson.JsonSyntaxException;
  28 +import com.google.protobuf.InvalidProtocolBufferException;
  29 +import com.google.protobuf.ProtocolStringList;
  30 +import io.netty.buffer.ByteBuf;
  31 +import io.netty.channel.ChannelFuture;
  32 +import io.netty.channel.ChannelHandlerContext;
  33 +import io.netty.handler.codec.mqtt.MqttMessage;
  34 +import io.netty.handler.codec.mqtt.MqttPublishMessage;
  35 +import lombok.extern.slf4j.Slf4j;
  36 +import org.springframework.util.CollectionUtils;
  37 +import org.springframework.util.ConcurrentReferenceHashMap;
  38 +import org.springframework.util.StringUtils;
  39 +import org.thingsboard.server.common.data.id.DeviceId;
  40 +import org.thingsboard.server.common.transport.TransportService;
  41 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  42 +import org.thingsboard.server.common.transport.adaptor.AdaptorException;
  43 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  44 +import org.thingsboard.server.common.transport.adaptor.ProtoConverter;
  45 +import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
  46 +import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
  47 +import org.thingsboard.server.gen.transport.TransportApiProtos;
  48 +import org.thingsboard.server.gen.transport.TransportProtos;
  49 +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
  50 +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
  51 +import org.thingsboard.server.transport.tcp.TcpTransportContext;
  52 +import org.thingsboard.server.transport.tcp.TcpTransportHandler;
  53 +import org.thingsboard.server.transport.tcp.adaptors.JsonTcpAdaptor;
  54 +import org.thingsboard.server.transport.tcp.adaptors.TcpTransportAdaptor;
  55 +import org.thingsboard.server.transport.tcp.adaptors.ProtoTcpAdaptor;
  56 +
  57 +import javax.annotation.Nullable;
  58 +import java.util.Collections;
  59 +import java.util.HashSet;
  60 +import java.util.List;
  61 +import java.util.Map;
  62 +import java.util.Set;
  63 +import java.util.UUID;
  64 +import java.util.concurrent.ConcurrentHashMap;
  65 +import java.util.concurrent.ConcurrentMap;
  66 +import java.util.concurrent.locks.Lock;
  67 +import java.util.concurrent.locks.ReentrantLock;
  68 +
  69 +import static org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
  70 +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED;
  71 +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN;
  72 +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG;
  73 +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_RPC_ASYNC_MSG;
  74 +
  75 +/**
  76 + * Created by ashvayka on 19.01.17.
  77 + */
  78 +@Slf4j
  79 +public class GatewaySessionHandler {
  80 +
  81 + private static final String DEFAULT_DEVICE_TYPE = "default";
  82 + private static final String CAN_T_PARSE_VALUE = "Can't parse value: ";
  83 + private static final String DEVICE_PROPERTY = "device";
  84 +
  85 + private final TcpTransportContext context;
  86 + private final TransportService transportService;
  87 + private final TransportDeviceInfo gateway;
  88 + private final UUID sessionId;
  89 + private final ConcurrentMap<String, Lock> deviceCreationLockMap;
  90 + private final ConcurrentMap<String, GatewayDeviceSessionCtx> devices;
  91 + private final ConcurrentMap<String, ListenableFuture<GatewayDeviceSessionCtx>> deviceFutures;
  92 + private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
  93 + private final ChannelHandlerContext channel;
  94 + private final DeviceSessionCtx deviceSessionCtx;
  95 +
  96 + public GatewaySessionHandler(DeviceSessionCtx deviceSessionCtx, UUID sessionId) {
  97 + this.context = deviceSessionCtx.getContext();
  98 + this.transportService = context.getTransportService();
  99 + this.deviceSessionCtx = deviceSessionCtx;
  100 + this.gateway = deviceSessionCtx.getDeviceInfo();
  101 + this.sessionId = sessionId;
  102 + this.devices = new ConcurrentHashMap<>();
  103 + this.deviceFutures = new ConcurrentHashMap<>();
  104 + this.deviceCreationLockMap = createWeakMap();
  105 + this.mqttQoSMap = deviceSessionCtx.getMqttQoSMap();
  106 + this.channel = deviceSessionCtx.getChannel();
  107 + }
  108 +
  109 + ConcurrentReferenceHashMap<String, Lock> createWeakMap() {
  110 + return new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK);
  111 + }
  112 +
  113 + public void onDeviceConnect(MqttPublishMessage mqttMsg) throws AdaptorException {
  114 + if (isJsonPayloadType()) {
  115 + onDeviceConnectJson(mqttMsg);
  116 + } else {
  117 + onDeviceConnectProto(mqttMsg);
  118 + }
  119 + }
  120 +
  121 + public void onDeviceDisconnect(MqttPublishMessage mqttMsg) throws AdaptorException {
  122 + if (isJsonPayloadType()) {
  123 + onDeviceDisconnectJson(mqttMsg);
  124 + } else {
  125 + onDeviceDisconnectProto(mqttMsg);
  126 + }
  127 + }
  128 +
  129 + public void onDeviceTelemetry(MqttPublishMessage mqttMsg) throws AdaptorException {
  130 + int msgId = getMsgId(mqttMsg);
  131 + ByteBuf payload = mqttMsg.payload();
  132 + if (isJsonPayloadType()) {
  133 + onDeviceTelemetryJson(msgId, payload);
  134 + } else {
  135 + onDeviceTelemetryProto(msgId, payload);
  136 + }
  137 + }
  138 +
  139 + public void onDeviceClaim(MqttPublishMessage mqttMsg) throws AdaptorException {
  140 + int msgId = getMsgId(mqttMsg);
  141 + ByteBuf payload = mqttMsg.payload();
  142 + if (isJsonPayloadType()) {
  143 + onDeviceClaimJson(msgId, payload);
  144 + } else {
  145 + onDeviceClaimProto(msgId, payload);
  146 + }
  147 + }
  148 +
  149 + public void onDeviceAttributes(MqttPublishMessage mqttMsg) throws AdaptorException {
  150 + int msgId = getMsgId(mqttMsg);
  151 + ByteBuf payload = mqttMsg.payload();
  152 + if (isJsonPayloadType()) {
  153 + onDeviceAttributesJson(msgId, payload);
  154 + } else {
  155 + onDeviceAttributesProto(msgId, payload);
  156 + }
  157 + }
  158 +
  159 + public void onDeviceAttributesRequest(MqttPublishMessage mqttMsg) throws AdaptorException {
  160 + if (isJsonPayloadType()) {
  161 + onDeviceAttributesRequestJson(mqttMsg);
  162 + } else {
  163 + onDeviceAttributesRequestProto(mqttMsg);
  164 + }
  165 + }
  166 +
  167 + public void onDeviceRpcResponse(MqttPublishMessage mqttMsg) throws AdaptorException {
  168 + int msgId = getMsgId(mqttMsg);
  169 + ByteBuf payload = mqttMsg.payload();
  170 + if (isJsonPayloadType()) {
  171 + onDeviceRpcResponseJson(msgId, payload);
  172 + } else {
  173 + onDeviceRpcResponseProto(msgId, payload);
  174 + }
  175 + }
  176 +
  177 + public void onGatewayDisconnect() {
  178 + devices.forEach(this::deregisterSession);
  179 + }
  180 +
  181 + public void onDeviceDeleted(String deviceName) {
  182 + deregisterSession(deviceName);
  183 + }
  184 +
  185 + public String getNodeId() {
  186 + return context.getNodeId();
  187 + }
  188 +
  189 + public UUID getSessionId() {
  190 + return sessionId;
  191 + }
  192 +
  193 + public TcpTransportAdaptor getPayloadAdaptor() {
  194 + return deviceSessionCtx.getPayloadAdaptor();
  195 + }
  196 +
  197 + void deregisterSession(String deviceName) {
  198 + GatewayDeviceSessionCtx deviceSessionCtx = devices.remove(deviceName);
  199 + if (deviceSessionCtx != null) {
  200 + deregisterSession(deviceName, deviceSessionCtx);
  201 + } else {
  202 + log.debug("[{}] Device [{}] was already removed from the gateway session", sessionId, deviceName);
  203 + }
  204 + }
  205 +
  206 + ChannelFuture writeAndFlush(MqttMessage mqttMessage) {
  207 + return channel.writeAndFlush(mqttMessage);
  208 + }
  209 +
  210 + int nextMsgId() {
  211 + return deviceSessionCtx.nextMsgId();
  212 + }
  213 +
  214 + private boolean isJsonPayloadType() {
  215 + return deviceSessionCtx.isJsonPayloadType();
  216 + }
  217 +
  218 + private void processOnConnect(MqttPublishMessage msg, String deviceName, String deviceType) {
  219 + log.trace("[{}] onDeviceConnect: {}", sessionId, deviceName);
  220 + Futures.addCallback(onDeviceConnect(deviceName, deviceType), new FutureCallback<GatewayDeviceSessionCtx>() {
  221 + @Override
  222 + public void onSuccess(@Nullable GatewayDeviceSessionCtx result) {
  223 + ack(msg);
  224 + log.trace("[{}] onDeviceConnectOk: {}", sessionId, deviceName);
  225 + }
  226 +
  227 + @Override
  228 + public void onFailure(Throwable t) {
  229 + log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, t);
  230 +
  231 + }
  232 + }, context.getExecutor());
  233 + }
  234 +
  235 + private ListenableFuture<GatewayDeviceSessionCtx> onDeviceConnect(String deviceName, String deviceType) {
  236 + GatewayDeviceSessionCtx result = devices.get(deviceName);
  237 + if (result == null) {
  238 + Lock deviceCreationLock = deviceCreationLockMap.computeIfAbsent(deviceName, s -> new ReentrantLock());
  239 + deviceCreationLock.lock();
  240 + try {
  241 + result = devices.get(deviceName);
  242 + if (result == null) {
  243 + return getDeviceCreationFuture(deviceName, deviceType);
  244 + } else {
  245 + return Futures.immediateFuture(result);
  246 + }
  247 + } finally {
  248 + deviceCreationLock.unlock();
  249 + }
  250 + } else {
  251 + return Futures.immediateFuture(result);
  252 + }
  253 + }
  254 +
  255 + private ListenableFuture<GatewayDeviceSessionCtx> getDeviceCreationFuture(String deviceName, String deviceType) {
  256 + final SettableFuture<GatewayDeviceSessionCtx> futureToSet = SettableFuture.create();
  257 + ListenableFuture<GatewayDeviceSessionCtx> future = deviceFutures.putIfAbsent(deviceName, futureToSet);
  258 + if (future != null) {
  259 + return future;
  260 + }
  261 + try {
  262 + transportService.process(GetOrCreateDeviceFromGatewayRequestMsg.newBuilder()
  263 + .setDeviceName(deviceName)
  264 + .setDeviceType(deviceType)
  265 + .setGatewayIdMSB(gateway.getDeviceId().getId().getMostSignificantBits())
  266 + .setGatewayIdLSB(gateway.getDeviceId().getId().getLeastSignificantBits()).build(),
  267 + new TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse>() {
  268 + @Override
  269 + public void onSuccess(GetOrCreateDeviceFromGatewayResponse msg) {
  270 + GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), msg.getDeviceProfile(), mqttQoSMap, transportService);
  271 + if (devices.putIfAbsent(deviceName, deviceSessionCtx) == null) {
  272 + log.trace("[{}] First got or created device [{}], type [{}] for the gateway session", sessionId, deviceName, deviceType);
  273 + SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo();
  274 + transportService.registerAsyncSession(deviceSessionInfo, deviceSessionCtx);
  275 + transportService.process(TransportProtos.TransportToDeviceActorMsg.newBuilder()
  276 + .setSessionInfo(deviceSessionInfo)
  277 + .setSessionEvent(SESSION_EVENT_MSG_OPEN)
  278 + .setSubscribeToAttributes(SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG)
  279 + .setSubscribeToRPC(SUBSCRIBE_TO_RPC_ASYNC_MSG)
  280 + .build(), null);
  281 + }
  282 + futureToSet.set(devices.get(deviceName));
  283 + deviceFutures.remove(deviceName);
  284 + }
  285 +
  286 + @Override
  287 + public void onError(Throwable e) {
  288 + log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, e);
  289 + futureToSet.setException(e);
  290 + deviceFutures.remove(deviceName);
  291 + }
  292 + });
  293 + return futureToSet;
  294 + } catch (Throwable e) {
  295 + deviceFutures.remove(deviceName);
  296 + throw e;
  297 + }
  298 + }
  299 +
  300 + private int getMsgId(MqttPublishMessage mqttMsg) {
  301 + return mqttMsg.variableHeader().packetId();
  302 + }
  303 +
  304 + private void onDeviceConnectJson(MqttPublishMessage mqttMsg) throws AdaptorException {
  305 + JsonElement json = getJson(mqttMsg);
  306 + String deviceName = checkDeviceName(getDeviceName(json));
  307 + String deviceType = getDeviceType(json);
  308 + processOnConnect(mqttMsg, deviceName, deviceType);
  309 + }
  310 +
  311 + private void onDeviceConnectProto(MqttPublishMessage mqttMsg) throws AdaptorException {
  312 + try {
  313 + TransportApiProtos.ConnectMsg connectProto = TransportApiProtos.ConnectMsg.parseFrom(getBytes(mqttMsg.payload()));
  314 + String deviceName = checkDeviceName(connectProto.getDeviceName());
  315 + String deviceType = StringUtils.isEmpty(connectProto.getDeviceType()) ? DEFAULT_DEVICE_TYPE : connectProto.getDeviceType();
  316 + processOnConnect(mqttMsg, deviceName, deviceType);
  317 + } catch (RuntimeException | InvalidProtocolBufferException e) {
  318 + throw new AdaptorException(e);
  319 + }
  320 + }
  321 +
  322 + private void onDeviceDisconnectJson(MqttPublishMessage msg) throws AdaptorException {
  323 + String deviceName = checkDeviceName(getDeviceName(getJson(msg)));
  324 + processOnDisconnect(msg, deviceName);
  325 + }
  326 +
  327 + private void onDeviceDisconnectProto(MqttPublishMessage mqttMsg) throws AdaptorException {
  328 + try {
  329 + TransportApiProtos.DisconnectMsg connectProto = TransportApiProtos.DisconnectMsg.parseFrom(getBytes(mqttMsg.payload()));
  330 + String deviceName = checkDeviceName(connectProto.getDeviceName());
  331 + processOnDisconnect(mqttMsg, deviceName);
  332 + } catch (RuntimeException | InvalidProtocolBufferException e) {
  333 + throw new AdaptorException(e);
  334 + }
  335 + }
  336 +
  337 + private void processOnDisconnect(MqttPublishMessage msg, String deviceName) {
  338 + deregisterSession(deviceName);
  339 + ack(msg);
  340 + }
  341 +
  342 + private void onDeviceTelemetryJson(int msgId, ByteBuf payload) throws AdaptorException {
  343 + JsonElement json = JsonTcpAdaptor.validateJsonPayload(sessionId, payload);
  344 + if (json.isJsonObject()) {
  345 + JsonObject jsonObj = json.getAsJsonObject();
  346 + for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) {
  347 + String deviceName = deviceEntry.getKey();
  348 + Futures.addCallback(checkDeviceConnected(deviceName),
  349 + new FutureCallback<>() {
  350 + @Override
  351 + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
  352 + if (!deviceEntry.getValue().isJsonArray()) {
  353 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
  354 + }
  355 + try {
  356 + TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(deviceEntry.getValue().getAsJsonArray());
  357 + processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, msgId);
  358 + } catch (Throwable e) {
  359 + log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, deviceEntry.getValue(), e);
  360 + channel.close();
  361 + }
  362 + }
  363 +
  364 + @Override
  365 + public void onFailure(Throwable t) {
  366 + log.debug("[{}] Failed to process device telemetry command: {}", sessionId, deviceName, t);
  367 + }
  368 + }, context.getExecutor());
  369 + }
  370 + } else {
  371 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
  372 + }
  373 + }
  374 +
  375 + private void onDeviceTelemetryProto(int msgId, ByteBuf payload) throws AdaptorException {
  376 + try {
  377 + TransportApiProtos.GatewayTelemetryMsg telemetryMsgProto = TransportApiProtos.GatewayTelemetryMsg.parseFrom(getBytes(payload));
  378 + List<TransportApiProtos.TelemetryMsg> deviceMsgList = telemetryMsgProto.getMsgList();
  379 + if (!CollectionUtils.isEmpty(deviceMsgList)) {
  380 + deviceMsgList.forEach(telemetryMsg -> {
  381 + String deviceName = checkDeviceName(telemetryMsg.getDeviceName());
  382 + Futures.addCallback(checkDeviceConnected(deviceName),
  383 + new FutureCallback<GatewayDeviceSessionCtx>() {
  384 + @Override
  385 + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
  386 + TransportProtos.PostTelemetryMsg msg = telemetryMsg.getMsg();
  387 + try {
  388 + TransportProtos.PostTelemetryMsg postTelemetryMsg = ProtoConverter.validatePostTelemetryMsg(msg.toByteArray());
  389 + processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, msgId);
  390 + } catch (Throwable e) {
  391 + log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, msg, e);
  392 + channel.close();
  393 + }
  394 + }
  395 +
  396 + @Override
  397 + public void onFailure(Throwable t) {
  398 + log.debug("[{}] Failed to process device telemetry command: {}", sessionId, deviceName, t);
  399 + }
  400 + }, context.getExecutor());
  401 + });
  402 + } else {
  403 + log.debug("[{}] Devices telemetry messages is empty for: [{}]", sessionId, gateway.getDeviceId());
  404 + throw new IllegalArgumentException("[" + sessionId + "] Devices telemetry messages is empty for [" + gateway.getDeviceId() + "]");
  405 + }
  406 + } catch (RuntimeException | InvalidProtocolBufferException e) {
  407 + throw new AdaptorException(e);
  408 + }
  409 + }
  410 +
  411 + private void processPostTelemetryMsg(GatewayDeviceSessionCtx deviceCtx, TransportProtos.PostTelemetryMsg postTelemetryMsg, String deviceName, int msgId) {
  412 + transportService.process(deviceCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(channel, deviceName, msgId, postTelemetryMsg));
  413 + }
  414 +
  415 + private void onDeviceClaimJson(int msgId, ByteBuf payload) throws AdaptorException {
  416 + JsonElement json = JsonTcpAdaptor.validateJsonPayload(sessionId, payload);
  417 + if (json.isJsonObject()) {
  418 + JsonObject jsonObj = json.getAsJsonObject();
  419 + for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) {
  420 + String deviceName = deviceEntry.getKey();
  421 + Futures.addCallback(checkDeviceConnected(deviceName),
  422 + new FutureCallback<GatewayDeviceSessionCtx>() {
  423 + @Override
  424 + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
  425 + if (!deviceEntry.getValue().isJsonObject()) {
  426 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
  427 + }
  428 + try {
  429 + DeviceId deviceId = deviceCtx.getDeviceId();
  430 + TransportProtos.ClaimDeviceMsg claimDeviceMsg = JsonConverter.convertToClaimDeviceProto(deviceId, deviceEntry.getValue());
  431 + processClaimDeviceMsg(deviceCtx, claimDeviceMsg, deviceName, msgId);
  432 + } catch (Throwable e) {
  433 + log.warn("[{}][{}] Failed to convert claim message: {}", gateway.getDeviceId(), deviceName, deviceEntry.getValue(), e);
  434 + }
  435 + }
  436 +
  437 + @Override
  438 + public void onFailure(Throwable t) {
  439 + log.debug("[{}] Failed to process device claiming command: {}", sessionId, deviceName, t);
  440 + }
  441 + }, context.getExecutor());
  442 + }
  443 + } else {
  444 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
  445 + }
  446 + }
  447 +
  448 + private void onDeviceClaimProto(int msgId, ByteBuf payload) throws AdaptorException {
  449 + try {
  450 + TransportApiProtos.GatewayClaimMsg claimMsgProto = TransportApiProtos.GatewayClaimMsg.parseFrom(getBytes(payload));
  451 + List<TransportApiProtos.ClaimDeviceMsg> claimMsgList = claimMsgProto.getMsgList();
  452 + if (!CollectionUtils.isEmpty(claimMsgList)) {
  453 + claimMsgList.forEach(claimDeviceMsg -> {
  454 + String deviceName = checkDeviceName(claimDeviceMsg.getDeviceName());
  455 + Futures.addCallback(checkDeviceConnected(deviceName),
  456 + new FutureCallback<GatewayDeviceSessionCtx>() {
  457 + @Override
  458 + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
  459 + TransportApiProtos.ClaimDevice claimRequest = claimDeviceMsg.getClaimRequest();
  460 + if (claimRequest == null) {
  461 + throw new IllegalArgumentException("Claim request for device: " + deviceName + " is null!");
  462 + }
  463 + try {
  464 + DeviceId deviceId = deviceCtx.getDeviceId();
  465 + TransportProtos.ClaimDeviceMsg claimDeviceMsg = ProtoConverter.convertToClaimDeviceProto(deviceId, claimRequest.toByteArray());
  466 + processClaimDeviceMsg(deviceCtx, claimDeviceMsg, deviceName, msgId);
  467 + } catch (Throwable e) {
  468 + log.warn("[{}][{}] Failed to convert claim message: {}", gateway.getDeviceId(), deviceName, claimRequest, e);
  469 + }
  470 + }
  471 +
  472 + @Override
  473 + public void onFailure(Throwable t) {
  474 + log.debug("[{}] Failed to process device claiming command: {}", sessionId, deviceName, t);
  475 + }
  476 + }, context.getExecutor());
  477 + });
  478 + } else {
  479 + log.debug("[{}] Devices claim messages is empty for: [{}]", sessionId, gateway.getDeviceId());
  480 + throw new IllegalArgumentException("[" + sessionId + "] Devices claim messages is empty for [" + gateway.getDeviceId() + "]");
  481 + }
  482 + } catch (RuntimeException | InvalidProtocolBufferException e) {
  483 + throw new AdaptorException(e);
  484 + }
  485 + }
  486 +
  487 + private void processClaimDeviceMsg(GatewayDeviceSessionCtx deviceCtx, TransportProtos.ClaimDeviceMsg claimDeviceMsg, String deviceName, int msgId) {
  488 + transportService.process(deviceCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(channel, deviceName, msgId, claimDeviceMsg));
  489 + }
  490 +
  491 + private void onDeviceAttributesJson(int msgId, ByteBuf payload) throws AdaptorException {
  492 + JsonElement json = JsonTcpAdaptor.validateJsonPayload(sessionId, payload);
  493 + if (json.isJsonObject()) {
  494 + JsonObject jsonObj = json.getAsJsonObject();
  495 + for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) {
  496 + String deviceName = deviceEntry.getKey();
  497 + Futures.addCallback(checkDeviceConnected(deviceName),
  498 + new FutureCallback<GatewayDeviceSessionCtx>() {
  499 + @Override
  500 + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
  501 + if (!deviceEntry.getValue().isJsonObject()) {
  502 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
  503 + }
  504 + TransportProtos.PostAttributeMsg postAttributeMsg = JsonConverter.convertToAttributesProto(deviceEntry.getValue().getAsJsonObject());
  505 + processPostAttributesMsg(deviceCtx, postAttributeMsg, deviceName, msgId);
  506 + }
  507 +
  508 + @Override
  509 + public void onFailure(Throwable t) {
  510 + log.debug("[{}] Failed to process device attributes command: {}", sessionId, deviceName, t);
  511 + }
  512 + }, context.getExecutor());
  513 + }
  514 + } else {
  515 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
  516 + }
  517 + }
  518 +
  519 + private void onDeviceAttributesProto(int msgId, ByteBuf payload) throws AdaptorException {
  520 + try {
  521 + TransportApiProtos.GatewayAttributesMsg attributesMsgProto = TransportApiProtos.GatewayAttributesMsg.parseFrom(getBytes(payload));
  522 + List<TransportApiProtos.AttributesMsg> attributesMsgList = attributesMsgProto.getMsgList();
  523 + if (!CollectionUtils.isEmpty(attributesMsgList)) {
  524 + attributesMsgList.forEach(attributesMsg -> {
  525 + String deviceName = checkDeviceName(attributesMsg.getDeviceName());
  526 + Futures.addCallback(checkDeviceConnected(deviceName),
  527 + new FutureCallback<GatewayDeviceSessionCtx>() {
  528 + @Override
  529 + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
  530 + TransportProtos.PostAttributeMsg kvListProto = attributesMsg.getMsg();
  531 + if (kvListProto == null) {
  532 + throw new IllegalArgumentException("Attributes List for device: " + deviceName + " is empty!");
  533 + }
  534 + try {
  535 + TransportProtos.PostAttributeMsg postAttributeMsg = ProtoConverter.validatePostAttributeMsg(kvListProto.toByteArray());
  536 + processPostAttributesMsg(deviceCtx, postAttributeMsg, deviceName, msgId);
  537 + } catch (Throwable e) {
  538 + log.warn("[{}][{}] Failed to process device attributes command: {}", gateway.getDeviceId(), deviceName, kvListProto, e);
  539 + }
  540 + }
  541 +
  542 + @Override
  543 + public void onFailure(Throwable t) {
  544 + log.debug("[{}] Failed to process device attributes command: {}", sessionId, deviceName, t);
  545 + }
  546 + }, context.getExecutor());
  547 + });
  548 + } else {
  549 + log.debug("[{}] Devices attributes keys list is empty for: [{}]", sessionId, gateway.getDeviceId());
  550 + throw new IllegalArgumentException("[" + sessionId + "] Devices attributes keys list is empty for [" + gateway.getDeviceId() + "]");
  551 + }
  552 + } catch (RuntimeException | InvalidProtocolBufferException e) {
  553 + throw new AdaptorException(e);
  554 + }
  555 + }
  556 +
  557 + private void processPostAttributesMsg(GatewayDeviceSessionCtx deviceCtx, TransportProtos.PostAttributeMsg postAttributeMsg, String deviceName, int msgId) {
  558 + transportService.process(deviceCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(channel, deviceName, msgId, postAttributeMsg));
  559 + }
  560 +
  561 + private void onDeviceAttributesRequestJson(MqttPublishMessage msg) throws AdaptorException {
  562 + JsonElement json = JsonTcpAdaptor.validateJsonPayload(sessionId, msg.payload());
  563 + if (json.isJsonObject()) {
  564 + JsonObject jsonObj = json.getAsJsonObject();
  565 + int requestId = jsonObj.get("id").getAsInt();
  566 + String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString();
  567 + boolean clientScope = jsonObj.get("client").getAsBoolean();
  568 + Set<String> keys;
  569 + if (jsonObj.has("key")) {
  570 + keys = Collections.singleton(jsonObj.get("key").getAsString());
  571 + } else {
  572 + JsonArray keysArray = jsonObj.get("keys").getAsJsonArray();
  573 + keys = new HashSet<>();
  574 + for (JsonElement keyObj : keysArray) {
  575 + keys.add(keyObj.getAsString());
  576 + }
  577 + }
  578 + TransportProtos.GetAttributeRequestMsg requestMsg = toGetAttributeRequestMsg(requestId, clientScope, keys);
  579 + processGetAttributeRequestMessage(msg, deviceName, requestMsg);
  580 + } else {
  581 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
  582 + }
  583 + }
  584 +
  585 + private void onDeviceAttributesRequestProto(MqttPublishMessage mqttMsg) throws AdaptorException {
  586 + try {
  587 + TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = TransportApiProtos.GatewayAttributesRequestMsg.parseFrom(getBytes(mqttMsg.payload()));
  588 + String deviceName = checkDeviceName(gatewayAttributesRequestMsg.getDeviceName());
  589 + int requestId = gatewayAttributesRequestMsg.getId();
  590 + boolean clientScope = gatewayAttributesRequestMsg.getClient();
  591 + ProtocolStringList keysList = gatewayAttributesRequestMsg.getKeysList();
  592 + Set<String> keys = new HashSet<>(keysList);
  593 + TransportProtos.GetAttributeRequestMsg requestMsg = toGetAttributeRequestMsg(requestId, clientScope, keys);
  594 + processGetAttributeRequestMessage(mqttMsg, deviceName, requestMsg);
  595 + } catch (RuntimeException | InvalidProtocolBufferException e) {
  596 + throw new AdaptorException(e);
  597 + }
  598 + }
  599 +
  600 + private void onDeviceRpcResponseJson(int msgId, ByteBuf payload) throws AdaptorException {
  601 + JsonElement json = JsonTcpAdaptor.validateJsonPayload(sessionId, payload);
  602 + if (json.isJsonObject()) {
  603 + JsonObject jsonObj = json.getAsJsonObject();
  604 + String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString();
  605 + Futures.addCallback(checkDeviceConnected(deviceName),
  606 + new FutureCallback<GatewayDeviceSessionCtx>() {
  607 + @Override
  608 + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
  609 + Integer requestId = jsonObj.get("id").getAsInt();
  610 + String data = jsonObj.get("data").toString();
  611 + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
  612 + .setRequestId(requestId).setPayload(data).build();
  613 + processRpcResponseMsg(deviceCtx, rpcResponseMsg, deviceName, msgId);
  614 + }
  615 +
  616 + @Override
  617 + public void onFailure(Throwable t) {
  618 + log.debug("[{}] Failed to process device Rpc response command: {}", sessionId, deviceName, t);
  619 + }
  620 + }, context.getExecutor());
  621 + } else {
  622 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
  623 + }
  624 + }
  625 +
  626 + private void onDeviceRpcResponseProto(int msgId, ByteBuf payload) throws AdaptorException {
  627 + try {
  628 + TransportApiProtos.GatewayRpcResponseMsg gatewayRpcResponseMsg = TransportApiProtos.GatewayRpcResponseMsg.parseFrom(getBytes(payload));
  629 + String deviceName = checkDeviceName(gatewayRpcResponseMsg.getDeviceName());
  630 + Futures.addCallback(checkDeviceConnected(deviceName),
  631 + new FutureCallback<GatewayDeviceSessionCtx>() {
  632 + @Override
  633 + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
  634 + Integer requestId = gatewayRpcResponseMsg.getId();
  635 + String data = gatewayRpcResponseMsg.getData();
  636 + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
  637 + .setRequestId(requestId).setPayload(data).build();
  638 + processRpcResponseMsg(deviceCtx, rpcResponseMsg, deviceName, msgId);
  639 + }
  640 +
  641 + @Override
  642 + public void onFailure(Throwable t) {
  643 + log.debug("[{}] Failed to process device Rpc response command: {}", sessionId, deviceName, t);
  644 + }
  645 + }, context.getExecutor());
  646 + } catch (RuntimeException | InvalidProtocolBufferException e) {
  647 + throw new AdaptorException(e);
  648 + }
  649 + }
  650 +
  651 + private void processRpcResponseMsg(GatewayDeviceSessionCtx deviceCtx, TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg, String deviceName, int msgId) {
  652 + transportService.process(deviceCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(channel, deviceName, msgId, rpcResponseMsg));
  653 + }
  654 +
  655 + private void processGetAttributeRequestMessage(MqttPublishMessage mqttMsg, String deviceName, TransportProtos.GetAttributeRequestMsg requestMsg) {
  656 + int msgId = getMsgId(mqttMsg);
  657 + Futures.addCallback(checkDeviceConnected(deviceName),
  658 + new FutureCallback<GatewayDeviceSessionCtx>() {
  659 + @Override
  660 + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
  661 + transportService.process(deviceCtx.getSessionInfo(), requestMsg, getPubAckCallback(channel, deviceName, msgId, requestMsg));
  662 + }
  663 +
  664 + @Override
  665 + public void onFailure(Throwable t) {
  666 + ack(mqttMsg);
  667 + log.debug("[{}] Failed to process device attributes request command: {}", sessionId, deviceName, t);
  668 + }
  669 + }, context.getExecutor());
  670 + }
  671 +
  672 + private TransportProtos.GetAttributeRequestMsg toGetAttributeRequestMsg(int requestId, boolean clientScope, Set<String> keys) {
  673 + TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
  674 + result.setRequestId(requestId);
  675 +
  676 + if (clientScope) {
  677 + result.addAllClientAttributeNames(keys);
  678 + } else {
  679 + result.addAllSharedAttributeNames(keys);
  680 + }
  681 + return result.build();
  682 + }
  683 +
  684 + private ListenableFuture<GatewayDeviceSessionCtx> checkDeviceConnected(String deviceName) {
  685 + GatewayDeviceSessionCtx ctx = devices.get(deviceName);
  686 + if (ctx == null) {
  687 + log.debug("[{}] Missing device [{}] for the gateway session", sessionId, deviceName);
  688 + return onDeviceConnect(deviceName, DEFAULT_DEVICE_TYPE);
  689 + } else {
  690 + return Futures.immediateFuture(ctx);
  691 + }
  692 + }
  693 +
  694 + private String checkDeviceName(String deviceName) {
  695 + if (StringUtils.isEmpty(deviceName)) {
  696 + throw new RuntimeException("Device name is empty!");
  697 + } else {
  698 + return deviceName;
  699 + }
  700 + }
  701 +
  702 + private String getDeviceName(JsonElement json) {
  703 + return json.getAsJsonObject().get(DEVICE_PROPERTY).getAsString();
  704 + }
  705 +
  706 + private String getDeviceType(JsonElement json) {
  707 + JsonElement type = json.getAsJsonObject().get("type");
  708 + return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString();
  709 + }
  710 +
  711 + private JsonElement getJson(MqttPublishMessage mqttMsg) throws AdaptorException {
  712 + return JsonTcpAdaptor.validateJsonPayload(sessionId, mqttMsg.payload());
  713 + }
  714 +
  715 + private byte[] getBytes(ByteBuf payload) {
  716 + return ProtoTcpAdaptor.toBytes(payload);
  717 + }
  718 +
  719 + private void ack(MqttPublishMessage msg) {
  720 + int msgId = getMsgId(msg);
  721 + if (msgId > 0) {
  722 + writeAndFlush(TcpTransportHandler.createMqttPubAckMsg(msgId));
  723 + }
  724 + }
  725 +
  726 + private void deregisterSession(String deviceName, GatewayDeviceSessionCtx deviceSessionCtx) {
  727 + transportService.deregisterSession(deviceSessionCtx.getSessionInfo());
  728 + transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null);
  729 + log.debug("[{}] Removed device [{}] from the gateway session", sessionId, deviceName);
  730 + }
  731 +
  732 + private <T> TransportServiceCallback<Void> getPubAckCallback(final ChannelHandlerContext ctx, final String deviceName, final int msgId, final T msg) {
  733 + return new TransportServiceCallback<Void>() {
  734 + @Override
  735 + public void onSuccess(Void dummy) {
  736 + log.trace("[{}][{}] Published msg: {}", sessionId, deviceName, msg);
  737 + if (msgId > 0) {
  738 + ctx.writeAndFlush(TcpTransportHandler.createMqttPubAckMsg(msgId));
  739 + }
  740 + }
  741 +
  742 + @Override
  743 + public void onError(Throwable e) {
  744 + log.trace("[{}] Failed to publish msg: {} for device: {}", sessionId, msg, deviceName, e);
  745 + ctx.close();
  746 + }
  747 + };
  748 + }
  749 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.session;
  17 +
  18 +import io.netty.handler.codec.mqtt.MqttQoS;
  19 +import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
  20 +
  21 +import java.util.List;
  22 +import java.util.Map;
  23 +import java.util.UUID;
  24 +import java.util.concurrent.ConcurrentMap;
  25 +import java.util.stream.Collectors;
  26 +
  27 +/**
  28 + * Created by ashvayka on 30.08.18.
  29 + */
  30 +public abstract class MqttDeviceAwareSessionContext extends DeviceAwareSessionContext {
  31 +
  32 + private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
  33 +
  34 + public MqttDeviceAwareSessionContext(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap) {
  35 + super(sessionId);
  36 + this.mqttQoSMap = mqttQoSMap;
  37 + }
  38 +
  39 + public ConcurrentMap<MqttTopicMatcher, Integer> getMqttQoSMap() {
  40 + return mqttQoSMap;
  41 + }
  42 +
  43 + public MqttQoS getQoSForTopic(String topic) {
  44 + List<Integer> qosList = mqttQoSMap.entrySet()
  45 + .stream()
  46 + .filter(entry -> entry.getKey().matches(topic))
  47 + .map(Map.Entry::getValue)
  48 + .collect(Collectors.toList());
  49 + if (!qosList.isEmpty()) {
  50 + return MqttQoS.valueOf(qosList.get(0));
  51 + } else {
  52 + return MqttQoS.AT_LEAST_ONCE;
  53 + }
  54 + }
  55 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.session;
  17 +
  18 +import java.util.regex.Pattern;
  19 +
  20 +public class MqttTopicMatcher {
  21 +
  22 + private final String topic;
  23 + private final Pattern topicRegex;
  24 +
  25 + public MqttTopicMatcher(String topic) {
  26 + if(topic == null){
  27 + throw new NullPointerException("topic");
  28 + }
  29 + this.topic = topic;
  30 + this.topicRegex = Pattern.compile(topic.replace("+", "[^/]+").replace("#", ".+") + "$");
  31 + }
  32 +
  33 + public String getTopic() {
  34 + return topic;
  35 + }
  36 +
  37 + public boolean matches(String topic){
  38 + return this.topicRegex.matcher(topic).matches();
  39 + }
  40 +
  41 + @Override
  42 + public boolean equals(Object o) {
  43 + if (this == o) return true;
  44 + if (o == null || getClass() != o.getClass()) return false;
  45 +
  46 + MqttTopicMatcher that = (MqttTopicMatcher) o;
  47 +
  48 + return topic.equals(that.topic);
  49 + }
  50 +
  51 + @Override
  52 + public int hashCode() {
  53 + return topic.hashCode();
  54 + }
  55 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.util;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class AlwaysTrueTopicFilter implements TcpTopicFilter {
  22 +
  23 + @Override
  24 + public boolean filter(String topic) {
  25 + return true;
  26 + }
  27 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.util;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class EqualsTopicFilter implements TcpTopicFilter {
  22 +
  23 + private final String filter;
  24 +
  25 + @Override
  26 + public boolean filter(String topic) {
  27 + return filter.equals(topic);
  28 + }
  29 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.util;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  20 +
  21 +import java.util.concurrent.ConcurrentHashMap;
  22 +import java.util.concurrent.ConcurrentMap;
  23 +
  24 +@Slf4j
  25 +public class MqttTopicFilterFactory {
  26 +
  27 + private static final ConcurrentMap<String, TcpTopicFilter> filters = new ConcurrentHashMap<>();
  28 + private static final TcpTopicFilter DEFAULT_TELEMETRY_TOPIC_FILTER = toFilter(MqttTopics.DEVICE_TELEMETRY_TOPIC);
  29 + private static final TcpTopicFilter DEFAULT_ATTRIBUTES_TOPIC_FILTER = toFilter(MqttTopics.DEVICE_ATTRIBUTES_TOPIC);
  30 +
  31 + public static TcpTopicFilter toFilter(String topicFilter) {
  32 + if (topicFilter == null || topicFilter.isEmpty()) {
  33 + throw new IllegalArgumentException("Topic filter can't be empty!");
  34 + }
  35 + return filters.computeIfAbsent(topicFilter, filter -> {
  36 + if (filter.equals("#")) {
  37 + return new AlwaysTrueTopicFilter();
  38 + } else if (filter.contains("+") || filter.contains("#")) {
  39 + String regex = filter
  40 + .replace("\\", "\\\\")
  41 + .replace("+", "[^/]+")
  42 + .replace("/#", "($|/.*)");
  43 + log.debug("Converting [{}] to [{}]", filter, regex);
  44 + return new RegexTopicFilter(regex);
  45 + } else {
  46 + return new EqualsTopicFilter(filter);
  47 + }
  48 + });
  49 + }
  50 +
  51 + public static TcpTopicFilter getDefaultTelemetryFilter() {
  52 + return DEFAULT_TELEMETRY_TOPIC_FILTER;
  53 + }
  54 +
  55 + public static TcpTopicFilter getDefaultAttributesFilter() {
  56 + return DEFAULT_ATTRIBUTES_TOPIC_FILTER;
  57 + }
  58 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.util;
  17 +
  18 +import lombok.Data;
  19 +
  20 +import java.util.regex.Pattern;
  21 +
  22 +@Data
  23 +public class RegexTopicFilter implements TcpTopicFilter {
  24 +
  25 + private final Pattern regex;
  26 +
  27 + public RegexTopicFilter(String regex) {
  28 + this.regex = Pattern.compile(regex);
  29 + }
  30 +
  31 + @Override
  32 + public boolean filter(String topic) {
  33 + return regex.matcher(topic).matches();
  34 + }
  35 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp.util;
  17 +
  18 +public interface TcpTopicFilter {
  19 +
  20 + boolean filter(String topic);
  21 +
  22 +}
... ...
... ... @@ -939,6 +939,11 @@
939 939 </dependency>
940 940 <dependency>
941 941 <groupId>org.thingsboard.common.transport</groupId>
  942 + <artifactId>tcp</artifactId>
  943 + <version>${project.version}</version>
  944 + </dependency>
  945 + <dependency>
  946 + <groupId>org.thingsboard.common.transport</groupId>
942 947 <artifactId>http</artifactId>
943 948 <version>${project.version}</version>
944 949 </dependency>
... ...
... ... @@ -41,7 +41,7 @@
41 41 <pkg.copyInstallScripts>false</pkg.copyInstallScripts>
42 42 <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
43 43 <pkg.implementationTitle>ThingsBoard MQTT Transport Service</pkg.implementationTitle>
44   - <pkg.mainClass>org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication</pkg.mainClass>
  44 + <pkg.mainClass>org.thingsboard.server.tcp.ThingsboardMqttTransportApplication</pkg.mainClass>
45 45 </properties>
46 46
47 47 <dependencies>
... ...
... ... @@ -36,6 +36,7 @@
36 36 <modules>
37 37 <module>http</module>
38 38 <module>mqtt</module>
  39 + <module>tcp</module>
39 40 <module>coap</module>
40 41 <module>lwm2m</module>
41 42 <module>snmp</module>
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2022 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  20 + <modelVersion>4.0.0</modelVersion>
  21 + <parent>
  22 + <groupId>org.thingsboard</groupId>
  23 + <version>3.3.4-SNAPSHOT</version>
  24 + <artifactId>transport</artifactId>
  25 + </parent>
  26 + <groupId>org.thingsboard.transport</groupId>
  27 + <artifactId>tcp</artifactId>
  28 + <packaging>jar</packaging>
  29 +
  30 + <name>Thingsboard TCP Transport Service</name>
  31 + <url>https://thingsboard.io</url>
  32 +
  33 + <properties>
  34 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  35 + <main.dir>${basedir}/../..</main.dir>
  36 + <pkg.type>java</pkg.type>
  37 + <pkg.disabled>false</pkg.disabled>
  38 + <pkg.process-resources.phase>process-resources</pkg.process-resources.phase>
  39 + <pkg.package.phase>package</pkg.package.phase>
  40 + <pkg.name>tb-tcp-transport</pkg.name>
  41 + <pkg.copyInstallScripts>false</pkg.copyInstallScripts>
  42 + <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
  43 + <pkg.implementationTitle>ThingsBoard TCP Transport Service</pkg.implementationTitle>
  44 + <pkg.mainClass>org.thingsboard.server.tcp.ThingsboardTcpTransportApplication</pkg.mainClass>
  45 + </properties>
  46 +
  47 + <dependencies>
  48 + <dependency>
  49 + <groupId>org.thingsboard.common.transport</groupId>
  50 + <artifactId>tcp</artifactId>
  51 + </dependency>
  52 + <dependency>
  53 + <groupId>org.thingsboard.common</groupId>
  54 + <artifactId>queue</artifactId>
  55 + </dependency>
  56 + <dependency>
  57 + <groupId>org.springframework.boot</groupId>
  58 + <artifactId>spring-boot-starter-web</artifactId>
  59 + </dependency>
  60 + <dependency>
  61 + <groupId>com.sun.winsw</groupId>
  62 + <artifactId>winsw</artifactId>
  63 + <classifier>bin</classifier>
  64 + <type>exe</type>
  65 + <scope>provided</scope>
  66 + </dependency>
  67 + <dependency>
  68 + <groupId>org.springframework.boot</groupId>
  69 + <artifactId>spring-boot-starter-test</artifactId>
  70 + <scope>test</scope>
  71 + </dependency>
  72 + <dependency>
  73 + <groupId>org.junit.vintage</groupId>
  74 + <artifactId>junit-vintage-engine</artifactId>
  75 + <scope>test</scope>
  76 + </dependency>
  77 + <dependency>
  78 + <groupId>org.awaitility</groupId>
  79 + <artifactId>awaitility</artifactId>
  80 + <scope>test</scope>
  81 + </dependency>
  82 + </dependencies>
  83 +
  84 + <build>
  85 + <finalName>${pkg.name}-${project.version}</finalName>
  86 + <resources>
  87 + <resource>
  88 + <directory>${project.basedir}/src/main/resources</directory>
  89 + </resource>
  90 + </resources>
  91 + <plugins>
  92 + <plugin>
  93 + <groupId>org.apache.maven.plugins</groupId>
  94 + <artifactId>maven-resources-plugin</artifactId>
  95 + </plugin>
  96 + <plugin>
  97 + <groupId>org.apache.maven.plugins</groupId>
  98 + <artifactId>maven-dependency-plugin</artifactId>
  99 + </plugin>
  100 + <plugin>
  101 + <groupId>org.apache.maven.plugins</groupId>
  102 + <artifactId>maven-jar-plugin</artifactId>
  103 + </plugin>
  104 + <plugin>
  105 + <groupId>org.springframework.boot</groupId>
  106 + <artifactId>spring-boot-maven-plugin</artifactId>
  107 + </plugin>
  108 + <plugin>
  109 + <groupId>org.thingsboard</groupId>
  110 + <artifactId>gradle-maven-plugin</artifactId>
  111 + </plugin>
  112 + <plugin>
  113 + <groupId>org.apache.maven.plugins</groupId>
  114 + <artifactId>maven-assembly-plugin</artifactId>
  115 + </plugin>
  116 + <plugin>
  117 + <groupId>org.apache.maven.plugins</groupId>
  118 + <artifactId>maven-install-plugin</artifactId>
  119 + </plugin>
  120 + </plugins>
  121 + </build>
  122 + <repositories>
  123 + <repository>
  124 + <id>jenkins</id>
  125 + <name>Jenkins Repository</name>
  126 + <url>https://repo.jenkins-ci.org/releases</url>
  127 + <snapshots>
  128 + <enabled>false</enabled>
  129 + </snapshots>
  130 + </repository>
  131 + </repositories>
  132 +</project>
... ...
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2022 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 +<!DOCTYPE configuration>
  20 +<configuration>
  21 +
  22 + <appender name="fileLogAppender"
  23 + class="ch.qos.logback.core.rolling.RollingFileAppender">
  24 + <file>${pkg.logFolder}/${pkg.name}.log</file>
  25 + <rollingPolicy
  26 + class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
  27 + <fileNamePattern>${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
  28 + <maxFileSize>100MB</maxFileSize>
  29 + <maxHistory>30</maxHistory>
  30 + <totalSizeCap>3GB</totalSizeCap>
  31 + </rollingPolicy>
  32 + <encoder>
  33 + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
  34 + </encoder>
  35 + </appender>
  36 +
  37 + <logger name="org.thingsboard.server" level="INFO" />
  38 +
  39 + <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
  40 +
  41 + <root level="INFO">
  42 + <appender-ref ref="fileLogAppender"/>
  43 + </root>
  44 +
  45 +</configuration>
... ...
  1 +#
  2 +# Copyright © 2016-2022 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 +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  18 +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError"
  19 +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  20 +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
  21 +export LOG_FILENAME=${pkg.name}.out
  22 +export LOADER_PATH=${pkg.installFolder}/conf
... ...
  1 +/**
  2 + * Copyright © 2016-2022 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.tcp;
  17 +
  18 +import org.springframework.boot.SpringApplication;
  19 +import org.springframework.boot.SpringBootConfiguration;
  20 +import org.springframework.context.annotation.ComponentScan;
  21 +import org.springframework.scheduling.annotation.EnableAsync;
  22 +import org.springframework.scheduling.annotation.EnableScheduling;
  23 +
  24 +import java.util.Arrays;
  25 +
  26 +@SpringBootConfiguration
  27 +@EnableAsync
  28 +@EnableScheduling
  29 +@ComponentScan({"org.thingsboard.server.tcp", "org.thingsboard.server.common", "org.thingsboard.server.transport.tcp", "org.thingsboard.server.queue", "org.thingsboard.server.cache"})
  30 +public class ThingsboardTcpTransportApplication {
  31 +
  32 + private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
  33 + private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-tcp-transport";
  34 +
  35 + public static void main(String[] args) {
  36 + SpringApplication.run(ThingsboardTcpTransportApplication.class, updateArguments(args));
  37 + }
  38 +
  39 + private static String[] updateArguments(String[] args) {
  40 + if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) {
  41 + String[] modifiedArgs = new String[args.length + 1];
  42 + System.arraycopy(args, 0, modifiedArgs, 0, args.length);
  43 + modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM;
  44 + return modifiedArgs;
  45 + }
  46 + return args;
  47 + }
  48 +}
... ...
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2022 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 +<!DOCTYPE configuration>
  20 +<configuration scan="true" scanPeriod="10 seconds">
  21 +
  22 + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  23 + <encoder>
  24 + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
  25 + </encoder>
  26 + </appender>
  27 +
  28 + <logger name="org.thingsboard.server" level="TRACE" />
  29 +
  30 + <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
  31 +
  32 + <root level="INFO">
  33 + <appender-ref ref="STDOUT"/>
  34 + </root>
  35 +
  36 +</configuration>
\ No newline at end of file
... ...
  1 +#
  2 +# Copyright © 2016-2022 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 +# If you enabled process metrics you should also enable 'web-environment'.
  18 +spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
  19 +# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
  20 +spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
  21 +
  22 +server:
  23 + # Server bind address (has no effect if web-environment is disabled).
  24 + address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
  25 + # Server bind port (has no effect if web-environment is disabled).
  26 + port: "${HTTP_BIND_PORT:8083}"
  27 +
  28 +# Zookeeper connection parameters. Used for service discovery.
  29 +zk:
  30 + # Enable/disable zookeeper discovery service.
  31 + enabled: "${ZOOKEEPER_ENABLED:false}"
  32 + # Zookeeper connect string
  33 + url: "${ZOOKEEPER_URL:localhost:2181}"
  34 + # Zookeeper retry interval in milliseconds
  35 + retry_interval_ms: "${ZOOKEEPER_RETRY_INTERVAL_MS:3000}"
  36 + # Zookeeper connection timeout in milliseconds
  37 + connection_timeout_ms: "${ZOOKEEPER_CONNECTION_TIMEOUT_MS:3000}"
  38 + # Zookeeper session timeout in milliseconds
  39 + session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}"
  40 + # Name of the directory in zookeeper 'filesystem'
  41 + zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}"
  42 +
  43 +cache:
  44 + type: "${CACHE_TYPE:redis}"
  45 +
  46 +redis:
  47 + # standalone or cluster
  48 + connection:
  49 + type: "${REDIS_CONNECTION_TYPE:standalone}"
  50 + standalone:
  51 + host: "${REDIS_HOST:localhost}"
  52 + port: "${REDIS_PORT:6379}"
  53 + useDefaultClientConfig: "${REDIS_USE_DEFAULT_CLIENT_CONFIG:true}"
  54 + # this value may be used only if you used not default ClientConfig
  55 + clientName: "${REDIS_CLIENT_NAME:standalone}"
  56 + # this value may be used only if you used not default ClientConfig
  57 + connectTimeout: "${REDIS_CLIENT_CONNECT_TIMEOUT:30000}"
  58 + # this value may be used only if you used not default ClientConfig
  59 + readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}"
  60 + # this value may be used only if you used not default ClientConfig
  61 + usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:false}"
  62 + cluster:
  63 + # Comma-separated list of "host:port" pairs to bootstrap from.
  64 + nodes: "${REDIS_NODES:}"
  65 + # Maximum number of redirects to follow when executing commands across the cluster.
  66 + max-redirects: "${REDIS_MAX_REDIRECTS:12}"
  67 + useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:true}"
  68 + # db index
  69 + db: "${REDIS_DB:0}"
  70 + # db password
  71 + password: "${REDIS_PASSWORD:}"
  72 + # pool config
  73 + pool_config:
  74 + maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}"
  75 + maxIdle: "${REDIS_POOL_CONFIG_MAX_IDLE:128}"
  76 + minIdle: "${REDIS_POOL_CONFIG_MIN_IDLE:16}"
  77 + testOnBorrow: "${REDIS_POOL_CONFIG_TEST_ON_BORROW:true}"
  78 + testOnReturn: "${REDIS_POOL_CONFIG_TEST_ON_RETURN:true}"
  79 + testWhileIdle: "${REDIS_POOL_CONFIG_TEST_WHILE_IDLE:true}"
  80 + minEvictableMs: "${REDIS_POOL_CONFIG_MIN_EVICTABLE_MS:60000}"
  81 + evictionRunsMs: "${REDIS_POOL_CONFIG_EVICTION_RUNS_MS:30000}"
  82 + maxWaitMills: "${REDIS_POOL_CONFIG_MAX_WAIT_MS:60000}"
  83 + numberTestsPerEvictionRun: "${REDIS_POOL_CONFIG_NUMBER_TESTS_PER_EVICTION_RUN:3}"
  84 + blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}"
  85 +
  86 +# TCP_ server parameters
  87 +transport:
  88 + tcp:
  89 + bind_address: "${TCP_BIND_ADDRESS:0.0.0.0}"
  90 + bind_port: "${TCP_BIND_PORT:8088}"
  91 + # Enable proxy protocol support. Disabled by default. If enabled, supports both v1 and v2.
  92 + # Useful to get the real IP address of the client in the logs and for rate limits.
  93 + proxy_enabled: "${TCP_PROXY_PROTOCOL_ENABLED:false}"
  94 + timeout: "${TCP_TIMEOUT:10000}"
  95 + msg_queue_size_per_device_limit: "${TCP_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism
  96 + netty:
  97 + leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}"
  98 + boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
  99 + worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
  100 + max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
  101 + so_keep_alive: "${NETTY_SO_KEEPALIVE:false}"
  102 + # TCP_ SSL configuration
  103 + ssl:
  104 + # Enable/disable SSL support
  105 + enabled: "${TCP_SSL_ENABLED:false}"
  106 + # TCP_ SSL bind address
  107 + bind_address: "${TCP_SSL_BIND_ADDRESS:0.0.0.0}"
  108 + # TCP_ SSL bind port
  109 + bind_port: "${TCP_SSL_BIND_PORT:8883}"
  110 + # SSL protocol: See https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms
  111 + protocol: "${TCP_SSL_PROTOCOL:TLSv1.2}"
  112 + # Server SSL credentials
  113 + credentials:
  114 + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
  115 + type: "${TCP_SSL_CREDENTIALS_TYPE:PEM}"
  116 + # PEM server credentials
  117 + pem:
  118 + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key)
  119 + cert_file: "${TCP_SSL_PEM_CERT:tcpserver.pem}"
  120 + # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file;
  121 + key_file: "${TCP_SSL_PEM_KEY:tcpserver_key.pem}"
  122 + # Server certificate private key password (optional)
  123 + key_password: "${TCP_SSL_PEM_KEY_PASSWORD:server_key_password}"
  124 + # Keystore server credentials
  125 + keystore:
  126 + # Type of the key store
  127 + type: "${TCP_SSL_KEY_STORE_TYPE:JKS}"
  128 + # Path to the key store that holds the SSL certificate
  129 + store_file: "${TCP_SSL_KEY_STORE:tcpserver.jks}"
  130 + # Password used to access the key store
  131 + store_password: "${TCP_SSL_KEY_STORE_PASSWORD:server_ks_password}"
  132 + # Optional alias of the private key; If not set, the platform will load the first private key from the keystore;
  133 + key_alias: "${TCP_SSL_KEY_ALIAS:}"
  134 + # Password used to access the key
  135 + key_password: "${TCP_SSL_KEY_PASSWORD:server_key_password}"
  136 + # Skip certificate validity check for client certificates.
  137 + skip_validity_check_for_client_cert: "${TCP_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
  138 + sessions:
  139 + inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
  140 + report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
  141 + json:
  142 + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON
  143 + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
  144 + # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
  145 + max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
  146 + log:
  147 + enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"
  148 + max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}"
  149 + stats:
  150 + enabled: "${TB_TRANSPORT_STATS_ENABLED:true}"
  151 + print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}"
  152 + client_side_rpc:
  153 + timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}"
  154 + rate_limits:
  155 + # Enable or disable generic rate limits. Device and Tenant specific rate limits are controlled in Tenant Profile.
  156 + ip_limits_enabled: "${TB_TRANSPORT_IP_RATE_LIMITS_ENABLED:false}"
  157 + # Maximum number of connect attempts with invalid credentials
  158 + max_wrong_credentials_per_ip: "${TB_TRANSPORT_MAX_WRONG_CREDENTIALS_PER_IP:10}"
  159 + # Timeout to expire block IP addresses
  160 + ip_block_timeout: "${TB_TRANSPORT_IP_BLOCK_TIMEOUT:60000}"
  161 +
  162 +
  163 +queue:
  164 + type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
  165 + kafka:
  166 + bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
  167 + acks: "${TB_KAFKA_ACKS:all}"
  168 + retries: "${TB_KAFKA_RETRIES:1}"
  169 + batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
  170 + linger.ms: "${TB_KAFKA_LINGER_MS:1}"
  171 + max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}"
  172 + max.in.flight.requests.per.connection: "${TB_KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}"
  173 + buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
  174 + replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}"
  175 + use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}"
  176 + confluent:
  177 + ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}"
  178 + sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}"
  179 + sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}"
  180 + security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
  181 + other: # In this section you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside
  182 + - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms
  183 + value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds)
  184 + - key: "session.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms
  185 + value: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds)
  186 + topic-properties:
  187 + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
  188 + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
  189 + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
  190 + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
  191 + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}"
  192 + aws_sqs:
  193 + use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}"
  194 + access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}"
  195 + secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}"
  196 + region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}"
  197 + threads_per_topic: "${TB_QUEUE_AWS_SQS_THREADS_PER_TOPIC:1}"
  198 + queue-properties:
  199 + rule-engine: "${TB_QUEUE_AWS_SQS_RE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
  200 + core: "${TB_QUEUE_AWS_SQS_CORE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
  201 + transport-api: "${TB_QUEUE_AWS_SQS_TA_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
  202 + notifications: "${TB_QUEUE_AWS_SQS_NOTIFICATIONS_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
  203 + js-executor: "${TB_QUEUE_AWS_SQS_JE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
  204 + # VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
  205 + pubsub:
  206 + project_id: "${TB_QUEUE_PUBSUB_PROJECT_ID:YOUR_PROJECT_ID}"
  207 + service_account: "${TB_QUEUE_PUBSUB_SERVICE_ACCOUNT:YOUR_SERVICE_ACCOUNT}"
  208 + max_msg_size: "${TB_QUEUE_PUBSUB_MAX_MSG_SIZE:1048576}" #in bytes
  209 + max_messages: "${TB_QUEUE_PUBSUB_MAX_MESSAGES:1000}"
  210 + queue-properties:
  211 + rule-engine: "${TB_QUEUE_PUBSUB_RE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
  212 + core: "${TB_QUEUE_PUBSUB_CORE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
  213 + transport-api: "${TB_QUEUE_PUBSUB_TA_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
  214 + notifications: "${TB_QUEUE_PUBSUB_NOTIFICATIONS_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
  215 + js-executor: "${TB_QUEUE_PUBSUB_JE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
  216 + service_bus:
  217 + namespace_name: "${TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME:YOUR_NAMESPACE_NAME}"
  218 + sas_key_name: "${TB_QUEUE_SERVICE_BUS_SAS_KEY_NAME:YOUR_SAS_KEY_NAME}"
  219 + sas_key: "${TB_QUEUE_SERVICE_BUS_SAS_KEY:YOUR_SAS_KEY}"
  220 + max_messages: "${TB_QUEUE_SERVICE_BUS_MAX_MESSAGES:1000}"
  221 + queue-properties:
  222 + rule-engine: "${TB_QUEUE_SERVICE_BUS_RE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
  223 + core: "${TB_QUEUE_SERVICE_BUS_CORE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
  224 + transport-api: "${TB_QUEUE_SERVICE_BUS_TA_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
  225 + notifications: "${TB_QUEUE_SERVICE_BUS_NOTIFICATIONS_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
  226 + js-executor: "${TB_QUEUE_SERVICE_BUS_JE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
  227 + rabbitmq:
  228 + exchange_name: "${TB_QUEUE_RABBIT_MQ_EXCHANGE_NAME:}"
  229 + host: "${TB_QUEUE_RABBIT_MQ_HOST:localhost}"
  230 + port: "${TB_QUEUE_RABBIT_MQ_PORT:5672}"
  231 + virtual_host: "${TB_QUEUE_RABBIT_MQ_VIRTUAL_HOST:/}"
  232 + username: "${TB_QUEUE_RABBIT_MQ_USERNAME:YOUR_USERNAME}"
  233 + password: "${TB_QUEUE_RABBIT_MQ_PASSWORD:YOUR_PASSWORD}"
  234 + automatic_recovery_enabled: "${TB_QUEUE_RABBIT_MQ_AUTOMATIC_RECOVERY_ENABLED:false}"
  235 + connection_timeout: "${TB_QUEUE_RABBIT_MQ_CONNECTION_TIMEOUT:60000}"
  236 + handshake_timeout: "${TB_QUEUE_RABBIT_MQ_HANDSHAKE_TIMEOUT:10000}"
  237 + queue-properties:
  238 + rule-engine: "${TB_QUEUE_RABBIT_MQ_RE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
  239 + core: "${TB_QUEUE_RABBIT_MQ_CORE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
  240 + transport-api: "${TB_QUEUE_RABBIT_MQ_TA_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
  241 + notifications: "${TB_QUEUE_RABBIT_MQ_NOTIFICATIONS_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
  242 + js-executor: "${TB_QUEUE_RABBIT_MQ_JE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
  243 + partitions:
  244 + hash_function_name: "${TB_QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}"
  245 + virtual_nodes_size: "${TB_QUEUE_PARTITIONS_VIRTUAL_NODES_SIZE:16}"
  246 + transport_api:
  247 + requests_topic: "${TB_QUEUE_TRANSPORT_API_REQUEST_TOPIC:tb_transport.api.requests}"
  248 + responses_topic: "${TB_QUEUE_TRANSPORT_API_RESPONSE_TOPIC:tb_transport.api.responses}"
  249 + max_pending_requests: "${TB_QUEUE_TRANSPORT_MAX_PENDING_REQUESTS:10000}"
  250 + max_requests_timeout: "${TB_QUEUE_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}"
  251 + max_callback_threads: "${TB_QUEUE_TRANSPORT_MAX_CALLBACK_THREADS:100}"
  252 + request_poll_interval: "${TB_QUEUE_TRANSPORT_REQUEST_POLL_INTERVAL_MS:25}"
  253 + response_poll_interval: "${TB_QUEUE_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}"
  254 + core:
  255 + topic: "${TB_QUEUE_CORE_TOPIC:tb_core}"
  256 + poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}"
  257 + partitions: "${TB_QUEUE_CORE_PARTITIONS:10}"
  258 + pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}"
  259 + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}"
  260 + stats:
  261 + enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}"
  262 + print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}"
  263 + js:
  264 + # JS Eval request topic
  265 + request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js_eval.requests}"
  266 + # JS Eval responses topic prefix that is combined with node id
  267 + response_topic_prefix: "${REMOTE_JS_EVAL_RESPONSE_TOPIC:js_eval.responses}"
  268 + # JS Eval max pending requests
  269 + max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}"
  270 + # JS Eval max request timeout
  271 + max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
  272 + # JS response poll interval
  273 + response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
  274 + rule-engine:
  275 + topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
  276 + poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"
  277 + pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}"
  278 + stats:
  279 + enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}"
  280 + print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}"
  281 + queues:
  282 + - name: "${TB_QUEUE_RE_MAIN_QUEUE_NAME:Main}"
  283 + topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}"
  284 + poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}"
  285 + partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}"
  286 + pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}"
  287 + submit-strategy:
  288 + type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
  289 + # For BATCH only
  290 + batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch
  291 + processing-strategy:
  292 + type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:SKIP_ALL_FAILURES}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  293 + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  294 + retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
  295 + failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
  296 + pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}"# Time in seconds to wait in consumer thread before retries;
  297 + - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
  298 + topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
  299 + poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
  300 + partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}"
  301 + pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}"
  302 + submit-strategy:
  303 + type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
  304 + # For BATCH only
  305 + batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
  306 + processing-strategy:
  307 + type: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  308 + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  309 + retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
  310 + failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
  311 + pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  312 + - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
  313 + topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
  314 + poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
  315 + partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}"
  316 + pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:60000}"
  317 + submit-strategy:
  318 + type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
  319 + # For BATCH only
  320 + batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
  321 + processing-strategy:
  322 + type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  323 + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  324 + retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
  325 + failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
  326 + pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  327 + transport:
  328 + # For high priority notifications that require minimum latency and processing time
  329 + notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
  330 + poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
  331 +
  332 +service:
  333 + type: "${TB_SERVICE_TYPE:tb-transport}"
  334 + # Unique id for this service (autogenerated if empty)
  335 + id: "${TB_SERVICE_ID:}"
  336 + tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id.
  337 +
  338 +metrics:
  339 + # Enable/disable actuator metrics.
  340 + enabled: "${METRICS_ENABLED:false}"
  341 +
  342 +management:
  343 + endpoints:
  344 + web:
  345 + exposure:
  346 + # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
  347 + include: '${METRICS_ENDPOINTS_EXPOSE:info}'
... ...