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 | ... | ... |
common/transport/tcp/pom.xml
0 → 100644
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> | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpSslHandlerProvider.java
0 → 100644
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 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpTransportContext.java
0 → 100644
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 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpTransportHandler.java
0 → 100644
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 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpTransportService.java
0 → 100644
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 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/adaptors/JsonTcpAdaptor.java
0 → 100644
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 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/limits/IpFilter.java
0 → 100644
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 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/limits/ProxyIpFilter.java
0 → 100644
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 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/EqualsTopicFilter.java
0 → 100644
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 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/RegexTopicFilter.java
0 → 100644
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 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/TcpTopicFilter.java
0 → 100644
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> | ... | ... |
transport/tcp/pom.xml
0 → 100644
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> | ... | ... |
transport/tcp/src/main/conf/logback.xml
0 → 100644
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 | ... | ... |
transport/tcp/src/main/java/org/thingsboard/server/tcp/ThingsboardTcpTransportApplication.java
0 → 100644
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 | +} | ... | ... |
transport/tcp/src/main/resources/logback.xml
0 → 100644
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}' | ... | ... |