Commit 9d89fc70564a58cc0ee9ffc4a7f928da6437aaa7

Authored by xp.Huang
2 parents fdb96a88 9b027765

Merge branch '20221019' into 'master'

refactor: 脚本引擎模板调整

See merge request huang/thingsboard3.3.2!139
Showing 75 changed files with 5949 additions and 126 deletions
... ... @@ -83,6 +83,10 @@
83 83 </dependency>
84 84 <dependency>
85 85 <groupId>org.thingsboard.common.transport</groupId>
  86 + <artifactId>tcp</artifactId>
  87 + </dependency>
  88 + <dependency>
  89 + <groupId>org.thingsboard.common.transport</groupId>
86 90 <artifactId>http</artifactId>
87 91 </dependency>
88 92 <dependency>
... ...
... ... @@ -28,30 +28,13 @@ import org.springframework.security.core.Authentication;
28 28 import org.springframework.security.core.context.SecurityContextHolder;
29 29 import org.springframework.web.bind.annotation.ExceptionHandler;
30 30 import org.thingsboard.server.cluster.TbClusterService;
31   -import org.thingsboard.server.common.data.Customer;
32   -import org.thingsboard.server.common.data.Dashboard;
33   -import org.thingsboard.server.common.data.DashboardInfo;
34   -import org.thingsboard.server.common.data.Device;
35   -import org.thingsboard.server.common.data.DeviceInfo;
36   -import org.thingsboard.server.common.data.DeviceProfile;
37   -import org.thingsboard.server.common.data.EntityType;
38   -import org.thingsboard.server.common.data.EntityView;
39   -import org.thingsboard.server.common.data.EntityViewInfo;
40   -import org.thingsboard.server.common.data.HasName;
41   -import org.thingsboard.server.common.data.HasTenantId;
42   -import org.thingsboard.server.common.data.OtaPackage;
43   -import org.thingsboard.server.common.data.OtaPackageInfo;
44   -import org.thingsboard.server.common.data.TbResource;
45   -import org.thingsboard.server.common.data.TbResourceInfo;
46   -import org.thingsboard.server.common.data.Tenant;
47   -import org.thingsboard.server.common.data.TenantInfo;
48   -import org.thingsboard.server.common.data.TenantProfile;
49   -import org.thingsboard.server.common.data.User;
  31 +import org.thingsboard.server.common.data.*;
50 32 import org.thingsboard.server.common.data.alarm.Alarm;
51 33 import org.thingsboard.server.common.data.alarm.AlarmInfo;
52 34 import org.thingsboard.server.common.data.asset.Asset;
53 35 import org.thingsboard.server.common.data.asset.AssetInfo;
54 36 import org.thingsboard.server.common.data.audit.ActionType;
  37 +import org.thingsboard.server.common.data.device.profile.*;
55 38 import org.thingsboard.server.common.data.edge.Edge;
56 39 import org.thingsboard.server.common.data.edge.EdgeEventActionType;
57 40 import org.thingsboard.server.common.data.edge.EdgeEventType;
... ... @@ -919,4 +902,29 @@ public abstract class BaseController {
919 902 return MediaType.APPLICATION_OCTET_STREAM;
920 903 }
921 904 }
  905 +
  906 + /**
  907 + * 构建设备配置的配置数据
  908 + * @param transportType 产品的通信协议
  909 + * @param deviceProfileData 空的设备配置数据
  910 + * @param transportConfiguration 传输配置
  911 + * @param scriptText 自定义数据协议的解析脚本
  912 + */
  913 + protected void buildDeviceProfileData(String transportType,DeviceProfileData deviceProfileData,DeviceProfileTransportConfiguration transportConfiguration,String scriptText) {
  914 + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration());
  915 + deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null));
  916 +
  917 + // 传输类型默认都是Default
  918 + if(transportType ==null || DeviceTransportType.DEFAULT.name().equals(transportType)){
  919 + deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration());
  920 + }else if(DeviceTransportType.TCP.name().equals(transportType)){
  921 + YtTcpDeviceProfileTransportConfiguration tcpDeviceProfileTransportConfiguration = (YtTcpDeviceProfileTransportConfiguration) transportConfiguration;
  922 + String scriptId = tcpDeviceProfileTransportConfiguration.getScriptId();
  923 + tcpDeviceProfileTransportConfiguration.setPingText(scriptText);
  924 + deviceProfileData.setTransportConfiguration(tcpDeviceProfileTransportConfiguration);
  925 + }else{
  926 + deviceProfileData.setTransportConfiguration(transportConfiguration);
  927 + }
  928 +
  929 + }
922 930 }
... ...
... ... @@ -59,6 +59,7 @@ public class YtAdminController extends BaseController {
59 59 private final UserService tbUserService;
60 60
61 61 @PostMapping("/tenant")
  62 + @Deprecated
62 63 @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{'api:yt:admin:tenant:post'})")
63 64 public ResponseEntity<TenantDTO> saveTenant(@RequestBody TenantReqDTO tenantReqDTO) {
64 65 TenantDTO newTenant = ytTenantService.createNewTenant(tenantReqDTO);
... ...
... ... @@ -13,10 +13,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType;
13 13 import org.thingsboard.server.common.data.DeviceProfileType;
14 14 import org.thingsboard.server.common.data.DeviceTransportType;
15 15 import org.thingsboard.server.common.data.audit.ActionType;
16   -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
17   -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
18   -import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
19   -import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
  16 +import org.thingsboard.server.common.data.device.profile.*;
20 17 import org.thingsboard.server.common.data.edge.EdgeEventActionType;
21 18 import org.thingsboard.server.common.data.exception.ThingsboardException;
22 19 import org.thingsboard.server.common.data.id.DeviceProfileId;
... ... @@ -24,6 +21,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
24 21 import org.thingsboard.server.common.data.id.TenantId;
25 22 import org.thingsboard.server.common.data.page.PageLink;
26 23 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  24 +import org.thingsboard.server.common.data.rule.RuleChain;
27 25 import org.thingsboard.server.common.data.yunteng.common.DeleteGroup;
28 26 import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
29 27 import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException;
... ... @@ -34,6 +32,7 @@ import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;
34 32 import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData;
35 33 import org.thingsboard.server.common.msg.queue.ServiceQueue;
36 34 import org.thingsboard.server.controller.BaseController;
  35 +import org.thingsboard.server.dao.yunteng.service.YtDeviceScriptService;
37 36 import org.thingsboard.server.dao.yunteng.service.YtDeviceProfileService;
38 37 import org.thingsboard.server.service.security.permission.Operation;
39 38
... ... @@ -51,7 +50,7 @@ import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.
51 50 @Api(tags = {"设备配置管理"})
52 51 public class YtDeviceProfileController extends BaseController {
53 52 private final YtDeviceProfileService ytDeviceProfileService;
54   -
  53 + private final YtDeviceScriptService javaScriptService;
55 54 @PostMapping()
56 55 @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:post','api:yt:deviceProfile:update'})")
57 56 @ApiOperation("创建 | 编辑")
... ... @@ -66,11 +65,14 @@ public class YtDeviceProfileController extends BaseController {
66 65 * 2/3.处理TB业务逻辑
67 66 * 3/3.处理业务平台的业务逻辑
68 67 */
69   -
70   - deviceProfileDTO.setTenantId(getCurrentUser().getCurrentTenantId());
  68 + String tenantId = getCurrentUser().getCurrentTenantId();
  69 + deviceProfileDTO.setTenantId(tenantId);
71 70 DeviceProfile tbDeviceProfile = buildTbDeviceProfileFromDeviceProfileDTO(deviceProfileDTO);
  71 +
72 72 updateTbDeviceProfile(tbDeviceProfile, created);
73 73
  74 + ytDeviceProfileService.insertOrUpdate(deviceProfileDTO);
  75 +
74 76 return ResponseEntity.ok(deviceProfileDTO);
75 77 }
76 78
... ... @@ -130,9 +132,7 @@ public class YtDeviceProfileController extends BaseController {
130 132 @RequestParam(value = "transportType", required = false) String transportType,
131 133 @RequestParam(value = ORDER_FILED, required = false) String orderBy,
132 134 @RequestParam(value = ORDER_TYPE, required = false) OrderTypeEnum orderType) throws ThingsboardException {
133   -
134   - PageLink pageLink = createPageLink(pageSize, page > 1 ? page - 1 : 0, name, orderBy==null ? "createdTime":orderBy, orderType == null ? OrderTypeEnum.DESC.name(): orderType.name());
135   - return ytDeviceProfileService.page(pageLink, getCurrentUser().getCurrentTenantId(), transportType);
  135 + return ytDeviceProfileService.page(page,pageSize,orderBy,orderType, getCurrentUser().getCurrentTenantId(),name, transportType);
136 136 }
137 137
138 138 @GetMapping("/me/list")
... ... @@ -146,11 +146,13 @@ public class YtDeviceProfileController extends BaseController {
146 146 @ApiOperation("删除")
147 147 @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:delete'})")
148 148 public void deleteDevices(@Validated({DeleteGroup.class}) @RequestBody DeleteDTO deleteDTO) throws ThingsboardException {
149   - ytDeviceProfileService.checkDeviceProfiles(getCurrentUser().getCurrentTenantId(), deleteDTO.getIds());
  149 + String tenantId = getCurrentUser().getCurrentTenantId();
  150 + ytDeviceProfileService.checkDeviceProfiles(tenantId, deleteDTO.getIds());
150 151
151 152 for (String id : deleteDTO.getIds()) {
152 153 deleteTbDeviceProfile(id);
153 154 }
  155 + ytDeviceProfileService.deleteDeviceProfiles(tenantId,deleteDTO.getIds());
154 156
155 157 }
156 158
... ... @@ -217,33 +219,50 @@ public class YtDeviceProfileController extends BaseController {
217 219 tbDeviceProfile.setTenantId(TenantId.fromUUID(tenantId));
218 220 tbDeviceProfile.setDefault(deviceProfileDTO.isDefault());
219 221
  222 +
  223 + String chainStr = deviceProfileDTO.getDefaultRuleChainId();
  224 + if(StringUtils.isBlank(chainStr)){
  225 + throw new YtDataValidationException(ErrorMessage.RULE_CHAIN_NOT_ENABLE.getMessage());
  226 + }
  227 + UUID chainId = UUID.fromString(chainStr);
  228 + RuleChain chain = ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID,new RuleChainId(chainId));
  229 + if(chain==null || !deviceProfileDTO.getTenantId().equals(chain.getTenantId().getId().toString())){
  230 + throw new YtDataValidationException(ErrorMessage.RULE_CHAIN_NOT_ENABLE.getMessage());
  231 + }
  232 +
220 233 // 获取当前租户的默认规则链
221 234 if (StringUtils.isNotBlank(deviceProfileDTO.getDefaultRuleChainId())) {
222   - UUID chainId = UUID.fromString(deviceProfileDTO.getDefaultRuleChainId());
223 235 tbDeviceProfile.setDefaultRuleChainId(new RuleChainId(chainId));
224 236 }
225 237
226 238 tbDeviceProfile.setDefaultQueueName(ServiceQueue.MAIN);
227 239 tbDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
228 240
229   - DeviceProfileData deviceProfileData = new DeviceProfileData();
230   - deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration());
231   - deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null));
232 241
233 242 // 传输类型默认都是Default
234 243 String transportType = deviceProfileDTO.getTransportType();
  244 + String scriptText=null;
235 245 if(transportType ==null || DeviceTransportType.DEFAULT.name().equals(transportType)){
236 246 tbDeviceProfile.setTransportType(DeviceTransportType.DEFAULT);
237   - deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration());
238 247 }else{
239 248 tbDeviceProfile.setTransportType(DeviceTransportType.valueOf(transportType));
240   - deviceProfileData.setTransportConfiguration(deviceProfileDTO.getProfileData().getTransportConfiguration());
241 249 }
242 250
  251 + if(DeviceTransportType.TCP.name().equals(transportType)){
  252 + YtTcpDeviceProfileTransportConfiguration tcpDeviceProfileTransportConfiguration = (YtTcpDeviceProfileTransportConfiguration) deviceProfileDTO.getProfileData().getTransportConfiguration();
  253 + String scriptId = tcpDeviceProfileTransportConfiguration.getScriptId();
  254 + scriptText =javaScriptService.getScriptText(deviceProfileDTO.getTenantId(), scriptId);
  255 + deviceProfileDTO.setScriptId(scriptId);
  256 + }
  257 +
  258 + DeviceProfileData deviceProfileData = new DeviceProfileData();
  259 + buildDeviceProfileData(transportType,deviceProfileData,deviceProfileDTO.getProfileData().getTransportConfiguration(),scriptText);
  260 +
243 261 if(deviceProfileDTO.getProfileData()!=null
244 262 && deviceProfileDTO.getProfileData().getAlarms() !=null){
245 263 deviceProfileData.setAlarms(deviceProfileDTO.getProfileData().getAlarms());
246 264 }
  265 +
247 266 tbDeviceProfile.setProfileData(deviceProfileData);
248 267
249 268 return tbDeviceProfile;
... ...
  1 +package org.thingsboard.server.controller.yunteng;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import com.fasterxml.jackson.databind.ObjectMapper;
  5 +import com.fasterxml.jackson.databind.node.ObjectNode;
  6 +import com.google.common.util.concurrent.Futures;
  7 +import com.google.common.util.concurrent.ListenableFuture;
  8 +import com.google.common.util.concurrent.MoreExecutors;
  9 +import com.google.gson.JsonElement;
  10 +import com.google.gson.JsonParser;
  11 +import io.swagger.annotations.Api;
  12 +import io.swagger.annotations.ApiOperation;
  13 +import lombok.RequiredArgsConstructor;
  14 +import org.apache.commons.lang3.StringUtils;
  15 +import org.springframework.http.ResponseEntity;
  16 +import org.springframework.security.access.prepost.PreAuthorize;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.*;
  19 +import org.thingsboard.rule.engine.api.ScriptEngine;
  20 +import org.thingsboard.server.common.data.DeviceProfile;
  21 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
  22 +import org.thingsboard.server.common.data.DeviceProfileType;
  23 +import org.thingsboard.server.common.data.DeviceTransportType;
  24 +import org.thingsboard.server.common.data.audit.ActionType;
  25 +import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  26 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
  27 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  28 +import org.thingsboard.server.common.data.id.DeviceProfileId;
  29 +import org.thingsboard.server.common.data.id.RuleChainId;
  30 +import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  32 +import org.thingsboard.server.common.data.yunteng.common.DeleteGroup;
  33 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  34 +import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException;
  35 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  36 +import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO;
  37 +import org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO;
  38 +import org.thingsboard.server.common.data.yunteng.dto.YtDeviceScriptDTO;
  39 +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;
  40 +import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData;
  41 +import org.thingsboard.server.common.msg.queue.ServiceQueue;
  42 +import org.thingsboard.server.common.yunteng.script.YtScriptInvokeService;
  43 +import org.thingsboard.server.common.yunteng.script.YtScriptType;
  44 +import org.thingsboard.server.controller.BaseController;
  45 +import org.thingsboard.server.dao.yunteng.service.YtDeviceProfileService;
  46 +import org.thingsboard.server.dao.yunteng.service.YtDeviceScriptService;
  47 +
  48 +import java.time.LocalDateTime;
  49 +import java.time.ZoneOffset;
  50 +import java.util.List;
  51 +import java.util.Objects;
  52 +import java.util.UUID;
  53 +import java.util.concurrent.TimeUnit;
  54 +
  55 +import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*;
  56 +
  57 +@RestController
  58 +@RequiredArgsConstructor
  59 +@RequestMapping("api/yt/js")
  60 +@Api(tags = {"设备数据解析脚本管理"})
  61 +public class YtDeviceScriptController extends BaseController {
  62 + private final YtDeviceScriptService scriptService;
  63 + private final YtDeviceProfileService ytDeviceProfileService;
  64 + private final YtScriptInvokeService jsEngine;
  65 + private static final JsonParser parser = new JsonParser();
  66 +
  67 + private static final ObjectMapper objectMapper = new ObjectMapper();
  68 +
  69 + @PostMapping()
  70 + @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:js:post','api:yt:js:update'})")
  71 + @ApiOperation("创建 | 编辑")
  72 + public ResponseEntity<YtDeviceScriptDTO> saveJavaScript(
  73 + @RequestBody YtDeviceScriptDTO scriptDTO) throws ThingsboardException {
  74 + String id = scriptDTO.getId();
  75 + boolean created = StringUtils.isBlank(id);
  76 + String tenantId = getCurrentUser().getCurrentTenantId();
  77 + scriptDTO.setTenantId(tenantId);
  78 + scriptService.validateFormdata(scriptDTO,created);
  79 + /**
  80 + * 业务流程
  81 + * 1/3.编辑时同步刷新相关的设备配置
  82 + * 2/3.处理TB业务逻辑
  83 + * 3/3.处理业务平台的业务逻辑
  84 + */
  85 + if(!created){
  86 + List<DeviceProfileDTO> usedProfiles =ytDeviceProfileService.findDeviceProfile(tenantId,id);
  87 + for(DeviceProfileDTO profile:usedProfiles){
  88 + DeviceProfile tbDeviceProfile = buildTbDeviceProfileFromDeviceProfileDTO(profile,scriptDTO.getTenantId(),scriptDTO.getConvertJs());
  89 + updateTbDeviceProfile(tbDeviceProfile);
  90 + }
  91 +
  92 + }
  93 +
  94 +
  95 + String creator = getCurrentUser().getCurrentUserId();
  96 + scriptDTO.setCreator(creator);
  97 + scriptDTO.setUpdater(creator);
  98 + scriptService.insertOrUpdate(scriptDTO);
  99 +
  100 + return ResponseEntity.ok(scriptDTO);
  101 + }
  102 +
  103 + /**
  104 + * 更新thingsboard的设备配置信息
  105 + *
  106 + * @param deviceProfile 设备配置
  107 + * @throws ThingsboardException
  108 + */
  109 + private DeviceProfile updateTbDeviceProfile(DeviceProfile deviceProfile) throws ThingsboardException {
  110 + boolean isFirmwareChanged = false;
  111 + boolean isSoftwareChanged = false;
  112 +
  113 + DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfile.getId());
  114 + if (!Objects.equals(deviceProfile.getFirmwareId(), oldDeviceProfile.getFirmwareId())) {
  115 + isFirmwareChanged = true;
  116 + }
  117 + if (!Objects.equals(deviceProfile.getSoftwareId(), oldDeviceProfile.getSoftwareId())) {
  118 + isSoftwareChanged = true;
  119 + }
  120 + if (FastIotConstants.ASSERT_DEFAULT_NAME.equals(oldDeviceProfile.getName()) && !Objects.equals(deviceProfile.getName(), oldDeviceProfile.getName())) {
  121 + throw new YtDataValidationException(ErrorMessage.ASSERT_DEFAULT_NAME_NO_CHANGED.getMessage());
  122 + }
  123 +
  124 + DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
  125 +
  126 + tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
  127 + tbClusterService.broadcastEntityStateChangeEvent(deviceProfile.getTenantId(), savedDeviceProfile.getId(), ComponentLifecycleEvent.UPDATED);
  128 +
  129 + logEntityAction(savedDeviceProfile.getId(), savedDeviceProfile,
  130 + null,ActionType.UPDATED, null);
  131 +
  132 + otaPackageStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged);
  133 +
  134 + sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(),EdgeEventActionType.UPDATED);
  135 + return savedDeviceProfile;
  136 + }
  137 +
  138 + @GetMapping("{id}")
  139 + @ApiOperation("详情")
  140 +// @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:get'})")
  141 + public ResponseEntity<YtDeviceScriptDTO> getDevice(@PathVariable("id") String id) throws ThingsboardException {
  142 + return ResponseEntity.of(scriptService.getDeviceScript(getCurrentUser().getCurrentTenantId(), id));
  143 + }
  144 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  145 + @GetMapping(params = {PAGE_SIZE, PAGE})
  146 + @ApiOperation("分页查询")
  147 + public YtPageData<YtDeviceScriptDTO> pageDeviceProfile(
  148 + @RequestParam(PAGE_SIZE) int pageSize,
  149 + @RequestParam(PAGE) int page,
  150 + @RequestParam(value = "name", required = false) String name,
  151 + @RequestParam(value = "transportType", required = false) String transportType,
  152 + @RequestParam(value = ORDER_FILED, required = false) String orderFiled,
  153 + @RequestParam(value = ORDER_TYPE, required = false) OrderTypeEnum orderType) throws ThingsboardException {
  154 +
  155 + return scriptService.page(page, pageSize, orderFiled, orderType, getCurrentUser().getCurrentTenantId());
  156 + }
  157 +
  158 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  159 + @GetMapping("/me/list")
  160 + @ApiOperation("选项列表")
  161 + public ResponseEntity listDeviceProfile() throws ThingsboardException {
  162 + List<YtDeviceScriptDTO> results = scriptService.findDeviceScript(getCurrentUser().getCurrentTenantId());
  163 + return ResponseEntity.ok(results);
  164 + }
  165 +
  166 + @DeleteMapping
  167 + @ApiOperation("删除")
  168 + @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:delete'})")
  169 + public void deleteDevices(@Validated({DeleteGroup.class}) @RequestBody DeleteDTO deleteDTO) throws ThingsboardException {
  170 + scriptService.deleteScriptes(getCurrentUser().getCurrentTenantId(), deleteDTO.getIds());
  171 + }
  172 +
  173 +
  174 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  175 + @PostMapping("/test")
  176 + @ApiOperation("测试脚本执行结果")
  177 + public ResponseEntity<JsonNode> test(@RequestBody JsonNode inputParams) throws ThingsboardException {
  178 + try {
  179 + String script = inputParams.get("script").asText();
  180 + String jsParam = inputParams.get("params").asText();
  181 +
  182 +
  183 + String output = "";
  184 + String errorText = "";
  185 + ScriptEngine engine = null;
  186 + try {
  187 + UUID scriptId = jsEngine.eval(YtScriptType.TCP_TRANSPORT_SCRIPT, script).get();
  188 + ListenableFuture<String> result = Futures.transformAsync(jsEngine.invokeFunction(scriptId, jsParam),
  189 + o -> {
  190 + try {
  191 + JsonElement json = parser.parse(o.toString());
  192 + return Futures.immediateFuture(json.toString());
  193 + } catch (Exception e) {
  194 + return Futures.immediateFailedFuture(e);
  195 + }
  196 + }, MoreExecutors.directExecutor());
  197 +
  198 + output =result.get(20, TimeUnit.SECONDS);
  199 + } catch (Exception e) {
  200 + errorText = e.getMessage();
  201 + } finally {
  202 + if (engine != null) {
  203 + engine.destroy();
  204 + }
  205 + }
  206 + ObjectNode result = objectMapper.createObjectNode();
  207 + result.put("output", output);
  208 + result.put("error", errorText);
  209 + return ResponseEntity.ok(result);
  210 + } catch (Exception e) {
  211 + throw handleException(e);
  212 + }
  213 +
  214 +
  215 + }
  216 +
  217 +
  218 +
  219 +
  220 +
  221 + /**
  222 + * 构造调用TBDeviceProfile需要的参数
  223 + *
  224 + * @param deviceProfileDTO 页面接收的参数
  225 + * @return 封装好的TBDeviceProfile
  226 + */
  227 + private DeviceProfile buildTbDeviceProfileFromDeviceProfileDTO(DeviceProfileDTO deviceProfileDTO,String scriptId,String scriptText) {
  228 + DeviceProfile tbDeviceProfile = new DeviceProfile();
  229 + if (StringUtils.isNotBlank(deviceProfileDTO.getId())) {
  230 + UUID profileId = UUID.fromString(deviceProfileDTO.getId());
  231 + tbDeviceProfile.setId(new DeviceProfileId(profileId));
  232 + tbDeviceProfile.setCreatedTime(deviceProfileDTO.getCreateTime().toInstant(ZoneOffset.of("+8")).toEpochMilli());
  233 + }else{
  234 + tbDeviceProfile.setCreatedTime(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli());
  235 + }
  236 + tbDeviceProfile.setName(deviceProfileDTO.getName());
  237 + tbDeviceProfile.setImage(deviceProfileDTO.getImage());
  238 + tbDeviceProfile.setDescription(deviceProfileDTO.getDescription());
  239 + tbDeviceProfile.setType(DeviceProfileType.DEFAULT);
  240 + UUID tenantId = UUID.fromString(deviceProfileDTO.getTenantId());
  241 + tbDeviceProfile.setTenantId(TenantId.fromUUID(tenantId));
  242 + tbDeviceProfile.setDefault(deviceProfileDTO.isDefault());
  243 +
  244 + // 获取当前租户的默认规则链
  245 + if (StringUtils.isNotBlank(deviceProfileDTO.getDefaultRuleChainId())) {
  246 + UUID chainId = UUID.fromString(deviceProfileDTO.getDefaultRuleChainId());
  247 + tbDeviceProfile.setDefaultRuleChainId(new RuleChainId(chainId));
  248 + }
  249 +
  250 + tbDeviceProfile.setDefaultQueueName(ServiceQueue.MAIN);
  251 + tbDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
  252 +
  253 +
  254 + // 传输类型默认都是Default
  255 + String transportType = deviceProfileDTO.getTransportType();
  256 + tbDeviceProfile.setTransportType(DeviceTransportType.valueOf(transportType));
  257 +
  258 +
  259 + deviceProfileDTO.setScriptId(scriptId);
  260 +
  261 + DeviceProfileData deviceProfileData = new DeviceProfileData();
  262 + buildDeviceProfileData(transportType,deviceProfileData,deviceProfileDTO.getProfileData().getTransportConfiguration(),scriptText);
  263 +
  264 +
  265 + tbDeviceProfile.setProfileData(deviceProfileData);
  266 +
  267 + return tbDeviceProfile;
  268 + }
  269 +}
... ...
... ... @@ -176,7 +176,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
176 176
177 177 @Override
178 178 public void createSysAdmin() {
179   - createUser(Authority.SYS_ADMIN, null, null, "sysadmin@thingsboard.org", "sysadmin");
  179 + createUser(Authority.SYS_ADMIN, null, null, "sysadmin@qq.com", "Sysadmin@123");
180 180 }
181 181
182 182 @Override
... ...
... ... @@ -170,9 +170,9 @@ ui:
170 170 database:
171 171 ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
172 172 ts:
173   - type: "${DATABASE_TS_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale)
  173 + type: "${DATABASE_TS_TYPE:timescale}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale)
174 174 ts_latest:
175   - type: "${DATABASE_TS_LATEST_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale)
  175 + type: "${DATABASE_TS_LATEST_TYPE:timescale}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale)
176 176
177 177 # note: timescale works only with postgreSQL database for DATABASE_ENTITIES_TYPE.
178 178
... ... @@ -539,9 +539,9 @@ spring:
539 539 database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}"
540 540 datasource:
541 541 driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}"
542   - url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://47.99.141.212:20638/thingsboard-3.3.4}"
  542 + url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://47.99.141.212:20638/timescale-3.3.4}"
543 543 username: "${SPRING_DATASOURCE_USERNAME:postgres}"
544   - password: "${SPRING_DATASOURCE_PASSWORD:Vrr861!@waja}"
  544 + password: "${SPRING_DATASOURCE_PASSWORD:Xga751++nqnk}"
545 545 # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://101.133.234.90:28776/thingsboard-3.3.2}"
546 546 # username: "${SPRING_DATASOURCE_USERNAME:postgres}"
547 547 # password: "${SPRING_DATASOURCE_PASSWORD:Bua312!!iwcw}"
... ... @@ -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}"
... ... @@ -1143,8 +1196,8 @@ file:
1143 1196 staticUrl: /oss/files/** #oss静态访问路径 只有type = local需要
1144 1197 randomFileName: ${file.storage.randomFileName}
1145 1198 minio:
1146   - minioUrl: ${MINIO_URL:https://dev.thingskit.com:9000} #minio储存地址
1147   -# minioUrl: https://demo.thingskit.com:9000 #minio储存地址
  1199 +# minioUrl: ${MINIO_URL:https://dev.thingskit.com:9000} #minio储存地址
  1200 + minioUrl: https://demo.thingskit.com:9000 #minio储存地址
1148 1201 minioName: ${MINIO_NAME:YunTeng} #minio账户
1149 1202 minioPass: ${MINIO_PWD:YunTeng123456} #minio访问密码
1150 1203 bucketName: yunteng #minio储存桶名称
... ...
... ... @@ -43,6 +43,8 @@ public class DataConstants {
43 43 public static final String COAP_TRANSPORT_NAME = "COAP";
44 44 public static final String LWM2M_TRANSPORT_NAME = "LWM2M";
45 45 public static final String MQTT_TRANSPORT_NAME = "MQTT";
  46 + //Thingskit function
  47 + public static final String TCP_TRANSPORT_NAME = "TCP";
46 48 public static final String HTTP_TRANSPORT_NAME = "HTTP";
47 49 public static final String SNMP_TRANSPORT_NAME = "SNMP";
48 50
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data;
18 18 public enum DeviceTransportType {
19 19 DEFAULT,
20 20 MQTT,
  21 + TCP,
21 22 COAP,
22 23 LWM2M,
23 24 SNMP
... ...
... ... @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
21 21 import com.fasterxml.jackson.annotation.JsonTypeInfo;
22 22 import io.swagger.annotations.ApiModel;
23 23 import org.thingsboard.server.common.data.DeviceTransportType;
  24 +import org.thingsboard.server.common.data.device.profile.YtTcpDeviceProfileTransportConfiguration;
24 25
25 26 import java.io.Serializable;
26 27
... ... @@ -33,6 +34,7 @@ import java.io.Serializable;
33 34 @JsonSubTypes({
34 35 @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"),
35 36 @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"),
  37 + @JsonSubTypes.Type(value = YtTcpDeviceTransportConfiguration.class, name = "TCP"),
36 38 @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP"),
37 39 @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"),
38 40 @JsonSubTypes.Type(value = SnmpDeviceTransportConfiguration.class, name = "SNMP")})
... ...
  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.common.data.device.data;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonAnyGetter;
  19 +import com.fasterxml.jackson.annotation.JsonAnySetter;
  20 +import com.fasterxml.jackson.annotation.JsonIgnore;
  21 +import lombok.Data;
  22 +import org.thingsboard.server.common.data.DeviceTransportType;
  23 +
  24 +import java.util.HashMap;
  25 +import java.util.Map;
  26 +
  27 +@Data
  28 +public class YtTcpDeviceTransportConfiguration implements DeviceTransportConfiguration {
  29 +
  30 +
  31 + @Override
  32 + public DeviceTransportType getType() {
  33 + return DeviceTransportType.TCP;
  34 + }
  35 +
  36 +}
... ...
... ... @@ -31,6 +31,7 @@ import java.io.Serializable;
31 31 @JsonSubTypes({
32 32 @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"),
33 33 @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"),
  34 + @JsonSubTypes.Type(value = YtTcpDeviceProfileTransportConfiguration.class, name = "TCP"),
34 35 @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"),
35 36 @JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP"),
36 37 @JsonSubTypes.Type(value = SnmpDeviceProfileTransportConfiguration.class, name = "SNMP")
... ...
  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.common.data.device.profile;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.DeviceTransportType;
  20 +import org.thingsboard.server.common.data.validation.NoXss;
  21 +import org.thingsboard.server.common.data.yunteng.enums.TcpDataTypeEnum;
  22 +
  23 +@Data
  24 +public class YtTcpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
  25 +
  26 + @NoXss
  27 + private TcpDataTypeEnum dataFormat = TcpDataTypeEnum.HEX;
  28 + private String scriptId;
  29 + private String scriptText;
  30 + private String pingText;
  31 +
  32 + @Override
  33 + public DeviceTransportType getType() {
  34 + return DeviceTransportType.TCP;
  35 + }
  36 +
  37 +
  38 +
  39 +
  40 +}
... ...
... ... @@ -49,6 +49,11 @@ public final class ModelConstants {
49 49 "iotfs_user_organization_mapping";
50 50 /** 设备表 */
51 51 public static final String IOTFS_DEVICE_TABLE_NAME = "iotfs_device";
  52 + /** 产品信息(设备配置)扩展表 */
  53 + public static final String IOTFS_DEVICE_PROFILE_TABLE_NAME = "iotfs_device_profile";
  54 + /** 自定义数据协议解析脚本 */
  55 + public static final String IOTFS_DEVICE_SCRIPT_TABLE_NAME = "iotfs_java_script";
  56 +
52 57 /** 设备类型表 */
53 58 public static final String IOTFS_DEVICE_TYPE_TABLE_NAME = "iotfs_device_type";
54 59 /** 告警配置表 */
... ...
... ... @@ -81,7 +81,9 @@ public enum ErrorMessage {
81 81 DATA_BOARD_IS_PRIVATE(400057,"该数据看板不是公有视图"),
82 82 MESSAGE_SEND_TOO_FAST(400058,"1分钟内请求次数过多,请休息一下!"),
83 83 PASSWORD_INCORRECT(4010059, "密码错误!"),
84   - MESSAGE_SEND_FAILED(4010059, "消息发送失败!"),
  84 + MESSAGE_SEND_FAILED(4010060, "消息发送失败!"),
  85 + PROJECT_USED_SCRIPT(400061,"产品【%s】正在使用待删除的解析脚本"),
  86 + RULE_CHAIN_NOT_ENABLE(400062,"规则链不是有效的!"),
85 87 HAVE_NO_PERMISSION(500002,"没有修改权限");
86 88 private final int code;
87 89 private String message;
... ...
... ... @@ -7,11 +7,15 @@ import lombok.EqualsAndHashCode;
7 7 import org.thingsboard.server.common.data.DeviceProfileType;
8 8 import org.thingsboard.server.common.data.DeviceTransportType;
9 9 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  10 +import org.thingsboard.server.common.data.id.DeviceProfileId;
  11 +import org.thingsboard.server.common.data.id.TenantId;
10 12 import org.thingsboard.server.common.data.yunteng.common.AddGroup;
  13 +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum;
11 14 import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
12 15
13 16 import javax.validation.Valid;
14 17 import javax.validation.constraints.NotEmpty;
  18 +import javax.validation.constraints.NotNull;
15 19 import java.time.Instant;
16 20 import java.time.LocalDateTime;
17 21 import java.time.ZoneId;
... ... @@ -21,27 +25,38 @@ import java.util.UUID;
21 25
22 26 @EqualsAndHashCode(callSuper = true)
23 27 @Data
24   -public class DeviceProfileDTO extends TenantDTO {
  28 +public class DeviceProfileDTO extends BaseDTO {
25 29 @NotEmpty(message = "设备配置名称不能为空或者空字符串", groups = AddGroup.class)
26   - @ApiModelProperty(value = "设备配置名称")
  30 + @ApiModelProperty(value = "产品(设备配置)名称")
27 31 private String name;
28 32
29 33 private String description;
  34 +
30 35 /**
31   - * 转换脚本:TCP才会使用
  36 + * TCP协议的解析脚本ID,由后端自己回填
32 37 */
33   - private String convertJs;
  38 + private String scriptId;
34 39
  40 + @ApiModelProperty(value = "租户ID")
  41 + private String tenantId;
35 42 @NotEmpty(message = "传输协议不能为空或者空字符串", groups = AddGroup.class)
36 43 @ApiModelProperty(value = "传输协议", required = true)
37   - @NotEmpty(message = "传输协议不能为空或者二空字符串")
38 44 private String transportType;
  45 +
  46 + private String provisionType;
  47 +
  48 + @NotNull(message = "产品类型不能为空", groups = {AddGroup.class})
  49 + @ApiModelProperty(value = "产品类型:GATEWAY,DIRECT_CONNECTION,SENSOR", required = true)
  50 + private DeviceTypeEnum deviceType;
  51 +
39 52 /**
40 53 * TB的设备配置文件
41 54 */
42 55 @Deprecated
43 56 private String tbProfileId;
44 57
  58 +
  59 +
45 60 @Valid
46 61 private transient DeviceProfileData profileData;
47 62 @ApiModelProperty(value = "关联规则链,默认关联根规则链", required = false)
... ... @@ -76,17 +91,11 @@ public class DeviceProfileDTO extends TenantDTO {
76 91 this.profileData = JacksonUtil.convertValue(profileData, DeviceProfileData.class);
77 92
78 93 }
79   -
80   - public DeviceProfileDTO(UUID id, String name, Long time, String description, DeviceTransportType transportType, String defaultRuleChainId,boolean isDefault,String image,DeviceProfileType type, String convertJs) {
81   - setId(id.toString());
82   - setCreateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.of("+8")));
  94 + public DeviceProfileDTO(String name, TenantId tenantId, DeviceProfileId tbProfileId, DeviceTypeEnum deviceType) {
83 95 this.name = name;
84   - this.isDefault = isDefault;
85   - this.image = image;
86   - this.description = description;
87   - this.type = type.name();
88   - this.convertJs = convertJs;
89   - this.transportType = transportType.name();
90   - this.defaultRuleChainId = defaultRuleChainId.toString();
  96 + this.tenantId =tenantId.getId().toString();
  97 + this.tbProfileId = tbProfileId.getId().toString();
  98 + this.deviceType = deviceType;
91 99 }
  100 +
92 101 }
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import org.thingsboard.server.common.data.yunteng.common.AddGroup;
  7 +import org.thingsboard.server.common.data.yunteng.common.UpdateGroup;
  8 +
  9 +import javax.validation.constraints.NotEmpty;
  10 +import javax.validation.constraints.NotNull;
  11 +
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@Data
  14 +public class YtDeviceScriptDTO extends BaseDTO {
  15 + @NotEmpty(message = "解析脚本名称不能为空或者空字符串", groups = {AddGroup.class,UpdateGroup.class})
  16 + @ApiModelProperty(value = "解析脚本名称")
  17 + private String name;
  18 +
  19 + /** TB的租户ID */
  20 + @ApiModelProperty(value = "租户ID")
  21 + private String tenantId;
  22 +
  23 + @ApiModelProperty(value = "脚本描述")
  24 + private String description;
  25 + @NotEmpty(message = "解析脚本内容不能为空或者空字符串", groups = {AddGroup.class,UpdateGroup.class})
  26 + @ApiModelProperty(value = "解析脚本方法体内容")
  27 + private String convertJs;
  28 +
  29 + @ApiModelProperty(value = "状态:0禁用 1启用")
  30 + @NotNull(message = "状态不能为空",groups = UpdateGroup.class)
  31 + private Integer status;
  32 +
  33 + public YtDeviceScriptDTO() {
  34 + }
  35 +}
... ...
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/enums/TcpDataTypeEnum.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/yunteng/enums/DeviceCredentialsEnum.java
... ... @@ -3,9 +3,8 @@ package org.thingsboard.server.common.data.yunteng.enums;
3 3 /**
4 4 * 设备凭证
5 5 */
6   -public enum DeviceCredentialsEnum {
7   - ACCESS_TOKEN,
8   - X509_CERTIFICATE,
9   - MQTT_BASIC,
10   - LWM2M_CREDENTIALS
  6 +public enum TcpDataTypeEnum {
  7 + HEX,
  8 + JSON,
  9 + ASCII
11 10 }
... ...
... ... @@ -37,6 +37,7 @@
37 37 <modules>
38 38 <module>transport-api</module>
39 39 <module>mqtt</module>
  40 + <module>tcp</module>
40 41 <module>http</module>
41 42 <module>coap</module>
42 43 <module>lwm2m</module>
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2022 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  20 + <modelVersion>4.0.0</modelVersion>
  21 + <parent>
  22 + <groupId>org.thingsboard.common</groupId>
  23 + <version>3.3.4-SNAPSHOT</version>
  24 + <artifactId>transport</artifactId>
  25 + </parent>
  26 + <groupId>org.thingsboard.common.transport</groupId>
  27 + <artifactId>tcp</artifactId>
  28 + <packaging>jar</packaging>
  29 +
  30 + <name>Thingsboard TCP 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 +
  101 + </dependencies>
  102 +
  103 +</project>
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.tcp;
  17 +
  18 +import io.netty.handler.ssl.SslHandler;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.beans.factory.annotation.Qualifier;
  22 +import org.springframework.beans.factory.annotation.Value;
  23 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  24 +import org.springframework.boot.context.properties.ConfigurationProperties;
  25 +import org.springframework.context.annotation.Bean;
  26 +import org.springframework.stereotype.Component;
  27 +import org.springframework.util.StringUtils;
  28 +import org.thingsboard.server.common.data.DeviceTransportType;
  29 +import org.thingsboard.server.common.msg.EncryptionUtil;
  30 +import org.thingsboard.server.common.transport.TransportService;
  31 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  32 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  33 +import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
  34 +import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig;
  35 +import org.thingsboard.server.common.transport.util.SslUtil;
  36 +import org.thingsboard.server.gen.transport.TransportProtos;
  37 +
  38 +import javax.net.ssl.KeyManager;
  39 +import javax.net.ssl.KeyManagerFactory;
  40 +import javax.net.ssl.SSLContext;
  41 +import javax.net.ssl.SSLEngine;
  42 +import javax.net.ssl.TrustManager;
  43 +import javax.net.ssl.TrustManagerFactory;
  44 +import javax.net.ssl.X509TrustManager;
  45 +import java.security.cert.CertificateEncodingException;
  46 +import java.security.cert.CertificateException;
  47 +import java.security.cert.X509Certificate;
  48 +import java.util.concurrent.CountDownLatch;
  49 +import java.util.concurrent.TimeUnit;
  50 +
  51 +/**
  52 + * Created by valerii.sosliuk on 11/6/16.
  53 + */
  54 +@Slf4j
  55 +@Component("TcpSslHandlerProvider")
  56 +@ConditionalOnProperty(prefix = "transport.tcp.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
  57 +public class TcpSslHandlerProvider {
  58 +
  59 + @Value("${transport.tcp.ssl.protocol}")
  60 + private String sslProtocol;
  61 +
  62 + @Autowired
  63 + private TransportService transportService;
  64 +
  65 + @Bean
  66 + @ConfigurationProperties(prefix = "transport.tcp.ssl.credentials")
  67 + public SslCredentialsConfig tcpSslCredentials() {
  68 + return new SslCredentialsConfig("tcp SSL Credentials", false);
  69 + }
  70 +
  71 + @Autowired
  72 + @Qualifier("tcpSslCredentials")
  73 + private SslCredentialsConfig tcpSslCredentialsConfig;
  74 +
  75 + private SSLContext sslContext;
  76 +
  77 + public SslHandler getSslHandler() {
  78 + if (sslContext == null) {
  79 + sslContext = createSslContext();
  80 + }
  81 + SSLEngine sslEngine = sslContext.createSSLEngine();
  82 + sslEngine.setUseClientMode(false);
  83 + sslEngine.setNeedClientAuth(false);
  84 + sslEngine.setWantClientAuth(true);
  85 + sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols());
  86 + sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
  87 + sslEngine.setEnableSessionCreation(true);
  88 + return new SslHandler(sslEngine);
  89 + }
  90 +
  91 + private SSLContext createSslContext() {
  92 + try {
  93 + SslCredentials sslCredentials = this.tcpSslCredentialsConfig.getCredentials();
  94 + TrustManagerFactory tmFactory = sslCredentials.createTrustManagerFactory();
  95 + KeyManagerFactory kmf = sslCredentials.createKeyManagerFactory();
  96 +
  97 + KeyManager[] km = kmf.getKeyManagers();
  98 + TrustManager x509wrapped = getX509TrustManager(tmFactory);
  99 + TrustManager[] tm = {x509wrapped};
  100 + if (StringUtils.isEmpty(sslProtocol)) {
  101 + sslProtocol = "TLS";
  102 + }
  103 + SSLContext sslContext = SSLContext.getInstance(sslProtocol);
  104 + sslContext.init(km, tm, null);
  105 + return sslContext;
  106 + } catch (Exception e) {
  107 + log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e);
  108 + throw new RuntimeException("Failed to get SSL context", e);
  109 + }
  110 + }
  111 +
  112 + private TrustManager getX509TrustManager(TrustManagerFactory tmf) throws Exception {
  113 + X509TrustManager x509Tm = null;
  114 + for (TrustManager tm : tmf.getTrustManagers()) {
  115 + if (tm instanceof X509TrustManager) {
  116 + x509Tm = (X509TrustManager) tm;
  117 + break;
  118 + }
  119 + }
  120 + return new ThingsboardTcpX509TrustManager(x509Tm, transportService);
  121 + }
  122 +
  123 + static class ThingsboardTcpX509TrustManager implements X509TrustManager {
  124 +
  125 + private final X509TrustManager trustManager;
  126 + private TransportService transportService;
  127 +
  128 + ThingsboardTcpX509TrustManager(X509TrustManager trustManager, TransportService transportService) {
  129 + this.trustManager = trustManager;
  130 + this.transportService = transportService;
  131 + }
  132 +
  133 + @Override
  134 + public X509Certificate[] getAcceptedIssuers() {
  135 + return trustManager.getAcceptedIssuers();
  136 + }
  137 +
  138 + @Override
  139 + public void checkServerTrusted(X509Certificate[] chain,
  140 + String authType) throws CertificateException {
  141 + trustManager.checkServerTrusted(chain, authType);
  142 + }
  143 +
  144 + @Override
  145 + public void checkClientTrusted(X509Certificate[] chain,
  146 + String authType) throws CertificateException {
  147 + String credentialsBody = null;
  148 + for (X509Certificate cert : chain) {
  149 + try {
  150 + String strCert = SslUtil.getCertificateString(cert);
  151 + String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
  152 + final String[] credentialsBodyHolder = new String[1];
  153 + CountDownLatch latch = new CountDownLatch(1);
  154 + transportService.process(DeviceTransportType.TCP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
  155 + new TransportServiceCallback<ValidateDeviceCredentialsResponse>() {
  156 + @Override
  157 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  158 + if (!StringUtils.isEmpty(msg.getCredentials())) {
  159 + credentialsBodyHolder[0] = msg.getCredentials();
  160 + }
  161 + latch.countDown();
  162 + }
  163 +
  164 + @Override
  165 + public void onError(Throwable e) {
  166 + log.error(e.getMessage(), e);
  167 + latch.countDown();
  168 + }
  169 + });
  170 + latch.await(10, TimeUnit.SECONDS);
  171 + if (strCert.equals(credentialsBodyHolder[0])) {
  172 + credentialsBody = credentialsBodyHolder[0];
  173 + break;
  174 + }
  175 + } catch (InterruptedException | CertificateEncodingException e) {
  176 + log.error(e.getMessage(), e);
  177 + }
  178 + }
  179 + if (credentialsBody == null) {
  180 + throw new CertificateException("Invalid Device Certificate");
  181 + }
  182 + }
  183 + }
  184 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.tcp;
  17 +
  18 +import io.netty.handler.ssl.SslHandler;
  19 +import lombok.Getter;
  20 +import lombok.Setter;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.springframework.beans.factory.annotation.Value;
  24 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  25 +import org.springframework.stereotype.Component;
  26 +import org.thingsboard.server.common.transport.TransportContext;
  27 +import org.thingsboard.server.transport.tcp.adaptors.JsonTcpAdaptor;
  28 +
  29 +import javax.annotation.PostConstruct;
  30 +import java.net.InetSocketAddress;
  31 +import java.util.concurrent.atomic.AtomicInteger;
  32 +
  33 +/**
  34 + * Created by ashvayka on 04.10.18.
  35 + */
  36 +@Slf4j
  37 +@Component
  38 +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.tcp.enabled}'=='true')")
  39 +public class TcpTransportContext extends TransportContext {
  40 +
  41 + @Getter
  42 + @Autowired(required = false)
  43 + private TcpSslHandlerProvider sslHandlerProvider;
  44 +
  45 + @Getter
  46 + @Autowired
  47 + private JsonTcpAdaptor jsonTcpAdaptor;
  48 +
  49 +
  50 + @Getter
  51 + @Value("${transport.tcp.netty.max_payload_size}")
  52 + private Integer maxPayloadSize;
  53 +
  54 + @Getter
  55 + @Value("${transport.tcp.ssl.skip_validity_check_for_client_cert:false}")
  56 + private boolean skipValidityCheckForClientCert;
  57 +
  58 + @Getter
  59 + @Setter
  60 + private SslHandler sslHandler;
  61 +
  62 + @Getter
  63 + @Value("${transport.tcp.msg_queue_size_per_device_limit:100}")
  64 + private int messageQueueSizePerDeviceLimit;
  65 +
  66 + @Getter
  67 + @Value("${transport.tcp.timeout:10000}")
  68 + private long timeout;
  69 +
  70 + @Getter
  71 + @Value("${transport.tcp.proxy_enabled:false}")
  72 + private boolean proxyEnabled;
  73 +
  74 + private final AtomicInteger connectionsCounter = new AtomicInteger();
  75 +
  76 + @PostConstruct
  77 + public void init() {
  78 + super.init();
  79 + transportService.createGaugeStats("openConnections", connectionsCounter);
  80 + }
  81 +
  82 + public void channelRegistered() {
  83 + connectionsCounter.incrementAndGet();
  84 + }
  85 +
  86 + public void channelUnregistered() {
  87 + connectionsCounter.decrementAndGet();
  88 + }
  89 +
  90 + public boolean checkAddress(InetSocketAddress address) {
  91 + return rateLimitService.checkAddress(address);
  92 + }
  93 +
  94 + public void onAuthSuccess(InetSocketAddress address) {
  95 + rateLimitService.onAuthSuccess(address);
  96 + }
  97 +
  98 + public void onAuthFailure(InetSocketAddress address) {
  99 + rateLimitService.onAuthFailure(address);
  100 + }
  101 +
  102 +}
... ...
  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.google.gson.JsonParseException;
  19 +import io.netty.buffer.ByteBuf;
  20 +import io.netty.buffer.Unpooled;
  21 +import io.netty.channel.ChannelFuture;
  22 +import io.netty.channel.ChannelHandlerContext;
  23 +import io.netty.channel.ChannelInboundHandlerAdapter;
  24 +import io.netty.handler.codec.mqtt.*;
  25 +import io.netty.handler.ssl.SslHandler;
  26 +import io.netty.util.ReferenceCountUtil;
  27 +import io.netty.util.concurrent.Future;
  28 +import io.netty.util.concurrent.GenericFutureListener;
  29 +import lombok.extern.slf4j.Slf4j;
  30 +import org.apache.commons.lang3.StringUtils;
  31 +import org.thingsboard.server.common.data.*;
  32 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  33 +import org.thingsboard.server.common.data.id.DeviceId;
  34 +import org.thingsboard.server.common.data.id.OtaPackageId;
  35 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  36 +import org.thingsboard.server.common.data.yunteng.enums.TcpDataTypeEnum;
  37 +import org.thingsboard.server.common.msg.EncryptionUtil;
  38 +import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
  39 +import org.thingsboard.server.common.transport.SessionMsgListener;
  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.auth.SessionInfoCreator;
  44 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  45 +import org.thingsboard.server.common.transport.util.SslUtil;
  46 +import org.thingsboard.server.gen.transport.TransportProtos;
  47 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
  48 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
  49 +import org.thingsboard.server.queue.scheduler.SchedulerComponent;
  50 +import org.thingsboard.server.transport.tcp.adaptors.TcpTransportAdaptor;
  51 +import org.thingsboard.server.transport.tcp.session.DeviceSessionCtx;
  52 +import org.thingsboard.server.transport.tcp.session.TCPMessage;
  53 +import org.thingsboard.server.transport.tcp.util.ByteUtils;
  54 +
  55 +import javax.net.ssl.SSLPeerUnverifiedException;
  56 +import java.net.InetSocketAddress;
  57 +import java.security.cert.Certificate;
  58 +import java.security.cert.X509Certificate;
  59 +import java.util.List;
  60 +import java.util.Optional;
  61 +import java.util.UUID;
  62 +import java.util.concurrent.ConcurrentHashMap;
  63 +import java.util.concurrent.ConcurrentMap;
  64 +import java.util.regex.Matcher;
  65 +import java.util.regex.Pattern;
  66 +
  67 +import static com.amazonaws.util.StringUtils.UTF8;
  68 +import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
  69 +import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
  70 +import static io.netty.handler.codec.mqtt.MqttMessageType.*;
  71 +import static io.netty.handler.codec.mqtt.MqttQoS.*;
  72 +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED;
  73 +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN;
  74 +
  75 +/**
  76 + * @author Andrew Shvayka
  77 + */
  78 +@Slf4j
  79 +public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener<Future<? super Void>>, SessionMsgListener {
  80 +
  81 + private static final Pattern FW_REQUEST_PATTERN = Pattern.compile(MqttTopics.DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN);
  82 + private static final Pattern SW_REQUEST_PATTERN = Pattern.compile(MqttTopics.DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN);
  83 +
  84 +
  85 + private static final String PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE";
  86 +
  87 + private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE;
  88 +
  89 + private final UUID sessionId;
  90 + private final TcpTransportContext context;
  91 + private final TransportService transportService;
  92 + private final SchedulerComponent scheduler;
  93 + private final SslHandler sslHandler;
  94 +
  95 +
  96 + /**需要处理的消息队列,例如:需要下发给设备的,设备上传的。*/
  97 + final DeviceSessionCtx deviceSessionCtx;
  98 + volatile InetSocketAddress address;
  99 +
  100 + private final ConcurrentHashMap<String, String> otaPackSessions;
  101 + private final ConcurrentHashMap<String, Integer> chunkSizes;
  102 + private final ConcurrentMap<Integer, TransportProtos.ToDeviceRpcRequestMsg> rpcAwaitingAck;
  103 +
  104 +
  105 +
  106 + TcpTransportHandler(TcpTransportContext context, SslHandler sslHandler) {
  107 + this.sessionId = UUID.randomUUID();
  108 + this.context = context;
  109 + this.transportService = context.getTransportService();
  110 + this.scheduler = context.getScheduler();
  111 + this.sslHandler = sslHandler;
  112 + this.deviceSessionCtx = new DeviceSessionCtx(sessionId, context);
  113 + this.otaPackSessions = new ConcurrentHashMap<>();
  114 + this.chunkSizes = new ConcurrentHashMap<>();
  115 + this.rpcAwaitingAck = new ConcurrentHashMap<>();
  116 + }
  117 +
  118 + @Override
  119 + public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
  120 + super.channelRegistered(ctx);
  121 + context.channelRegistered();
  122 + }
  123 +
  124 + @Override
  125 + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
  126 + super.channelUnregistered(ctx);
  127 + context.channelUnregistered();
  128 + }
  129 +
  130 + @Override
  131 + public void channelRead(ChannelHandlerContext ctx, Object msg) {
  132 + log.trace("【{}】 Processing msg: 【{}】", sessionId, msg);
  133 + if (address == null) {
  134 + address = getAddress(ctx);
  135 + }
  136 + try {
  137 + if (msg instanceof ByteBuf) {
  138 + ByteBuf message = (ByteBuf) msg;
  139 +
  140 + processTcpMsg(ctx, ByteUtils.buf2Bytes(message));
  141 +
  142 + } else {
  143 + log.debug("【{}】 Received non tcp message: 【{}】", sessionId, msg.getClass().getSimpleName());
  144 + ctx.close();
  145 + }
  146 + } finally {
  147 + ReferenceCountUtil.safeRelease(msg);
  148 + }
  149 + }
  150 +
  151 + InetSocketAddress getAddress(ChannelHandlerContext ctx) {
  152 + var address = ctx.channel().attr(TcpTransportService.ADDRESS).get();
  153 + if (address == null) {
  154 + log.trace("[{}] Received empty address.", ctx.channel().id());
  155 + InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress();
  156 + log.trace("[{}] Going to use address: {}", ctx.channel().id(), remoteAddress);
  157 + return remoteAddress;
  158 + } else {
  159 + log.trace("[{}] Received address: {}", ctx.channel().id(), address);
  160 + }
  161 + return address;
  162 + }
  163 +
  164 + void processTcpMsg(ChannelHandlerContext ctx, byte[] msg) {
  165 + deviceSessionCtx.setChannel(ctx);
  166 + if (deviceSessionCtx.getDeviceInfo() == null || deviceSessionCtx.getDeviceProfile() == null) {
  167 + processConnect(ctx, ByteUtils.getString(msg, ByteUtils.UTF_8));
  168 + } else {
  169 + enqueueRegularSessionMsg(ctx,msg);
  170 + }
  171 + }
  172 +
  173 +
  174 +
  175 + void enqueueRegularSessionMsg(ChannelHandlerContext ctx, byte[] msg) {
  176 + final int queueSize = deviceSessionCtx.getMsgQueueSize();
  177 + if (queueSize >= context.getMessageQueueSizePerDeviceLimit()) {
  178 + log.info("Closing current session because msq queue size for device {} exceed limit {} with msgQueueSize counter {} and actual queue size {}",
  179 + deviceSessionCtx.getDeviceId(), context.getMessageQueueSizePerDeviceLimit(), queueSize, deviceSessionCtx.getMsgQueueSize());
  180 + ctx.close();
  181 + return;
  182 + }
  183 +
  184 + deviceSessionCtx.addToQueue(new TCPMessage(MqttMessageType.PUBLISH,msg));
  185 + 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
  186 + }
  187 +
  188 +
  189 +
  190 + void processMsgQueue(ChannelHandlerContext ctx) {
  191 + if (!deviceSessionCtx.isConnected()) {
  192 + log.trace("[{}][{}] Postpone processing msg due to device is not connected. Msg queue size is {}", sessionId, deviceSessionCtx.getDeviceId(), deviceSessionCtx.getMsgQueueSize());
  193 + return;
  194 + }
  195 + deviceSessionCtx.tryProcessQueuedMsgs(msg -> processRegularSessionMsg(ctx, msg));
  196 + }
  197 +
  198 + void processRegularSessionMsg(ChannelHandlerContext ctx, TCPMessage msg) {
  199 + switch (msg.getMessageType()) {
  200 + case PUBLISH:
  201 + processPublish(ctx, msg);
  202 + break;
  203 + case SUBSCRIBE:
  204 +// processSubscribe(ctx, (MqttSubscribeMessage) msg);
  205 + break;
  206 + case UNSUBSCRIBE:
  207 +// processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg);
  208 + break;
  209 +
  210 + case DISCONNECT:
  211 + ctx.close();
  212 + break;
  213 + case PUBACK:
  214 +// int msgId = ((MqttPubAckMessage) msg).variableHeader().messageId();
  215 +// TransportProtos.ToDeviceRpcRequestMsg rpcRequest = rpcAwaitingAck.remove(msgId);
  216 +// if (rpcRequest != null) {
  217 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY);
  218 +// }
  219 + break;
  220 + default:
  221 + break;
  222 + }
  223 + }
  224 +
  225 + private ByteBuf toDeviceMsg(byte[] msgs){
  226 + return Unpooled.copiedBuffer(msgs);
  227 + }
  228 +
  229 + private String textFromMessage(byte[] payload){
  230 + if(deviceSessionCtx.getPayloadType().equals(TcpDataTypeEnum.ASCII)){
  231 + return ByteUtils.getString(payload,ByteUtils.UTF_8);
  232 + }else{
  233 + return ByteUtils.bytesToHex(payload);
  234 + }
  235 + }
  236 + private void processPublish(ChannelHandlerContext ctx, TCPMessage mqttMsg) {
  237 + if (!checkConnected(ctx, mqttMsg)) {
  238 + return;
  239 + }
  240 + if(deviceSessionCtx.getPingText().equals(mqttMsg.getMessage())){
  241 + ctx.channel().writeAndFlush(toDeviceMsg(mqttMsg.getMessage()));
  242 + transportService.reportActivity(deviceSessionCtx.getSessionInfo());
  243 + return;
  244 + }
  245 + String dataStr = textFromMessage(mqttMsg.getMessage());
  246 + log.trace("[{}][{}] Processing publish msg [{}]!", sessionId, deviceSessionCtx.getDeviceId(), dataStr);
  247 +
  248 + processDevicePublish(ctx, dataStr);
  249 + }
  250 +
  251 +
  252 + private void processDevicePublish(ChannelHandlerContext ctx, String mqttMsg) {
  253 + try {
  254 +// Matcher fwMatcher;
  255 + TcpTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor();
  256 + TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg);
  257 + transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, null);
  258 +// if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) {
  259 +// TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
  260 +// transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg));
  261 +// } else if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) {
  262 +// } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) {
  263 +// TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
  264 +// transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));
  265 +// attrReqTopicType = TopicType.V1;
  266 +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC)) {
  267 +// TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC);
  268 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg));
  269 +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC)) {
  270 +// TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC);
  271 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg));
  272 +// toServerRpcSubTopicType = TopicType.V1;
  273 +// } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) {
  274 +// TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg);
  275 +// transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg));
  276 +// } else if ((fwMatcher = FW_REQUEST_PATTERN.matcher(topicName)).find()) {
  277 +// getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.FIRMWARE);
  278 +// } else if ((fwMatcher = SW_REQUEST_PATTERN.matcher(topicName)).find()) {
  279 +// getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.SOFTWARE);
  280 +// } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC)) {
  281 +// TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg);
  282 +// transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg));
  283 +// } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC)) {
  284 +// TransportProtos.PostTelemetryMsg postTelemetryMsg = context.getJsonMqttAdaptor().convertToPostTelemetry(deviceSessionCtx, mqttMsg);
  285 +// transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg));
  286 +// } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_PROTO_TOPIC)) {
  287 +// TransportProtos.PostTelemetryMsg postTelemetryMsg = context.getAscallAdaptor().convertToPostTelemetry(deviceSessionCtx, mqttMsg);
  288 +// transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg));
  289 +// } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC)) {
  290 +// TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
  291 +// transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg));
  292 +// } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC)) {
  293 +// TransportProtos.PostAttributeMsg postAttributeMsg = context.getJsonMqttAdaptor().convertToPostAttributes(deviceSessionCtx, mqttMsg);
  294 +// transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg));
  295 +// } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC)) {
  296 +// TransportProtos.PostAttributeMsg postAttributeMsg = context.getAscallAdaptor().convertToPostAttributes(deviceSessionCtx, mqttMsg);
  297 +// transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg));
  298 +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC)) {
  299 +// TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = context.getJsonMqttAdaptor().convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC);
  300 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg));
  301 +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_PROTO_TOPIC)) {
  302 +// TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = context.getAscallAdaptor().convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_PROTO_TOPIC);
  303 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg));
  304 +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_TOPIC)) {
  305 +// TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_TOPIC);
  306 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg));
  307 +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_SHORT_JSON_TOPIC)) {
  308 +// TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = context.getJsonMqttAdaptor().convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_JSON_TOPIC);
  309 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg));
  310 +// toServerRpcSubTopicType = TopicType.V2_JSON;
  311 +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_SHORT_PROTO_TOPIC)) {
  312 +// TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = context.getAscallAdaptor().convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_PROTO_TOPIC);
  313 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg));
  314 +// toServerRpcSubTopicType = TopicType.V2_PROTO;
  315 +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_SHORT_TOPIC)) {
  316 +// TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_TOPIC);
  317 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg));
  318 +// toServerRpcSubTopicType = TopicType.V2;
  319 +// } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX)) {
  320 +// TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getJsonMqttAdaptor().convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX);
  321 +// transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));
  322 +// attrReqTopicType = TopicType.V2_JSON;
  323 +// } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX)) {
  324 +// TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getAscallAdaptor().convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX);
  325 +// transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));
  326 +// attrReqTopicType = TopicType.V2_PROTO;
  327 +// } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX)) {
  328 +// TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX);
  329 +// transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));
  330 +// attrReqTopicType = TopicType.V2;
  331 +// } else {
  332 +// transportService.reportActivity(deviceSessionCtx.getSessionInfo());
  333 +// ack(ctx, msgId);
  334 +// }
  335 + } catch (AdaptorException e) {
  336 + log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, mqttMsg, e);
  337 + ctx.close();
  338 + }
  339 + }
  340 +
  341 + private void getOtaPackageCallback(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, int msgId, Matcher fwMatcher, OtaPackageType type) {
  342 + String payload = mqttMsg.content().toString(UTF8);
  343 + int chunkSize = StringUtils.isNotEmpty(payload) ? Integer.parseInt(payload) : 0;
  344 + String requestId = fwMatcher.group("requestId");
  345 + int chunk = Integer.parseInt(fwMatcher.group("chunk"));
  346 +
  347 + if (chunkSize > 0) {
  348 + this.chunkSizes.put(requestId, chunkSize);
  349 + } else {
  350 + chunkSize = chunkSizes.getOrDefault(requestId, 0);
  351 + }
  352 +
  353 + if (chunkSize > context.getMaxPayloadSize()) {
  354 + sendOtaPackageError(ctx, PAYLOAD_TOO_LARGE);
  355 + return;
  356 + }
  357 +
  358 + String otaPackageId = otaPackSessions.get(requestId);
  359 +
  360 + if (otaPackageId != null) {
  361 + sendOtaPackage(ctx, mqttMsg.variableHeader().packetId(), otaPackageId, requestId, chunkSize, chunk, type);
  362 + } else {
  363 + TransportProtos.SessionInfoProto sessionInfo = deviceSessionCtx.getSessionInfo();
  364 + TransportProtos.GetOtaPackageRequestMsg getOtaPackageRequestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder()
  365 + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
  366 + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
  367 + .setTenantIdMSB(sessionInfo.getTenantIdMSB())
  368 + .setTenantIdLSB(sessionInfo.getTenantIdLSB())
  369 + .setType(type.name())
  370 + .build();
  371 + transportService.process(deviceSessionCtx.getSessionInfo(), getOtaPackageRequestMsg,
  372 + new OtaPackageCallback(ctx, msgId, getOtaPackageRequestMsg, requestId, chunkSize, chunk));
  373 + }
  374 + }
  375 +
  376 + private void ack(ChannelHandlerContext ctx, int msgId) {
  377 + if (msgId > 0) {
  378 + ctx.writeAndFlush(createMqttPubAckMsg(msgId));
  379 + }
  380 + }
  381 +
  382 + private <T> TransportServiceCallback<Void> getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) {
  383 + return new TransportServiceCallback<>() {
  384 + @Override
  385 + public void onSuccess(Void dummy) {
  386 + log.trace("[{}] Published msg: {}", sessionId, msg);
  387 + ack(ctx, msgId);
  388 + }
  389 +
  390 + @Override
  391 + public void onError(Throwable e) {
  392 + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
  393 + ctx.close();
  394 + }
  395 + };
  396 + }
  397 +
  398 + private class DeviceProvisionCallback implements TransportServiceCallback<ProvisionDeviceResponseMsg> {
  399 + private final ChannelHandlerContext ctx;
  400 + private final int msgId;
  401 + private final TransportProtos.ProvisionDeviceRequestMsg msg;
  402 +
  403 + DeviceProvisionCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.ProvisionDeviceRequestMsg msg) {
  404 + this.ctx = ctx;
  405 + this.msgId = msgId;
  406 + this.msg = msg;
  407 + }
  408 +
  409 + @Override
  410 + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) {
  411 +// log.trace("[{}] Published msg: {}", sessionId, msg);
  412 +// ack(ctx, msgId);
  413 +// try {
  414 +// if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) {
  415 +// deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  416 +// } else {
  417 +// deviceSessionCtx.getContext().getAscallAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  418 +// }
  419 +// scheduler.schedule((Callable<ChannelFuture>) ctx::close, 60, TimeUnit.SECONDS);
  420 +// } catch (Exception e) {
  421 +// log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
  422 +// }
  423 + }
  424 +
  425 + @Override
  426 + public void onError(Throwable e) {
  427 + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
  428 + ctx.close();
  429 + }
  430 + }
  431 +
  432 + private class OtaPackageCallback implements TransportServiceCallback<TransportProtos.GetOtaPackageResponseMsg> {
  433 + private final ChannelHandlerContext ctx;
  434 + private final int msgId;
  435 + private final TransportProtos.GetOtaPackageRequestMsg msg;
  436 + private final String requestId;
  437 + private final int chunkSize;
  438 + private final int chunk;
  439 +
  440 + OtaPackageCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.GetOtaPackageRequestMsg msg, String requestId, int chunkSize, int chunk) {
  441 + this.ctx = ctx;
  442 + this.msgId = msgId;
  443 + this.msg = msg;
  444 + this.requestId = requestId;
  445 + this.chunkSize = chunkSize;
  446 + this.chunk = chunk;
  447 + }
  448 +
  449 + @Override
  450 + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) {
  451 + if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) {
  452 + OtaPackageId firmwareId = new OtaPackageId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()));
  453 + otaPackSessions.put(requestId, firmwareId.toString());
  454 + sendOtaPackage(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk, OtaPackageType.valueOf(response.getType()));
  455 + } else {
  456 + sendOtaPackageError(ctx, response.getResponseStatus().toString());
  457 + }
  458 + }
  459 +
  460 + @Override
  461 + public void onError(Throwable e) {
  462 + log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e);
  463 + ctx.close();
  464 + }
  465 + }
  466 +
  467 + private void sendOtaPackage(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk, OtaPackageType type) {
  468 + log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId);
  469 + ack(ctx, msgId);
  470 + try {
  471 + byte[] firmwareChunk = context.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk);
  472 + deviceSessionCtx.getPayloadAdaptor()
  473 + .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk, type)
  474 + .ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  475 + } catch (Exception e) {
  476 + log.trace("[{}] Failed to send firmware response!", sessionId, e);
  477 + }
  478 + }
  479 +
  480 + private void sendOtaPackageError(ChannelHandlerContext ctx, String error) {
  481 + log.warn("[{}] {}", sessionId, error);
  482 + deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx
  483 + .getPayloadAdaptor()
  484 + .createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC, error.getBytes()));
  485 + ctx.close();
  486 + }
  487 +
  488 + private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
  489 +// if (!checkConnected(ctx, mqttMsg)) {
  490 +// return;
  491 +// }
  492 +// log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId());
  493 +// List<Integer> grantedQoSList = new ArrayList<>();
  494 +// boolean activityReported = false;
  495 +// for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) {
  496 +// String topic = subscription.topicName();
  497 +// MqttQoS reqQoS = subscription.qualityOfService();
  498 +// try {
  499 +// switch (topic) {
  500 +// case MqttTopics.DEVICE_ATTRIBUTES_TOPIC: {
  501 +// processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V1);
  502 +// activityReported = true;
  503 +// break;
  504 +// }
  505 +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC: {
  506 +// processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2);
  507 +// activityReported = true;
  508 +// break;
  509 +// }
  510 +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC: {
  511 +// processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_JSON);
  512 +// activityReported = true;
  513 +// break;
  514 +// }
  515 +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC: {
  516 +// processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_PROTO);
  517 +// activityReported = true;
  518 +// break;
  519 +// }
  520 +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC: {
  521 +// processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V1);
  522 +// activityReported = true;
  523 +// break;
  524 +// }
  525 +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC: {
  526 +// processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2);
  527 +// activityReported = true;
  528 +// break;
  529 +// }
  530 +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC: {
  531 +// processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_JSON);
  532 +// activityReported = true;
  533 +// break;
  534 +// }
  535 +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC: {
  536 +// processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_PROTO);
  537 +// activityReported = true;
  538 +// break;
  539 +// }
  540 +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC:
  541 +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_TOPIC:
  542 +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_JSON_TOPIC:
  543 +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_PROTO_TOPIC:
  544 +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
  545 +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC:
  546 +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC:
  547 +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC:
  548 +// case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC:
  549 +// case MqttTopics.GATEWAY_RPC_TOPIC:
  550 +// case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
  551 +// case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
  552 +// case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC:
  553 +// case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC:
  554 +// case MqttTopics.DEVICE_SOFTWARE_RESPONSES_TOPIC:
  555 +//
  556 +// break;
  557 +// default:
  558 +// log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS);
  559 +// grantedQoSList.add(FAILURE.value());
  560 +// break;
  561 +// }
  562 +// } catch (Exception e) {
  563 +// log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS, e);
  564 +// grantedQoSList.add(FAILURE.value());
  565 +// }
  566 +// }
  567 +// if (!activityReported) {
  568 +// transportService.reportActivity(deviceSessionCtx.getSessionInfo());
  569 +// }
  570 +// ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList));
  571 + }
  572 +
  573 +// private void processRpcSubscribe(List<Integer> grantedQoSList, String topic, MqttQoS reqQoS, TopicType topicType) {
  574 +// transportService.process(deviceSessionCtx.getSessionInfo(), TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null);
  575 +// rpcSubTopicType = topicType;
  576 +// }
  577 +//
  578 +// private void processAttributesSubscribe(List<Integer> grantedQoSList, String topic, MqttQoS reqQoS, TopicType topicType) {
  579 +// transportService.process(deviceSessionCtx.getSessionInfo(), TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null);
  580 +// attrSubTopicType = topicType;
  581 +// }
  582 +
  583 +
  584 +
  585 + private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) {
  586 +// if (!checkConnected(ctx, mqttMsg)) {
  587 +// return;
  588 +// }
  589 +// boolean activityReported = false;
  590 +// log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId());
  591 +// for (String topicName : mqttMsg.payload().topics()) {
  592 +// try {
  593 +// switch (topicName) {
  594 +// case MqttTopics.DEVICE_ATTRIBUTES_TOPIC:
  595 +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC:
  596 +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC:
  597 +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC: {
  598 +// transportService.process(deviceSessionCtx.getSessionInfo(),
  599 +// TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), null);
  600 +// activityReported = true;
  601 +// break;
  602 +// }
  603 +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC:
  604 +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC:
  605 +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC:
  606 +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC: {
  607 +// transportService.process(deviceSessionCtx.getSessionInfo(),
  608 +// TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), null);
  609 +// activityReported = true;
  610 +// break;
  611 +// }
  612 +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC:
  613 +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_TOPIC:
  614 +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_JSON_TOPIC:
  615 +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_PROTO_TOPIC:
  616 +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
  617 +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC:
  618 +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC:
  619 +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC:
  620 +// case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC:
  621 +// case MqttTopics.GATEWAY_RPC_TOPIC:
  622 +// case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
  623 +// case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
  624 +// case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC:
  625 +// case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC:
  626 +// case MqttTopics.DEVICE_SOFTWARE_RESPONSES_TOPIC:
  627 +// case MqttTopics.DEVICE_SOFTWARE_ERROR_TOPIC: {
  628 +// activityReported = true;
  629 +// break;
  630 +// }
  631 +// }
  632 +// } catch (Exception e) {
  633 +// log.debug("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName);
  634 +// }
  635 +// }
  636 +// if (!activityReported) {
  637 +// transportService.reportActivity(deviceSessionCtx.getSessionInfo());
  638 +// }
  639 +// ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId()));
  640 + }
  641 +
  642 + private MqttMessage createUnSubAckMessage(int msgId) {
  643 + MqttFixedHeader mqttFixedHeader =
  644 + new MqttFixedHeader(UNSUBACK, false, AT_MOST_ONCE, false, 0);
  645 + MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId);
  646 + return new MqttMessage(mqttFixedHeader, mqttMessageIdVariableHeader);
  647 + }
  648 +
  649 + void processConnect(ChannelHandlerContext ctx, String accessToken) {
  650 + log.debug("[{}][{}] Processing connect msg for client: {}!", address, sessionId, accessToken);
  651 +
  652 + if (DataConstants.PROVISION.equals(accessToken) || DataConstants.PROVISION.equals(accessToken)) {
  653 + deviceSessionCtx.setProvisionOnly(true);
  654 + ctx.writeAndFlush(createTcpConnAckMsg(CONNECTION_ACCEPTED));
  655 + } else {
  656 + X509Certificate cert;
  657 + if (sslHandler != null && (cert = getX509Certificate()) != null) {
  658 +// processX509CertConnect(ctx, cert, msg);
  659 + } else {
  660 + processAuthTokenConnect(ctx, accessToken);
  661 + }
  662 + }
  663 + }
  664 +
  665 + private void processAuthTokenConnect(ChannelHandlerContext ctx, String accessToken) {
  666 +
  667 + log.debug("[{}][{}] Processing connect msg for client with user name: {}!", address, sessionId, accessToken);
  668 + TransportProtos.ValidateDeviceTokenRequestMsg.Builder request = TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder()
  669 + .setToken(accessToken);
  670 +
  671 + transportService.process(DeviceTransportType.TCP, request.build(),
  672 + new TransportServiceCallback<>() {
  673 + @Override
  674 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  675 + onValidateDeviceResponse(msg, ctx);
  676 + }
  677 +
  678 + @Override
  679 + public void onError(Throwable e) {
  680 + log.trace("[{}] Failed to process credentials: {}", address, accessToken, e);
  681 + ctx.writeAndFlush(createTcpConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE));
  682 + ctx.close();
  683 + }
  684 + });
  685 + }
  686 +
  687 + private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert, MqttConnectMessage connectMessage) {
  688 + try {
  689 + if (!context.isSkipValidityCheckForClientCert()) {
  690 + cert.checkValidity();
  691 + }
  692 + String strCert = SslUtil.getCertificateString(cert);
  693 + String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
  694 + transportService.process(DeviceTransportType.MQTT, ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
  695 + new TransportServiceCallback<>() {
  696 + @Override
  697 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  698 + onValidateDeviceResponse(msg, ctx);
  699 + }
  700 +
  701 + @Override
  702 + public void onError(Throwable e) {
  703 + log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e);
  704 + ctx.writeAndFlush(createTcpConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE));
  705 + ctx.close();
  706 + }
  707 + });
  708 + } catch (Exception e) {
  709 + context.onAuthFailure(address);
  710 + ctx.writeAndFlush(createTcpConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED));
  711 + log.trace("[{}] X509 auth failure: {}", sessionId, address, e);
  712 + ctx.close();
  713 + }
  714 + }
  715 +
  716 + private X509Certificate getX509Certificate() {
  717 + try {
  718 + Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates();
  719 + if (certChain.length > 0) {
  720 + return (X509Certificate) certChain[0];
  721 + }
  722 + } catch (SSLPeerUnverifiedException e) {
  723 + log.warn(e.getMessage());
  724 + return null;
  725 + }
  726 + return null;
  727 + }
  728 +
  729 + private ByteBuf createTcpConnAckMsg(MqttConnectReturnCode msg) {
  730 + return Unpooled.copiedBuffer(ByteUtils.hexStr2Bytes(msg.name()));
  731 + }
  732 +
  733 + @Override
  734 + public void channelReadComplete(ChannelHandlerContext ctx) {
  735 + ctx.flush();
  736 + }
  737 +
  738 + @Override
  739 + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
  740 + log.error("[{}] Unexpected Exception", sessionId, cause);
  741 + ctx.close();
  742 + if (cause instanceof OutOfMemoryError) {
  743 + log.error("Received critical error. Going to shutdown the service.");
  744 + System.exit(1);
  745 + }
  746 + }
  747 +
  748 + private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) {
  749 + MqttFixedHeader mqttFixedHeader =
  750 + new MqttFixedHeader(SUBACK, false, AT_MOST_ONCE, false, 0);
  751 + MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId);
  752 + MqttSubAckPayload mqttSubAckPayload = new MqttSubAckPayload(grantedQoSList);
  753 + return new MqttSubAckMessage(mqttFixedHeader, mqttMessageIdVariableHeader, mqttSubAckPayload);
  754 + }
  755 +
  756 +
  757 +
  758 + public static MqttPubAckMessage createMqttPubAckMsg(int requestId) {
  759 + MqttFixedHeader mqttFixedHeader =
  760 + new MqttFixedHeader(PUBACK, false, AT_MOST_ONCE, false, 0);
  761 + MqttMessageIdVariableHeader mqttMsgIdVariableHeader =
  762 + MqttMessageIdVariableHeader.from(requestId);
  763 + return new MqttPubAckMessage(mqttFixedHeader, mqttMsgIdVariableHeader);
  764 + }
  765 +
  766 + private boolean checkConnected(ChannelHandlerContext ctx, TCPMessage msg) {
  767 + if (deviceSessionCtx.isConnected()) {
  768 + return true;
  769 + } else {
  770 + log.info("[{}] Closing current session due to invalid msg order: {}", sessionId, msg);
  771 + return false;
  772 + }
  773 + }
  774 +
  775 +
  776 +
  777 + @Override
  778 + public void operationComplete(Future<? super Void> future) throws Exception {
  779 + log.trace("[{}] Channel closed!", sessionId);
  780 + doDisconnect();
  781 + }
  782 +
  783 + public void doDisconnect() {
  784 + if (deviceSessionCtx.isConnected()) {
  785 + log.debug("[{}] Client disconnected!", sessionId);
  786 + transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null);
  787 + transportService.deregisterSession(deviceSessionCtx.getSessionInfo());
  788 + deviceSessionCtx.setDisconnected();
  789 + }
  790 + deviceSessionCtx.release();
  791 + }
  792 +
  793 +
  794 + private void onValidateDeviceResponse(ValidateDeviceCredentialsResponse msg, ChannelHandlerContext ctx) {
  795 + if (!msg.hasDeviceInfo()) {
  796 + context.onAuthFailure(address);
  797 + ctx.writeAndFlush(createTcpConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED));
  798 + ctx.close();
  799 + } else {
  800 + context.onAuthSuccess(address);
  801 + deviceSessionCtx.setDeviceInfo(msg.getDeviceInfo());
  802 + deviceSessionCtx.setDeviceProfile(msg.getDeviceProfile());
  803 + deviceSessionCtx.setSessionInfo(SessionInfoCreator.create(msg, context, sessionId));
  804 + transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_OPEN, new TransportServiceCallback<Void>() {
  805 + @Override
  806 + public void onSuccess(Void msg) {
  807 + transportService.registerAsyncSession(deviceSessionCtx.getSessionInfo(), TcpTransportHandler.this);
  808 + ctx.writeAndFlush(createTcpConnAckMsg(CONNECTION_ACCEPTED));
  809 + deviceSessionCtx.setConnected(true);
  810 + log.debug("[{}] Client connected!", sessionId);
  811 + 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.
  812 + }
  813 +
  814 + @Override
  815 + public void onError(Throwable e) {
  816 + if (e instanceof TbRateLimitsException) {
  817 + log.trace("[{}] Failed to submit session event: {}", sessionId, e.getMessage());
  818 + } else {
  819 + log.warn("[{}] Failed to submit session event", sessionId, e);
  820 + }
  821 + ctx.writeAndFlush(createTcpConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE));
  822 + ctx.close();
  823 + }
  824 + });
  825 + }
  826 + }
  827 +
  828 + @Override
  829 + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) {
  830 +// log.trace("[{}] Received get attributes response", sessionId);
  831 +// String topicBase = attrReqTopicType.getAttributesResponseTopicBase();
  832 +// TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(attrReqTopicType);
  833 +// try {
  834 +// adaptor.convertToPublish(deviceSessionCtx, response, topicBase).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  835 +// } catch (Exception e) {
  836 +// log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
  837 +// }
  838 + }
  839 +
  840 + @Override
  841 + public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg notification) {
  842 +// log.trace("[{}] Received attributes update notification to device", sessionId);
  843 +// String topic = attrSubTopicType.getAttributesSubTopic();
  844 +// TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(attrSubTopicType);
  845 +// try {
  846 +// adaptor.convertToPublish(deviceSessionCtx, notification, topic).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  847 +// } catch (Exception e) {
  848 +// log.trace("[{}] Failed to convert device attributes update to MQTT msg", sessionId, e);
  849 +// }
  850 + }
  851 +
  852 + @Override
  853 + public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) {
  854 + log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage());
  855 + deviceSessionCtx.getChannel().close();
  856 + }
  857 +
  858 + @Override
  859 + public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
  860 +// log.trace("[{}] Received RPC command to device", sessionId);
  861 +// String baseTopic = rpcSubTopicType.getRpcRequestTopicBase();
  862 +// TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(rpcSubTopicType);
  863 +// try {
  864 +// adaptor.convertToPublish(deviceSessionCtx, rpcRequest, baseTopic).ifPresent(payload -> {
  865 +// int msgId = ((MqttPublishMessage) payload).variableHeader().packetId();
  866 +// if (isAckExpected(payload)) {
  867 +// rpcAwaitingAck.put(msgId, rpcRequest);
  868 +// context.getScheduler().schedule(() -> {
  869 +// TransportProtos.ToDeviceRpcRequestMsg msg = rpcAwaitingAck.remove(msgId);
  870 +// if (msg != null) {
  871 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.TIMEOUT, TransportServiceCallback.EMPTY);
  872 +// }
  873 +// }, Math.max(0, Math.min(deviceSessionCtx.getContext().getTimeout(), rpcRequest.getExpirationTime() - System.currentTimeMillis())), TimeUnit.MILLISECONDS);
  874 +// }
  875 +// var cf = publish(payload, deviceSessionCtx);
  876 +// cf.addListener(result -> {
  877 +// if (result.cause() == null) {
  878 +// if (!isAckExpected(payload)) {
  879 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY);
  880 +// } else if (rpcRequest.getPersisted()) {
  881 +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.SENT, TransportServiceCallback.EMPTY);
  882 +// }
  883 +// } else {
  884 +// // TODO: send error
  885 +// }
  886 +// });
  887 +// });
  888 +// } catch (Exception e) {
  889 +// transportService.process(deviceSessionCtx.getSessionInfo(),
  890 +// TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
  891 +// .setRequestId(rpcRequest.getRequestId()).setError("Failed to convert device RPC command to MQTT msg").build(), TransportServiceCallback.EMPTY);
  892 +// log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e);
  893 +// }
  894 + }
  895 +
  896 + @Override
  897 + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg rpcResponse) {
  898 +// log.trace("[{}] Received RPC response from server", sessionId);
  899 +// String baseTopic = toServerRpcSubTopicType.getRpcResponseTopicBase();
  900 +// TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(toServerRpcSubTopicType);
  901 +// try {
  902 +// adaptor.convertToPublish(deviceSessionCtx, rpcResponse, baseTopic).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  903 +// } catch (Exception e) {
  904 +// log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e);
  905 +// }
  906 + }
  907 +
  908 + private ChannelFuture publish(MqttMessage message, DeviceSessionCtx deviceSessionCtx) {
  909 + return deviceSessionCtx.getChannel().writeAndFlush(message);
  910 + }
  911 +
  912 + private boolean isAckExpected(MqttMessage message) {
  913 + return message.fixedHeader().qosLevel().value() > 0;
  914 + }
  915 +
  916 + @Override
  917 + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
  918 + deviceSessionCtx.onDeviceProfileUpdate(sessionInfo, deviceProfile);
  919 + }
  920 +
  921 + @Override
  922 + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) {
  923 + deviceSessionCtx.onDeviceUpdate(sessionInfo, device, deviceProfileOpt);
  924 + }
  925 +
  926 + @Override
  927 + public void onDeviceDeleted(DeviceId deviceId) {
  928 + context.onAuthFailure(address);
  929 + ChannelHandlerContext ctx = deviceSessionCtx.getChannel();
  930 + ctx.close();
  931 + }
  932 +
  933 +}
... ...
  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.codec.string.StringDecoder;
  25 +import io.netty.handler.codec.string.StringEncoder;
  26 +import io.netty.handler.ssl.SslHandler;
  27 +import org.thingsboard.server.transport.tcp.limits.IpFilter;
  28 +import org.thingsboard.server.transport.tcp.limits.ProxyIpFilter;
  29 +
  30 +/**
  31 + * @author Andrew Shvayka
  32 + */
  33 +public class TcpTransportServerInitializer extends ChannelInitializer<SocketChannel> {
  34 +
  35 + private final TcpTransportContext context;
  36 + private final boolean sslEnabled;
  37 +
  38 + public TcpTransportServerInitializer(TcpTransportContext context, boolean sslEnabled) {
  39 + this.context = context;
  40 + this.sslEnabled = sslEnabled;
  41 + }
  42 +
  43 + @Override
  44 + public void initChannel(SocketChannel ch) {
  45 + ChannelPipeline pipeline = ch.pipeline();
  46 + SslHandler sslHandler = null;
  47 + if (context.isProxyEnabled()) {
  48 + pipeline.addLast("proxy", new HAProxyMessageDecoder());
  49 + pipeline.addLast("ipFilter", new ProxyIpFilter(context));
  50 + } else {
  51 + pipeline.addLast("ipFilter", new IpFilter(context));
  52 + }
  53 + if (sslEnabled && context.getSslHandlerProvider() != null) {
  54 + sslHandler = context.getSslHandlerProvider().getSslHandler();
  55 + pipeline.addLast(sslHandler);
  56 + }
  57 +// pipeline.addLast("decoder", new StringDecoder());
  58 +// pipeline.addLast("encoder", new StringEncoder());
  59 +
  60 + TcpTransportHandler handler = new TcpTransportHandler(context, sslHandler);
  61 +
  62 + pipeline.addLast(handler);
  63 + ch.closeFuture().addListener(handler);
  64 + }
  65 +
  66 +}
... ...
  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("TCP_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.adaptors;
  17 +
  18 +import com.google.common.util.concurrent.Futures;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import com.google.common.util.concurrent.MoreExecutors;
  21 +import com.google.gson.JsonElement;
  22 +import com.google.gson.JsonObject;
  23 +import com.google.gson.JsonParser;
  24 +import com.google.gson.JsonSyntaxException;
  25 +import io.netty.buffer.ByteBuf;
  26 +import io.netty.handler.codec.mqtt.MqttFixedHeader;
  27 +import io.netty.handler.codec.mqtt.MqttMessage;
  28 +import io.netty.handler.codec.mqtt.MqttPublishMessage;
  29 +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
  30 +import lombok.extern.slf4j.Slf4j;
  31 +import org.springframework.beans.factory.annotation.Autowired;
  32 +import org.springframework.stereotype.Component;
  33 +import org.springframework.util.StringUtils;
  34 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  35 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  36 +import org.thingsboard.server.common.transport.adaptor.AdaptorException;
  37 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  38 +import org.thingsboard.server.gen.transport.TransportProtos;
  39 +import org.thingsboard.server.common.yunteng.script.YtScriptInvokeService;
  40 +import org.thingsboard.server.common.yunteng.script.YtScriptType;
  41 +import org.thingsboard.server.transport.tcp.session.DeviceSessionCtx;
  42 +
  43 +import java.nio.charset.Charset;
  44 +import java.nio.charset.StandardCharsets;
  45 +import java.util.*;
  46 +import java.util.concurrent.ExecutionException;
  47 +
  48 +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT;
  49 +
  50 +
  51 +/**
  52 + * @author Andrew Shvayka
  53 + */
  54 +@Component
  55 +@Slf4j
  56 +public class JsonTcpAdaptor implements TcpTransportAdaptor {
  57 +
  58 + protected static final Charset UTF8 = StandardCharsets.UTF_8;
  59 + @Autowired
  60 + private YtScriptInvokeService jsEngine;
  61 + private static final JsonParser parser = new JsonParser();
  62 + @Override
  63 + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(DeviceSessionCtx ctx, String inbound) throws AdaptorException {
  64 + try {
  65 + JsonElement payload = validatePayload(ctx, inbound, false);
  66 + return JsonConverter.convertToTelemetryProto(payload);
  67 + } catch (IllegalStateException | JsonSyntaxException ex) {
  68 + log.debug("Failed to decode post telemetry request", ex);
  69 + throw new AdaptorException(ex);
  70 + } catch (ExecutionException e) {
  71 + throw new RuntimeException(e);
  72 + } catch (InterruptedException e) {
  73 + throw new RuntimeException(e);
  74 + }
  75 + }
  76 +
  77 + @Override
  78 + public UUID getJsScriptEngineFunctionId(String scriptBody, String... argNames) throws ExecutionException, InterruptedException {
  79 + return jsEngine.eval(YtScriptType.TCP_TRANSPORT_SCRIPT, scriptBody, argNames).get();
  80 + }
  81 +
  82 + @Override
  83 + public TransportProtos.PostAttributeMsg convertToPostAttributes(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException {
  84 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
  85 + try {
  86 + return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload));
  87 + } catch (IllegalStateException | JsonSyntaxException ex) {
  88 + log.debug("Failed to decode post attributes request", ex);
  89 + throw new AdaptorException(ex);
  90 + }
  91 + }
  92 +
  93 + @Override
  94 + public TransportProtos.ClaimDeviceMsg convertToClaimDevice(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException {
  95 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), true);
  96 + try {
  97 + return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload);
  98 + } catch (IllegalStateException | JsonSyntaxException ex) {
  99 + log.debug("Failed to decode claim device request", ex);
  100 + throw new AdaptorException(ex);
  101 + }
  102 + }
  103 +
  104 + @Override
  105 + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException {
  106 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
  107 + try {
  108 + return JsonConverter.convertToProvisionRequestMsg(payload);
  109 + } catch (IllegalStateException | JsonSyntaxException ex) {
  110 + throw new AdaptorException(ex);
  111 + }
  112 + }
  113 +
  114 + @Override
  115 + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(DeviceSessionCtx ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  116 + return processGetAttributeRequestMsg(inbound, topicBase);
  117 + }
  118 +
  119 + @Override
  120 + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(DeviceSessionCtx ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  121 + return processToDeviceRpcResponseMsg(inbound, topicBase);
  122 + }
  123 +
  124 + @Override
  125 + public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(DeviceSessionCtx ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  126 + return processToServerRpcRequestMsg(ctx, inbound, topicBase);
  127 + }
  128 +
  129 + @Override
  130 + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, TransportProtos.GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException {
  131 + return processConvertFromAttributeResponseMsg(ctx, responseMsg, topicBase);
  132 + }
  133 +
  134 + @Override
  135 + public Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
  136 + return processConvertFromGatewayAttributeResponseMsg(ctx, deviceName, responseMsg);
  137 + }
  138 +
  139 + @Override
  140 + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, TransportProtos.AttributeUpdateNotificationMsg notificationMsg, String topic) {
  141 + return Optional.of(createMqttPublishMsg(ctx, topic, JsonConverter.toJson(notificationMsg)));
  142 + }
  143 +
  144 + @Override
  145 + public Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) {
  146 + JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, notificationMsg);
  147 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, result));
  148 + }
  149 +
  150 + @Override
  151 + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest, String topicBase) {
  152 + return Optional.of(createMqttPublishMsg(ctx, topicBase + rpcRequest.getRequestId(), JsonConverter.toJson(rpcRequest, false)));
  153 + }
  154 +
  155 + @Override
  156 + public Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
  157 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, JsonConverter.toGatewayJson(deviceName, rpcRequest)));
  158 + }
  159 +
  160 + @Override
  161 + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, TransportProtos.ToServerRpcResponseMsg rpcResponse, String topicBase) {
  162 + return Optional.of(createMqttPublishMsg(ctx, topicBase + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse)));
  163 + }
  164 +
  165 + @Override
  166 + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) {
  167 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse)));
  168 + }
  169 +
  170 + @Override
  171 + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) {
  172 + return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
  173 + }
  174 +
  175 + public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
  176 + String payload = validatePayload(sessionId, payloadData, false);
  177 + try {
  178 + return new JsonParser().parse(payload);
  179 + } catch (JsonSyntaxException ex) {
  180 + log.debug("Payload is in incorrect format: {}", payload);
  181 + throw new AdaptorException(ex);
  182 + }
  183 + }
  184 +
  185 + private TransportProtos.GetAttributeRequestMsg processGetAttributeRequestMsg(MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  186 + String topicName = inbound.variableHeader().topicName();
  187 + try {
  188 + TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
  189 + result.setRequestId(getRequestId(topicName, topicBase));
  190 + String payload = inbound.payload().toString(UTF8);
  191 + JsonElement requestBody = new JsonParser().parse(payload);
  192 + Set<String> clientKeys = toStringSet(requestBody, "clientKeys");
  193 + Set<String> sharedKeys = toStringSet(requestBody, "sharedKeys");
  194 + if (clientKeys != null) {
  195 + result.addAllClientAttributeNames(clientKeys);
  196 + }
  197 + if (sharedKeys != null) {
  198 + result.addAllSharedAttributeNames(sharedKeys);
  199 + }
  200 + return result.build();
  201 + } catch (RuntimeException e) {
  202 + log.debug("Failed to decode get attributes request", e);
  203 + throw new AdaptorException(e);
  204 + }
  205 + }
  206 +
  207 + private TransportProtos.ToDeviceRpcResponseMsg processToDeviceRpcResponseMsg(MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  208 + String topicName = inbound.variableHeader().topicName();
  209 + try {
  210 + int requestId = getRequestId(topicName, topicBase);
  211 + String payload = inbound.payload().toString(UTF8);
  212 + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build();
  213 + } catch (RuntimeException e) {
  214 + log.debug("Failed to decode rpc response", e);
  215 + throw new AdaptorException(e);
  216 + }
  217 + }
  218 +
  219 + private TransportProtos.ToServerRpcRequestMsg processToServerRpcRequestMsg(DeviceSessionCtx ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException {
  220 + String topicName = inbound.variableHeader().topicName();
  221 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
  222 + try {
  223 + int requestId = getRequestId(topicName, topicBase);
  224 + return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId);
  225 + } catch (IllegalStateException | JsonSyntaxException ex) {
  226 + log.debug("Failed to decode to server rpc request", ex);
  227 + throw new AdaptorException(ex);
  228 + }
  229 + }
  230 +
  231 + private Optional<MqttMessage> processConvertFromAttributeResponseMsg(DeviceSessionCtx ctx, TransportProtos.GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException {
  232 + if (!StringUtils.isEmpty(responseMsg.getError())) {
  233 + throw new AdaptorException(responseMsg.getError());
  234 + } else {
  235 + int requestId = responseMsg.getRequestId();
  236 + if (requestId >= 0) {
  237 + return Optional.of(createMqttPublishMsg(ctx,
  238 + topicBase + requestId,
  239 + JsonConverter.toJson(responseMsg)));
  240 + }
  241 + return Optional.empty();
  242 + }
  243 + }
  244 +
  245 + private Optional<MqttMessage> processConvertFromGatewayAttributeResponseMsg(DeviceSessionCtx ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
  246 + if (!StringUtils.isEmpty(responseMsg.getError())) {
  247 + throw new AdaptorException(responseMsg.getError());
  248 + } else {
  249 + JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, responseMsg);
  250 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, result));
  251 + }
  252 + }
  253 +
  254 + protected MqttPublishMessage createMqttPublishMsg(DeviceSessionCtx ctx, String topic, JsonElement json) {
  255 + MqttFixedHeader mqttFixedHeader = null;
  256 +// new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
  257 + MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
  258 + ByteBuf payload = ALLOCATOR.buffer();
  259 + payload.writeBytes(json.toString().getBytes(UTF8));
  260 + return new MqttPublishMessage(mqttFixedHeader, header, payload);
  261 + }
  262 +
  263 + private Set<String> toStringSet(JsonElement requestBody, String name) {
  264 + JsonElement element = requestBody.getAsJsonObject().get(name);
  265 + if (element != null) {
  266 + return new HashSet<>(Arrays.asList(element.getAsString().split(",")));
  267 + } else {
  268 + return null;
  269 + }
  270 + }
  271 +
  272 + private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException {
  273 + String payload = payloadData.toString(UTF8);
  274 + if (payload == null) {
  275 + log.debug("[{}] Payload is empty!", sessionId);
  276 + if (!isEmptyPayloadAllowed) {
  277 + throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
  278 + }
  279 + }
  280 + return payload;
  281 + }
  282 +
  283 + private JsonElement validatePayload(DeviceSessionCtx session, String payload, boolean isEmptyPayloadAllowed) throws AdaptorException, ExecutionException, InterruptedException {
  284 + if (payload == null) {
  285 + log.debug("[{}] Payload is empty!", session.getSessionId());
  286 + if (!isEmptyPayloadAllowed) {
  287 + throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
  288 + }
  289 + }
  290 +// jsEngine.invokeFunction();
  291 +// new JsonParser().parse(payload)
  292 +// return payload;
  293 + ListenableFuture<JsonElement> result = Futures.transformAsync(jsEngine.invokeFunction(session.getScriptId(), payload),
  294 + o -> {
  295 + try {
  296 + return Futures.immediateFuture(parser.parse(o.toString()));
  297 + } catch (Exception e) {
  298 + return Futures.immediateFailedFuture(e);
  299 + }
  300 + }, MoreExecutors.directExecutor());
  301 +
  302 + return result.get();
  303 + }
  304 +
  305 + private int getRequestId(String topicName, String topic) {
  306 + return Integer.parseInt(topicName.substring(topic.length()));
  307 + }
  308 +
  309 +}
... ...
  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.DeviceSessionCtx;
  41 +
  42 +import java.util.Optional;
  43 +import java.util.UUID;
  44 +import java.util.concurrent.ExecutionException;
  45 +
  46 +/**
  47 + * @author Andrew Shvayka
  48 + */
  49 +public interface TcpTransportAdaptor {
  50 +
  51 + ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
  52 +
  53 + PostTelemetryMsg convertToPostTelemetry(DeviceSessionCtx ctx, String inbound) throws AdaptorException;
  54 +
  55 + UUID getJsScriptEngineFunctionId(String scriptBody, String... argNames) throws ExecutionException, InterruptedException;
  56 + PostAttributeMsg convertToPostAttributes(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException;
  57 +
  58 + GetAttributeRequestMsg convertToGetAttributes(DeviceSessionCtx ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException;
  59 +
  60 + ToDeviceRpcResponseMsg convertToDeviceRpcResponse(DeviceSessionCtx ctx, MqttPublishMessage mqttMsg, String topicBase) throws AdaptorException;
  61 +
  62 + ToServerRpcRequestMsg convertToServerRpcRequest(DeviceSessionCtx ctx, MqttPublishMessage mqttMsg, String topicBase) throws AdaptorException;
  63 +
  64 + ClaimDeviceMsg convertToClaimDevice(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException;
  65 +
  66 + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException;
  67 +
  68 + Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, GetAttributeResponseMsg responseMsg) throws AdaptorException;
  69 +
  70 + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, AttributeUpdateNotificationMsg notificationMsg, String topic) throws AdaptorException;
  71 +
  72 + Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException;
  73 +
  74 + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, ToDeviceRpcRequestMsg rpcRequest, String topicBase) throws AdaptorException;
  75 +
  76 + Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException;
  77 +
  78 + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, ToServerRpcResponseMsg rpcResponse, String topicBase) throws AdaptorException;
  79 +
  80 + ProvisionDeviceRequestMsg convertToProvisionRequestMsg(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException;
  81 +
  82 + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException;
  83 +
  84 + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException;
  85 +
  86 + default MqttPublishMessage createMqttPublishMsg(DeviceSessionCtx ctx, String topic, byte[] payloadInBytes) {
  87 + MqttFixedHeader mqttFixedHeader =null;
  88 +// new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
  89 + MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
  90 + ByteBuf payload = ALLOCATOR.buffer();
  91 + payload.writeBytes(payloadInBytes);
  92 + return new MqttPublishMessage(mqttFixedHeader, header, payload);
  93 + }
  94 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.tcp.limits;
  17 +
  18 +import io.netty.channel.ChannelHandlerContext;
  19 +import io.netty.handler.ipfilter.AbstractRemoteAddressFilter;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.thingsboard.server.transport.tcp.TcpTransportContext;
  22 +import org.thingsboard.server.transport.tcp.TcpTransportService;
  23 +
  24 +import java.net.InetSocketAddress;
  25 +
  26 +@Slf4j
  27 +public class IpFilter extends AbstractRemoteAddressFilter<InetSocketAddress> {
  28 +
  29 + private TcpTransportContext context;
  30 +
  31 + public IpFilter(TcpTransportContext context) {
  32 + this.context = context;
  33 + }
  34 +
  35 + @Override
  36 + protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception {
  37 + log.trace("[{}] Received msg: {}", ctx.channel().id(), remoteAddress);
  38 + if(context.checkAddress(remoteAddress)){
  39 + log.trace("[{}] Setting address: {}", ctx.channel().id(), remoteAddress);
  40 + ctx.channel().attr(TcpTransportService.ADDRESS).set(remoteAddress);
  41 + return true;
  42 + } else {
  43 + return false;
  44 + }
  45 + }
  46 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.tcp.limits;
  17 +
  18 +import io.netty.channel.ChannelHandler;
  19 +import io.netty.channel.ChannelHandlerContext;
  20 +import io.netty.channel.ChannelInboundHandlerAdapter;
  21 +import io.netty.handler.codec.haproxy.HAProxyMessage;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.thingsboard.server.transport.tcp.TcpTransportContext;
  24 +import org.thingsboard.server.transport.tcp.TcpTransportService;
  25 +
  26 +import java.net.InetSocketAddress;
  27 +
  28 +@Slf4j
  29 +public class ProxyIpFilter extends ChannelInboundHandlerAdapter {
  30 +
  31 +
  32 + private TcpTransportContext context;
  33 +
  34 + public ProxyIpFilter(TcpTransportContext context) {
  35 + this.context = context;
  36 + }
  37 +
  38 + @Override
  39 + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  40 + log.trace("[{}] Received msg: {}", ctx.channel().id(), msg);
  41 + if (msg instanceof HAProxyMessage) {
  42 + HAProxyMessage proxyMsg = (HAProxyMessage) msg;
  43 + if (proxyMsg.sourceAddress() != null && proxyMsg.sourcePort() > 0) {
  44 + InetSocketAddress address = new InetSocketAddress(proxyMsg.sourceAddress(), proxyMsg.sourcePort());
  45 + if (!context.checkAddress(address)) {
  46 + closeChannel(ctx);
  47 + } else {
  48 + log.trace("[{}] Setting address: {}", ctx.channel().id(), address);
  49 + ctx.channel().attr(TcpTransportService.ADDRESS).set(address);
  50 + // We no longer need this channel in the pipeline. Similar to HAProxyMessageDecoder
  51 + ctx.pipeline().remove(this);
  52 + }
  53 + } else {
  54 + log.trace("Received local health-check connection message: {}", proxyMsg);
  55 + closeChannel(ctx);
  56 + }
  57 + }
  58 + }
  59 +
  60 + private void closeChannel(ChannelHandlerContext ctx) {
  61 + while (ctx.pipeline().last() != this) {
  62 + ChannelHandler handler = ctx.pipeline().removeLast();
  63 + if (handler instanceof ChannelInboundHandlerAdapter) {
  64 + try {
  65 + ((ChannelInboundHandlerAdapter) handler).channelUnregistered(ctx);
  66 + } catch (Exception e) {
  67 + log.error("Failed to unregister channel: [{}]", ctx, e);
  68 + }
  69 + }
  70 +
  71 + }
  72 + ctx.pipeline().remove(this);
  73 + ctx.close();
  74 + }
  75 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + * <p>
  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 + * <p>
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + * <p>
  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.ChannelHandlerContext;
  19 +import io.netty.util.ReferenceCountUtil;
  20 +import lombok.Getter;
  21 +import lombok.Setter;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.thingsboard.server.common.data.DeviceProfile;
  24 +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
  25 +import org.thingsboard.server.common.data.device.profile.YtTcpDeviceProfileTransportConfiguration;
  26 +import org.thingsboard.server.common.data.yunteng.enums.TcpDataTypeEnum;
  27 +import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
  28 +import org.thingsboard.server.gen.transport.TransportProtos;
  29 +import org.thingsboard.server.transport.tcp.TcpTransportContext;
  30 +import org.thingsboard.server.transport.tcp.adaptors.TcpTransportAdaptor;
  31 +import org.thingsboard.server.transport.tcp.util.ByteUtils;
  32 +
  33 +import java.util.UUID;
  34 +import java.util.concurrent.ConcurrentLinkedQueue;
  35 +import java.util.concurrent.ExecutionException;
  36 +import java.util.concurrent.atomic.AtomicInteger;
  37 +import java.util.concurrent.locks.Lock;
  38 +import java.util.concurrent.locks.ReentrantLock;
  39 +import java.util.function.Consumer;
  40 +
  41 +/**
  42 + * @author Andrew Shvayka
  43 + */
  44 +@Slf4j
  45 +public class DeviceSessionCtx extends DeviceAwareSessionContext {
  46 +
  47 + @Getter
  48 + @Setter
  49 + private ChannelHandlerContext channel;
  50 +
  51 + @Getter
  52 + private final TcpTransportContext context;
  53 +
  54 + private final AtomicInteger msgIdSeq = new AtomicInteger(0);
  55 +
  56 + private final ConcurrentLinkedQueue<TCPMessage> msgQueue = new ConcurrentLinkedQueue<>();
  57 +
  58 + @Getter
  59 + private final Lock msgQueueProcessorLock = new ReentrantLock();
  60 +
  61 + private final AtomicInteger msgQueueSize = new AtomicInteger(0);
  62 +
  63 + @Getter
  64 + @Setter
  65 + private boolean provisionOnly = false;
  66 +
  67 + // private volatile TcpTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
  68 +// private volatile TcpTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
  69 + @Getter
  70 + private volatile TcpDataTypeEnum payloadType = TcpDataTypeEnum.HEX;
  71 + @Getter
  72 + private volatile byte[] pingText ;
  73 + // private volatile Descriptors.Descriptor attributesDynamicMessageDescriptor;
  74 +// private volatile Descriptors.Descriptor telemetryDynamicMessageDescriptor;
  75 +// private volatile Descriptors.Descriptor rpcResponseDynamicMessageDescriptor;
  76 +// private volatile DynamicMessage.Builder rpcRequestDynamicMessageBuilder;
  77 + private volatile TcpTransportAdaptor adaptor;
  78 + @Getter
  79 + private UUID scriptId;
  80 +
  81 + public DeviceSessionCtx(UUID sessionId, TcpTransportContext context) {
  82 + super(sessionId);
  83 + this.context = context;
  84 + this.adaptor = context.getJsonTcpAdaptor();
  85 + }
  86 +
  87 + public int nextMsgId() {
  88 + return msgIdSeq.incrementAndGet();
  89 + }
  90 +
  91 +
  92 + public TcpTransportAdaptor getPayloadAdaptor() {
  93 + return adaptor;
  94 + }
  95 +
  96 +
  97 +
  98 +
  99 + @Override
  100 + public void setDeviceProfile(DeviceProfile deviceProfile) {
  101 + super.setDeviceProfile(deviceProfile);
  102 + updateDeviceSessionConfiguration(deviceProfile);
  103 + }
  104 +
  105 + @Override
  106 + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
  107 + super.onDeviceProfileUpdate(sessionInfo, deviceProfile);
  108 + updateDeviceSessionConfiguration(deviceProfile);
  109 + }
  110 +
  111 + private void updateDeviceSessionConfiguration(DeviceProfile deviceProfile){
  112 + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
  113 +
  114 + YtTcpDeviceProfileTransportConfiguration tcpConfiguration = (YtTcpDeviceProfileTransportConfiguration) transportConfiguration;
  115 + payloadType = tcpConfiguration.getDataFormat();
  116 + if (TcpDataTypeEnum.ASCII.equals(payloadType)) {
  117 + payloadType = TcpDataTypeEnum.ASCII;
  118 + } else {
  119 + payloadType = TcpDataTypeEnum.HEX;
  120 + }
  121 + this.pingText = ByteUtils.getBytes(tcpConfiguration.getPingText(),ByteUtils.UTF_8);
  122 + String scriptBody = tcpConfiguration.getScriptText();
  123 + try {
  124 + this.scriptId = this.adaptor.getJsScriptEngineFunctionId(scriptBody);
  125 + } catch (ExecutionException e) {
  126 + log.warn("设备配置【{}】的脚本【{}】解析异常",deviceProfile.getSearchText(),scriptBody);
  127 + throw new RuntimeException(e);
  128 + } catch (InterruptedException e) {
  129 + throw new RuntimeException(e);
  130 + }
  131 + }
  132 +
  133 +
  134 +
  135 +
  136 +
  137 +
  138 + public void addToQueue(TCPMessage msg) {
  139 + msgQueueSize.incrementAndGet();
  140 + ReferenceCountUtil.retain(msg);
  141 + msgQueue.add(msg);
  142 + }
  143 +
  144 + public void tryProcessQueuedMsgs(Consumer<TCPMessage> msgProcessor) {
  145 + while (!msgQueue.isEmpty()) {
  146 + if (msgQueueProcessorLock.tryLock()) {
  147 + try {
  148 + TCPMessage msg;
  149 + while ((msg = msgQueue.poll()) != null) {
  150 + try {
  151 + msgQueueSize.decrementAndGet();
  152 + msgProcessor.accept(msg);
  153 + } finally {
  154 + ReferenceCountUtil.safeRelease(msg);
  155 + }
  156 + }
  157 + } finally {
  158 + msgQueueProcessorLock.unlock();
  159 + }
  160 + } else {
  161 + return;
  162 + }
  163 + }
  164 + }
  165 +
  166 + public int getMsgQueueSize() {
  167 + return msgQueueSize.get();
  168 + }
  169 +
  170 + public void release() {
  171 + if (!msgQueue.isEmpty()) {
  172 + log.warn("doDisconnect for device {} but unprocessed messages {} left in the msg queue", getDeviceId(), msgQueue.size());
  173 + msgQueue.forEach(ReferenceCountUtil::safeRelease);
  174 + msgQueue.clear();
  175 + }
  176 + }
  177 +
  178 +
  179 +}
... ...
  1 +package org.thingsboard.server.transport.tcp.session;
  2 +
  3 +import io.netty.handler.codec.mqtt.MqttMessageType;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +import lombok.ToString;
  7 +
  8 +import java.io.Serializable;
  9 +
  10 +@Data
  11 +public class TCPMessage implements Serializable {
  12 + private String requestId;
  13 + private MqttMessageType messageType;
  14 +
  15 + private byte[] message;
  16 + private boolean hex;
  17 +
  18 + private String deviceId;
  19 +
  20 + /**
  21 + * TCP 消息
  22 + * @param sn 设备sn
  23 + * @param message 消息体
  24 + * @param hex 是否是16进制
  25 + */
  26 + public TCPMessage(MqttMessageType messageType, String deviceId, byte[] message, boolean hex){
  27 + this.messageType = messageType;
  28 + this.deviceId = deviceId;
  29 + this.message = message;
  30 + this.hex = hex;
  31 + }
  32 +
  33 + public TCPMessage(MqttMessageType messageType, byte[] message){
  34 + this.messageType = messageType;
  35 + this.message = message;
  36 + }
  37 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.tcp.util;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class AlwaysTrueTopicFilter implements TcpTopicFilter {
  22 +
  23 + @Override
  24 + public boolean filter(String topic) {
  25 + return true;
  26 + }
  27 +}
... ...
  1 +package org.thingsboard.server.transport.tcp.util;
  2 +
  3 +import io.netty.buffer.ByteBuf;
  4 +import org.apache.commons.lang3.ArrayUtils;
  5 +
  6 +import java.io.*;
  7 +import java.nio.charset.Charset;
  8 +
  9 +/**
  10 + * @Description :数据类型转换工具类
  11 + * 二进制的最高位是符号位,0表示正,1表示负。
  12 + * 负数采用二进制的补码表示,10进制转为二进制得到的是源码,将源码按位取反得到的是反码,反码加1得到补码
  13 + *
  14 + *
  15 + * *¥¥¥【基本数据类型】¥¥¥¥¥¥¥¥¥¥¥¥
  16 + 1/8:boolean【1 位】
  17 + 3/8:byte 【1个字节,8 位】
  18 + 3/8:short 【2个字节,16位】
  19 + 4/8:int 【4个字节,32位】
  20 + 5/8:long 【8个字节,64位】
  21 + 6/8:float 【4个字节,32位】
  22 + 7/8:double 【8个字节,64位】
  23 + 8/8:chart 【2个字节,16位】
  24 + *
  25 + *
  26 + *
  27 + * *¥¥¥【位运算】¥¥¥¥¥¥¥¥¥¥¥¥
  28 + 左移【<< n】:向左移动,左边高位舍弃,右边的低位补0,例如:2左移3位,结果为16
  29 + 右移【>>n】:向右移动,右边的舍弃掉,左边补的值取决于原来最高位,原来是1就补1,原来是0就补0
  30 + 无符号右移【>>>n】向右移动,右边的舍弃掉,左边补0。
  31 + *
  32 + *
  33 + *
  34 + * 1.其它地方抛出异常,交由控制层统一处理
  35 + * 2.服务层注意持久化的事务管理
  36 + * @Author: junlianglee
  37 + * @Date Created in 2019/12/11.
  38 + * @Modified by Administrator on 2019/12/11.
  39 + */
  40 +public class ByteUtils {
  41 + public static final char[] ascii = "0123456789ABCDEF".toCharArray();
  42 + private static char[] HEX_VOCABLE = {'0', '1', '2', '3', '4', '5', '6', '7',
  43 + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
  44 +
  45 + public static final String UTF_8 = "UTF-8";
  46 + /**
  47 + * 将short整型数值转换为字节数组
  48 + *
  49 + * @param data
  50 + * @return
  51 + */
  52 + public static byte[] getBytes(short data) {
  53 + byte[] bytes = new byte[2];
  54 + bytes[0] = (byte) ((data & 0xff00) >> 8);//1个十六进制位匹配4个二进制位
  55 + bytes[1] = (byte) (data & 0xff);
  56 + return bytes;
  57 + }
  58 +
  59 + /**
  60 + * 将字符转换为字节数组
  61 + *
  62 + * @param data
  63 + * @return
  64 + */
  65 + public static byte[] getBytes(char data) {
  66 + byte[] bytes = new byte[2];
  67 + bytes[0] = (byte) (data >> 8);
  68 + bytes[1] = (byte) (data);
  69 + return bytes;
  70 + }
  71 +
  72 + /**
  73 + * 将布尔值转换为字节数组
  74 + *
  75 + * @param data
  76 + * @return
  77 + */
  78 + public static byte[] getBytes(boolean data) {
  79 + byte[] bytes = new byte[1];
  80 + bytes[0] = (byte) (data ? 1 : 0);
  81 + return bytes;
  82 + }
  83 +
  84 + /**
  85 + * 将整型数值转换为字节数组
  86 + *
  87 + * @param data
  88 + * @return
  89 + */
  90 + public static byte[] getBytes(int data) {
  91 + byte[] bytes = new byte[4];
  92 + bytes[0] = (byte) ((data & 0xff000000) >> 24);
  93 + bytes[1] = (byte) ((data & 0xff0000) >> 16);
  94 + bytes[2] = (byte) ((data & 0xff00) >> 8);
  95 + bytes[3] = (byte) (data & 0xff);
  96 + return bytes;
  97 + }
  98 +
  99 + /**
  100 + * 将long整型数值转换为字节数组
  101 + *
  102 + * @param data
  103 + * @return
  104 + */
  105 + public static byte[] getBytes(long data) {
  106 + byte[] bytes = new byte[8];
  107 + bytes[0] = (byte) ((data >> 56) & 0xff);
  108 + bytes[1] = (byte) ((data >> 48) & 0xff);
  109 + bytes[2] = (byte) ((data >> 40) & 0xff);
  110 + bytes[3] = (byte) ((data >> 32) & 0xff);
  111 + bytes[4] = (byte) ((data >> 24) & 0xff);
  112 + bytes[5] = (byte) ((data >> 16) & 0xff);
  113 + bytes[6] = (byte) ((data >> 8) & 0xff);
  114 + bytes[7] = (byte) (data & 0xff);
  115 + return bytes;
  116 + }
  117 +
  118 + /**
  119 + * 将float型数值转换为字节数组
  120 + *
  121 + * @param data
  122 + * @return
  123 + */
  124 + public static byte[] getBytes(float data) {
  125 + int intBits = Float.floatToIntBits(data);
  126 + return getBytes(intBits);
  127 + }
  128 +
  129 + /**
  130 + * 将double型数值转换为字节数组
  131 + *
  132 + * @param data
  133 + * @return
  134 + */
  135 + public static byte[] getBytes(double data) {
  136 + long intBits = Double.doubleToLongBits(data);
  137 + return getBytes(intBits);
  138 + }
  139 +
  140 + /**
  141 + * 将字符串按照charsetName编码格式的字节数组
  142 + *
  143 + * @param data
  144 + * 字符串
  145 + * @param charsetName
  146 + * 编码格式
  147 + * @return
  148 + */
  149 + public static byte[] getBytes(String data, String charsetName) {
  150 + Charset charset = Charset.forName(charsetName);
  151 + return data.getBytes(charset);
  152 + }
  153 +
  154 +
  155 +
  156 +
  157 +
  158 + /**
  159 + * 将字节数组前8字节转换为long整型数值
  160 + *
  161 + * @param bytes
  162 + * @return
  163 + */
  164 + public static long getLong(byte[] bytes) {
  165 + return (0xff00000000000000L & ((long) bytes[0] << 56)
  166 + | (0xff000000000000L & ((long) bytes[1] << 48))
  167 + | (0xff0000000000L & ((long) bytes[2] << 40))
  168 + | (0xff00000000L & ((long) bytes[3] << 32))
  169 + | (0xff000000L & ((long) bytes[4] << 24))
  170 + | (0xff0000L & ((long) bytes[5] << 16))
  171 + | (0xff00L & ((long) bytes[6] << 8))
  172 + | (0xffL & (long) bytes[7]));
  173 + }
  174 +
  175 +
  176 +
  177 + /**
  178 + * 将charsetName编码格式的字节数组转换为字符串
  179 + *
  180 + * @param bytes
  181 + * @param charsetName
  182 + * @return
  183 + */
  184 + public static String getString(byte[] bytes, String charsetName) {
  185 + return new String(bytes, Charset.forName(charsetName));
  186 + }
  187 +
  188 +
  189 + public static byte[] hexStr2Bytes(String hexString) {
  190 + if (hexString == null || hexString.equals("")) {
  191 + return null;
  192 + }
  193 + // toUpperCase将字符串中的所有字符转换为大写
  194 + hexString = hexString.toUpperCase();
  195 + int length = hexString.length() / 2;// 2个16进制字符表示一个字节,所以字节数组长度是字符串长度除以2
  196 + // toCharArray将此字符串转换为一个新的字符数组。
  197 + char[] hexChars = hexString.toCharArray();
  198 + byte[] d = new byte[length];
  199 + for (int i = 0; i < length; i++) {
  200 + int pos = i * 2;
  201 + d[i] = (byte) (charToByte(hexChars[pos]) << 4
  202 + | charToByte(hexChars[pos + 1]));
  203 + }
  204 + return d;
  205 + }
  206 +
  207 + // charToByte返回在指定字符的第一个发生的字符串中的索引,即返回匹配字符
  208 + private static byte charToByte(char c) {
  209 + return (byte) "0123456789ABCDEF".indexOf(c);
  210 + }
  211 + /**
  212 + * 将16进制字符串转换为字节数组
  213 + *
  214 + * @param hex
  215 + * @return
  216 + */
  217 + public static byte[] hexToBytes(String hex) {
  218 + if (hex.length() % 2 != 0)
  219 + throw new IllegalArgumentException(
  220 + "input string should be any multiple of 2!");
  221 + hex.toUpperCase();
  222 +
  223 + byte[] byteBuffer = new byte[hex.length() / 2];
  224 +
  225 + byte padding = 0x00;
  226 + boolean paddingTurning = false;
  227 + for (int i = 0; i < hex.length(); i++) {
  228 + if (paddingTurning) {
  229 + char c = hex.charAt(i);
  230 + int index = indexOf(hex, c);
  231 + padding = (byte) ((padding << 4) | index);
  232 + byteBuffer[i / 2] = padding;
  233 + padding = 0x00;
  234 + paddingTurning = false;
  235 + } else {
  236 + char c = hex.charAt(i);
  237 + int index = indexOf(hex, c);
  238 + padding = (byte) (padding | index);
  239 + paddingTurning = true;
  240 + }
  241 +
  242 + }
  243 + return byteBuffer;
  244 + }
  245 +
  246 + private static int indexOf(String input, char c) {
  247 + int index = ArrayUtils.indexOf(HEX_VOCABLE, c);
  248 +
  249 + if (index < 0) {
  250 + throw new IllegalArgumentException("err input:" + input);
  251 + }
  252 + return index;
  253 +
  254 + }
  255 + /**
  256 + * 字节数组转16进制字符串
  257 + *
  258 + * @param bs
  259 + * @return
  260 + */
  261 + public static String bytesToHex(byte[] bs) {
  262 + StringBuilder sb = new StringBuilder();
  263 + for (byte b : bs) {
  264 + int high = (b >> 4) & 0x0f;
  265 + int low = b & 0x0f;
  266 + sb.append(HEX_VOCABLE[high]);
  267 + sb.append(HEX_VOCABLE[low]);
  268 + }
  269 + return sb.toString();
  270 + }
  271 +
  272 + /**
  273 + * 字节数组取前len个字节转16进制字符串
  274 + *
  275 + * @param bs
  276 + * @param len
  277 + * @return
  278 + */
  279 + public static String bytesToHex(byte[] bs, int len) {
  280 + StringBuilder sb = new StringBuilder();
  281 + for (int i = 0; i < len; i++) {
  282 + byte b = bs[i];
  283 + int high = (b >> 4) & 0x0f;
  284 + int low = b & 0x0f;
  285 + sb.append(HEX_VOCABLE[high]);
  286 + sb.append(HEX_VOCABLE[low]);
  287 + }
  288 + return sb.toString();
  289 + }
  290 + /**
  291 + * 字节数组偏移offset长度之后的取len个字节转16进制字符串
  292 + *
  293 + * @param bs
  294 + * @param offset
  295 + * @param len
  296 + * @return
  297 + */
  298 + public static String bytesToHex(byte[] bs, int offset, int len) {
  299 + StringBuilder sb = new StringBuilder();
  300 + for (int i = 0; i < len; i++) {
  301 + byte b = bs[offset + i];
  302 + int high = (b >> 4) & 0x0f;
  303 + int low = b & 0x0f;
  304 + sb.append(HEX_VOCABLE[high]);
  305 + sb.append(HEX_VOCABLE[low]);
  306 + }
  307 + return sb.toString();
  308 + }
  309 + /**
  310 + * 字节数组转16进制字符串
  311 + *
  312 + * @param bs
  313 + * @return
  314 + */
  315 + public static String byteToHex(byte bs) {
  316 + StringBuilder sb = new StringBuilder();
  317 + int high = (bs >> 4) & 0x0f;
  318 + int low = bs & 0x0f;
  319 + sb.append(HEX_VOCABLE[high]);
  320 + sb.append(HEX_VOCABLE[low]);
  321 + return sb.toString();
  322 + }
  323 + /**
  324 + * 将字节数组取反
  325 + *
  326 + * @param src
  327 + * @return
  328 + */
  329 + public static String negate(byte[] src) {
  330 + if (src == null || src.length == 0) {
  331 + return null;
  332 + }
  333 + byte[] temp = new byte[2 * src.length];
  334 + for (int i = 0; i < src.length; i++) {
  335 + byte tmp = (byte) (0xFF ^ src[i]);
  336 + temp[i * 2] = (byte) ((tmp >> 4) & 0x0f);
  337 + temp[i * 2 + 1] = (byte) (tmp & 0x0f);
  338 + }
  339 + StringBuffer res = new StringBuffer();
  340 + for (int i = 0; i < temp.length; i++) {
  341 + res.append(ascii[temp[i]]);
  342 + }
  343 + return res.toString();
  344 + }
  345 +
  346 +
  347 +
  348 + /**
  349 + * 数组合并
  350 + * @param before
  351 + * @param end
  352 + * @return
  353 + */
  354 + public static byte[] merge(byte[]before,byte[]end){
  355 + byte[] result= new byte[before.length+end.length];
  356 + System.arraycopy(before,0,result,0,before.length);
  357 + System.arraycopy(end,0,result,before.length,end.length);
  358 + return result;
  359 + }
  360 +
  361 + public static byte[] subBytes(byte[]before,Integer start,Integer length){
  362 + byte[] result= new byte[length];
  363 + System.arraycopy(before,start,result,0,length);
  364 + return result;
  365 + }
  366 +
  367 + /**
  368 + * 比较字节数组是否相同
  369 + *
  370 + * @param a
  371 + * @param b
  372 + * @return
  373 + */
  374 + public static boolean compareBytes(byte[] a, byte[] b) {
  375 + if (a == null || a.length == 0 || b == null || b.length == 0
  376 + || a.length != b.length) {
  377 + return false;
  378 + }
  379 + if (a.length == b.length) {
  380 + for (int i = 0; i < a.length; i++) {
  381 + if (a[i] != b[i]) {
  382 + return false;
  383 + }
  384 + }
  385 + } else {
  386 + return false;
  387 + }
  388 + return true;
  389 + }
  390 + /**
  391 + * 只比对指定长度byte
  392 + *
  393 + * @param a
  394 + * @param b
  395 + * @param len
  396 + * @return
  397 + */
  398 + public static boolean compareBytes(byte[] a, byte[] b, int len) {
  399 + if (a == null || a.length == 0 || b == null || b.length == 0
  400 + || a.length < len || b.length < len) {
  401 + return false;
  402 + }
  403 + for (int i = 0; i < len; i++) {
  404 + if (a[i] != b[i]) {
  405 + return false;
  406 + }
  407 + }
  408 + return true;
  409 + }
  410 +
  411 + /**
  412 + * 将字节数组转换为二进制字符串
  413 + *
  414 + * @param items
  415 + * @return
  416 + */
  417 + public static String bytesToBinaryString(byte[] items) {
  418 + if (items == null || items.length == 0) {
  419 + return null;
  420 + }
  421 + StringBuffer buf = new StringBuffer();
  422 + for (byte item : items) {
  423 + buf.append(byteToBinaryString(item));
  424 + }
  425 + return buf.toString();
  426 + }
  427 +
  428 + /**
  429 + * 将字节转换为二进制字符串
  430 + *
  431 + * @param item
  432 + * @return
  433 + */
  434 + public static String byteToBinaryString(byte item) {
  435 + byte a = item;
  436 + StringBuffer buf = new StringBuffer();
  437 + for (int i = 0; i < 8; i++) {
  438 + buf.insert(0, a % 2);
  439 + a = (byte) (a >> 1);
  440 + }
  441 + return buf.toString();
  442 + }
  443 +
  444 + /**
  445 + * 对数组a,b进行异或运算
  446 + *
  447 + * @param a
  448 + * @param b
  449 + * @return
  450 + */
  451 + public static byte[] xor(byte[] a, byte[] b) {
  452 + if (a == null || a.length == 0 || b == null || b.length == 0
  453 + || a.length != b.length) {
  454 + return null;
  455 + }
  456 + byte[] result = new byte[a.length];
  457 + for (int i = 0; i < a.length; i++) {
  458 + result[i] = (byte) (a[i] ^ b[i]);
  459 + }
  460 + return result;
  461 + }
  462 +
  463 + /**
  464 + * 对数组a,b进行异或运算 运算长度len
  465 + *
  466 + * @param a
  467 + * @param b
  468 + * @param len
  469 + * @return
  470 + */
  471 + public static byte[] xor(byte[] a, byte[] b, int len) {
  472 + if (a == null || a.length == 0 || b == null || b.length == 0) {
  473 + return null;
  474 + }
  475 + if (a.length < len || b.length < len) {
  476 + return null;
  477 + }
  478 + byte[] result = new byte[len];
  479 + for (int i = 0; i < len; i++) {
  480 + result[i] = (byte) (a[i] ^ b[i]);
  481 + }
  482 + return result;
  483 + }
  484 + /**
  485 + * 将short整型数值转换为字节数组
  486 + *
  487 + * @param num
  488 + * @return
  489 + */
  490 + public static byte[] shortToBytes(int num) {
  491 + byte[] temp = new byte[2];
  492 + for (int i = 0; i < 2; i++) {
  493 + temp[i] = (byte) ((num >>> (8 - i * 8)) & 0xFF);
  494 + }
  495 + return temp;
  496 + }
  497 +
  498 + /**
  499 + * 将字节数组转为整型
  500 + *
  501 + * @param arr
  502 + * @return
  503 + */
  504 + public static int bytesToShort(byte[] arr) {
  505 + int mask = 0xFF;
  506 + int temp = 0;
  507 + int result = 0;
  508 + for (int i = 0; i < 2; i++) {
  509 + result <<= 8;
  510 + temp = arr[i] & mask;
  511 + result |= temp;
  512 + }
  513 + return result;
  514 + }
  515 +
  516 + /**
  517 + * 将整型数值转换为指定长度的字节数组
  518 + *
  519 + * @param num
  520 + * @return
  521 + */
  522 + public static byte[] intToBytes(int num) {
  523 + byte[] temp = new byte[4];
  524 + for (int i = 0; i < 4; i++) {
  525 + temp[i] = (byte) ((num >>> (24 - i * 8)) & 0xFF);
  526 + }
  527 + return temp;
  528 + }
  529 +
  530 + /**
  531 + * 将整型数值转换为指定长度的字节数组
  532 + *
  533 + * @param src
  534 + * @param len
  535 + * @return
  536 + */
  537 + public static byte[] intToBytes(int src, int len) {
  538 + if (len < 1 || len > 4) {
  539 + return null;
  540 + }
  541 + byte[] temp = new byte[len];
  542 + for (int i = 0; i < len; i++) {
  543 + temp[len - 1 - i] = (byte) ((src >>> (8 * i)) & 0xFF);
  544 + }
  545 + return temp;
  546 + }
  547 +
  548 + /**
  549 + * 将字节数组转换为整型数值
  550 + *
  551 + * @param arr
  552 + * @return
  553 + */
  554 + public static int bytesToInt(byte[] arr) {
  555 + int mask = 0xFF;
  556 + int temp = 0;
  557 + int result = 0;
  558 + for (int i = 0; i < 4; i++) {
  559 + result <<= 8;
  560 + temp = arr[i] & mask;
  561 + result |= temp;
  562 + }
  563 + return result;
  564 + }
  565 +
  566 + /**
  567 + * 将long整型数值转换为字节数组
  568 + *
  569 + * @param num
  570 + * @return
  571 + */
  572 + public static byte[] longToBytes(long num) {
  573 + byte[] temp = new byte[8];
  574 + for (int i = 0; i < 8; i++) {
  575 + temp[i] = (byte) ((num >>> (56 - i * 8)) & 0xFF);
  576 + }
  577 + return temp;
  578 + }
  579 +
  580 + /**
  581 + * 将字节数组转换为long整型数值
  582 + *
  583 + * @param arr
  584 + * @return
  585 + */
  586 + public static long bytesToLong(byte[] arr) {
  587 + int mask = 0xFF;
  588 + int temp = 0;
  589 + long result = 0;
  590 + int len = Math.min(8, arr.length);
  591 + for (int i = 0; i < len; i++) {
  592 + result <<= 8;
  593 + temp = arr[i] & mask;
  594 + result |= temp;
  595 + }
  596 + return result;
  597 + }
  598 +
  599 + /**
  600 + * 将16进制字符转换为字节
  601 + *
  602 + * @param c
  603 + * @return
  604 + */
  605 + public static byte toByte(char c) {
  606 + byte b = (byte) "0123456789ABCDEF".indexOf(c);
  607 + return b;
  608 + }
  609 +
  610 + /**
  611 + * 功能描述:把两个字节的字节数组转化为整型数据,高位补零,例如:<br/>
  612 + * 有字节数组byte[] data = new byte[]{1,2};转换后int数据的字节分布如下:<br/>
  613 + * 00000000 00000000 00000001 00000010,函数返回258
  614 + *
  615 + * @param lenData
  616 + * 需要进行转换的字节数组
  617 + * @return 字节数组所表示整型值的大小
  618 + */
  619 + public static int bytesToIntWhereByteLengthEquals2(byte lenData[]) {
  620 + if (lenData.length != 2) {
  621 + return -1;
  622 + }
  623 + byte fill[] = new byte[]{0, 0};
  624 + byte real[] = new byte[4];
  625 + System.arraycopy(fill, 0, real, 0, 2);
  626 + System.arraycopy(lenData, 0, real, 2, 2);
  627 + int len = byteToInt(real);
  628 + return len;
  629 +
  630 + }
  631 +
  632 + /**
  633 + * 功能描述:将byte数组转化为int类型的数据
  634 + *
  635 + * @param byteVal
  636 + * 需要转化的字节数组
  637 + * @return 字节数组所表示的整型数据
  638 + */
  639 + public static int byteToInt(byte[] byteVal) {
  640 + int result = 0;
  641 + for (int i = 0; i < byteVal.length; i++) {
  642 + int tmpVal = (byteVal[i] << (8 * (3 - i)));
  643 + switch (i) {
  644 + case 0 :
  645 + tmpVal = tmpVal & 0xFF000000;
  646 + break;
  647 + case 1 :
  648 + tmpVal = tmpVal & 0x00FF0000;
  649 + break;
  650 + case 2 :
  651 + tmpVal = tmpVal & 0x0000FF00;
  652 + break;
  653 + case 3 :
  654 + tmpVal = tmpVal & 0x000000FF;
  655 + break;
  656 + }
  657 +
  658 + result = result | tmpVal;
  659 + }
  660 + return result;
  661 + }
  662 + public static byte CheckXORSum(byte[] bData) {
  663 + byte sum = 0x00;
  664 + for (int i = 0; i < bData.length; i++) {
  665 + sum ^= bData[i];
  666 + }
  667 + return sum;
  668 + }
  669 + /**
  670 + * 从offset开始 将后续长度为len的byte字节转为int
  671 + *
  672 + * @param data
  673 + * @param offset
  674 + * @param len
  675 + * @return
  676 + */
  677 + public static int bytesToInt(byte[] data, int offset, int len) {
  678 + int mask = 0xFF;
  679 + int temp = 0;
  680 + int result = 0;
  681 + len = Math.min(len, 4);
  682 + for (int i = 0; i < len; i++) {
  683 + result <<= 8;
  684 + temp = data[offset + i] & mask;
  685 + result |= temp;
  686 + }
  687 + return result;
  688 + }
  689 +
  690 + /**
  691 + * byte字节数组中的字符串的长度
  692 + *
  693 + * @param data
  694 + * @return
  695 + */
  696 + public static int getBytesStringLen(byte[] data) {
  697 + int count = 0;
  698 + for (byte b : data) {
  699 + if (b == 0x00)
  700 + break;
  701 + count++;
  702 + }
  703 + return count;
  704 + }
  705 +
  706 + /**
  707 + * 校验和
  708 + *
  709 + * @param msg
  710 + * 需要计算校验和的byte数组
  711 + * @param length
  712 + * 校验和位数
  713 + * @return 计算出的校验和数组
  714 + */
  715 + public static byte[] SumCheck(byte[] msg, int length) {
  716 + long mSum = 0;
  717 + byte[] mByte = new byte[length];
  718 +
  719 + /** 逐Byte添加位数和 */
  720 + for (byte byteMsg : msg) {
  721 + long mNum = ((long) byteMsg >= 0)
  722 + ? (long) byteMsg
  723 + : ((long) byteMsg + 256);
  724 + mSum += mNum;
  725 + } /** end of for (byte byteMsg : msg) */
  726 +
  727 + /** 位数和转化为Byte数组 */
  728 + for (int liv_Count = 0; liv_Count < length; liv_Count++) {
  729 + mByte[length - liv_Count
  730 + - 1] = (byte) (mSum >> (liv_Count * 8) & 0xff);
  731 + } /** end of for (int liv_Count = 0; liv_Count < length; liv_Count++) */
  732 +
  733 + return mByte;
  734 + }
  735 +
  736 + /**
  737 + * 字节数据转十进制字符(补码-无符号)
  738 + *
  739 + * @param data
  740 + * @return
  741 + */
  742 + public static String getByteToStr(byte[] data) {
  743 + StringBuffer str = new StringBuffer();
  744 + for (int i = 0; i < data.length; i++) {
  745 + if (data[i] < 0) {
  746 + int tem = data[i] + 256;
  747 + str.append(tem + " ");
  748 + } else {
  749 + str.append(data[i] + " ");
  750 + }
  751 + }
  752 + return str.toString();
  753 + }
  754 +
  755 + public static byte[] buf2Bytes(ByteBuf in) {
  756 + return buf2Bytes( in, in.readableBytes()) ;
  757 + }
  758 + public static byte[] buf2Bytes(ByteBuf in,int readable) {
  759 + byte[] dst = new byte[readable];
  760 + in.getBytes(0, dst);
  761 + return dst;
  762 + }
  763 +
  764 + public static byte[] fileBytes(String pathname) throws IOException {
  765 + File filename = new File(pathname);
  766 + BufferedInputStream in = new BufferedInputStream(new FileInputStream(filename));
  767 + ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
  768 + byte[] temp = new byte[1024];
  769 + int size = 0;
  770 + while((size = in.read(temp)) != -1){
  771 + out.write(temp, 0, size);
  772 + }
  773 + in.close();
  774 + out.close();
  775 + byte[] content = out.toByteArray();
  776 + return content;
  777 + }
  778 + /**
  779 + * 单字节高低位互换
  780 + * @param src
  781 + * @return
  782 + */
  783 + public static int revert(int src) {
  784 + int lowByte = (src & 0xFF00) >> 8;
  785 + int highByte = (src & 0x00FF) << 8;
  786 + return lowByte | highByte;
  787 + }
  788 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.tcp.util;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class EqualsTopicFilter implements TcpTopicFilter {
  22 +
  23 + private final String filter;
  24 +
  25 + @Override
  26 + public boolean filter(String topic) {
  27 + return filter.equals(topic);
  28 + }
  29 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.tcp.util;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  20 +
  21 +import java.util.concurrent.ConcurrentHashMap;
  22 +import java.util.concurrent.ConcurrentMap;
  23 +
  24 +@Slf4j
  25 +public class MqttTopicFilterFactory {
  26 +
  27 + private static final ConcurrentMap<String, TcpTopicFilter> filters = new ConcurrentHashMap<>();
  28 + private static final TcpTopicFilter DEFAULT_TELEMETRY_TOPIC_FILTER = toFilter(MqttTopics.DEVICE_TELEMETRY_TOPIC);
  29 + private static final TcpTopicFilter DEFAULT_ATTRIBUTES_TOPIC_FILTER = toFilter(MqttTopics.DEVICE_ATTRIBUTES_TOPIC);
  30 +
  31 + public static TcpTopicFilter toFilter(String topicFilter) {
  32 + if (topicFilter == null || topicFilter.isEmpty()) {
  33 + throw new IllegalArgumentException("Topic filter can't be empty!");
  34 + }
  35 + return filters.computeIfAbsent(topicFilter, filter -> {
  36 + if (filter.equals("#")) {
  37 + return new AlwaysTrueTopicFilter();
  38 + } else if (filter.contains("+") || filter.contains("#")) {
  39 + String regex = filter
  40 + .replace("\\", "\\\\")
  41 + .replace("+", "[^/]+")
  42 + .replace("/#", "($|/.*)");
  43 + log.debug("Converting [{}] to [{}]", filter, regex);
  44 + return new RegexTopicFilter(regex);
  45 + } else {
  46 + return new EqualsTopicFilter(filter);
  47 + }
  48 + });
  49 + }
  50 +
  51 + public static TcpTopicFilter getDefaultTelemetryFilter() {
  52 + return DEFAULT_TELEMETRY_TOPIC_FILTER;
  53 + }
  54 +
  55 + public static TcpTopicFilter getDefaultAttributesFilter() {
  56 + return DEFAULT_ATTRIBUTES_TOPIC_FILTER;
  57 + }
  58 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.tcp.util;
  17 +
  18 +import lombok.Data;
  19 +
  20 +import java.util.regex.Pattern;
  21 +
  22 +@Data
  23 +public class RegexTopicFilter implements TcpTopicFilter {
  24 +
  25 + private final Pattern regex;
  26 +
  27 + public RegexTopicFilter(String regex) {
  28 + this.regex = Pattern.compile(regex);
  29 + }
  30 +
  31 + @Override
  32 + public boolean filter(String topic) {
  33 + return regex.matcher(topic).matches();
  34 + }
  35 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.tcp.util;
  17 +
  18 +public interface TcpTopicFilter {
  19 +
  20 + boolean filter(String topic);
  21 +
  22 +}
... ...
... ... @@ -137,6 +137,11 @@
137 137 <groupId>org.bouncycastle</groupId>
138 138 <artifactId>bcpkix-jdk15on</artifactId>
139 139 </dependency>
  140 +
  141 + <dependency>
  142 + <groupId>org.javadelight</groupId>
  143 + <artifactId>delight-nashorn-sandbox</artifactId>
  144 + </dependency>
140 145 </dependencies>
141 146
142 147 <build>
... ...
  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.common.yunteng.script;
  17 +
  18 +import com.google.common.util.concurrent.FutureCallback;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import com.google.common.util.concurrent.MoreExecutors;
  22 +import delight.nashornsandbox.NashornSandbox;
  23 +import delight.nashornsandbox.NashornSandboxes;
  24 +import lombok.Getter;
  25 +import lombok.extern.slf4j.Slf4j;
  26 +import org.springframework.beans.factory.annotation.Value;
  27 +import org.springframework.scheduling.annotation.Scheduled;
  28 +import org.thingsboard.common.util.ThingsBoardExecutors;
  29 +import org.thingsboard.server.common.yunteng.script.AbstractYtScriptInvokeService;
  30 +import org.thingsboard.server.common.yunteng.script.YtScriptExecutorService;
  31 +import org.thingsboard.server.common.yunteng.script.YtScriptStatCallback;
  32 +import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
  33 +
  34 +import javax.annotation.PostConstruct;
  35 +import javax.annotation.PreDestroy;
  36 +import javax.script.Invocable;
  37 +import javax.script.ScriptEngine;
  38 +import javax.script.ScriptEngineManager;
  39 +import javax.script.ScriptException;
  40 +import java.util.UUID;
  41 +import java.util.concurrent.ExecutionException;
  42 +import java.util.concurrent.ExecutorService;
  43 +import java.util.concurrent.TimeUnit;
  44 +import java.util.concurrent.atomic.AtomicInteger;
  45 +import java.util.concurrent.locks.ReentrantLock;
  46 +
  47 +@Slf4j
  48 +public abstract class AbstractNashornYtScriptInvokeService extends AbstractYtScriptInvokeService {
  49 +
  50 + private NashornSandbox sandbox;
  51 + private ScriptEngine engine;
  52 + private ExecutorService monitorExecutorService;
  53 +
  54 + private final AtomicInteger jsPushedMsgs = new AtomicInteger(0);
  55 + private final AtomicInteger jsInvokeMsgs = new AtomicInteger(0);
  56 + private final AtomicInteger jsEvalMsgs = new AtomicInteger(0);
  57 + private final AtomicInteger jsFailedMsgs = new AtomicInteger(0);
  58 + private final AtomicInteger jsTimeoutMsgs = new AtomicInteger(0);
  59 + private final FutureCallback<UUID> evalCallback = new YtScriptStatCallback<>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs);
  60 + private final FutureCallback<Object> invokeCallback = new YtScriptStatCallback<>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs);
  61 +
  62 + private final ReentrantLock evalLock = new ReentrantLock();
  63 +
  64 + @Getter
  65 + private final YtScriptExecutorService jsExecutor;
  66 +
  67 + @Value("${js.local.max_requests_timeout:0}")
  68 + private long maxRequestsTimeout;
  69 +
  70 + @Value("${js.local.stats.enabled:false}")
  71 + private boolean statsEnabled;
  72 +
  73 + public AbstractNashornYtScriptInvokeService(TbApiUsageClient apiUsageClient, YtScriptExecutorService jsExecutor) {
  74 + super();
  75 + this.jsExecutor = jsExecutor;
  76 + }
  77 +
  78 + @Scheduled(fixedDelayString = "${js.local.stats.print_interval_ms:10000}")
  79 + public void printStats() {
  80 + if (statsEnabled) {
  81 + int pushedMsgs = jsPushedMsgs.getAndSet(0);
  82 + int invokeMsgs = jsInvokeMsgs.getAndSet(0);
  83 + int evalMsgs = jsEvalMsgs.getAndSet(0);
  84 + int failed = jsFailedMsgs.getAndSet(0);
  85 + int timedOut = jsTimeoutMsgs.getAndSet(0);
  86 + if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) {
  87 + log.info("Nashorn JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]",
  88 + pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut);
  89 + }
  90 + }
  91 + }
  92 +
  93 + @PostConstruct
  94 + public void init() {
  95 + super.init(maxRequestsTimeout);
  96 + if (useJsSandbox()) {
  97 + sandbox = NashornSandboxes.create();
  98 + monitorExecutorService = ThingsBoardExecutors.newWorkStealingPool(getMonitorThreadPoolSize(), "nashorn-js-monitor");
  99 + sandbox.setExecutor(monitorExecutorService);
  100 + sandbox.setMaxCPUTime(getMaxCpuTime());
  101 + sandbox.allowNoBraces(false);
  102 + sandbox.allowLoadFunctions(true);
  103 + sandbox.setMaxPreparedStatements(30);
  104 + } else {
  105 + ScriptEngineManager factory = new ScriptEngineManager();
  106 + engine = factory.getEngineByName("nashorn");
  107 + }
  108 + }
  109 +
  110 + @PreDestroy
  111 + public void stop() {
  112 + super.stop();
  113 + if (monitorExecutorService != null) {
  114 + monitorExecutorService.shutdownNow();
  115 + }
  116 + }
  117 +
  118 + protected abstract boolean useJsSandbox();
  119 +
  120 + protected abstract int getMonitorThreadPoolSize();
  121 +
  122 + protected abstract long getMaxCpuTime();
  123 +
  124 + @Override
  125 + protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String jsScript) {
  126 + jsPushedMsgs.incrementAndGet();
  127 + ListenableFuture<UUID> result = jsExecutor.executeAsync(() -> {
  128 + try {
  129 + evalLock.lock();
  130 + try {
  131 + if (useJsSandbox()) {
  132 + sandbox.eval(jsScript);
  133 + } else {
  134 + engine.eval(jsScript);
  135 + }
  136 + } finally {
  137 + evalLock.unlock();
  138 + }
  139 + scriptIdToNameMap.put(scriptId, functionName);
  140 + return scriptId;
  141 + } catch (Exception e) {
  142 + log.debug("Failed to compile JS script: {}", e.getMessage(), e);
  143 + throw new ExecutionException(e);
  144 + }
  145 + });
  146 + if (maxRequestsTimeout > 0) {
  147 + result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
  148 + }
  149 + Futures.addCallback(result, evalCallback, MoreExecutors.directExecutor());
  150 + return result;
  151 + }
  152 +
  153 + @Override
  154 + protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) {
  155 + jsPushedMsgs.incrementAndGet();
  156 + ListenableFuture<Object> result = jsExecutor.executeAsync(() -> {
  157 + try {
  158 + if (useJsSandbox()) {
  159 + return sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
  160 + } else {
  161 + return ((Invocable) engine).invokeFunction(functionName, args);
  162 + }
  163 + } catch (Exception e) {
  164 + onScriptExecutionError(scriptId, e, functionName);
  165 + throw new ExecutionException(e);
  166 + }
  167 + });
  168 +
  169 + if (maxRequestsTimeout > 0) {
  170 + result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
  171 + }
  172 + Futures.addCallback(result, invokeCallback, MoreExecutors.directExecutor());
  173 + return result;
  174 + }
  175 +
  176 + protected void doRelease(UUID scriptId, String functionName) throws ScriptException {
  177 + if (useJsSandbox()) {
  178 + sandbox.eval(functionName + " = undefined;");
  179 + } else {
  180 + engine.eval(functionName + " = undefined;");
  181 + }
  182 + }
  183 +
  184 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.yunteng.script;
  17 +
  18 +import com.google.common.util.concurrent.Futures;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.thingsboard.common.util.ThingsBoardThreadFactory;
  22 +
  23 +import java.util.Map;
  24 +import java.util.UUID;
  25 +import java.util.concurrent.ConcurrentHashMap;
  26 +import java.util.concurrent.Executors;
  27 +import java.util.concurrent.ScheduledExecutorService;
  28 +import java.util.concurrent.atomic.AtomicInteger;
  29 +
  30 +/**
  31 + * Created by ashvayka on 26.09.18.
  32 + */
  33 +@Slf4j
  34 +public abstract class AbstractYtScriptInvokeService implements YtScriptInvokeService {
  35 +
  36 +
  37 +
  38 + protected ScheduledExecutorService timeoutExecutorService;
  39 + protected Map<UUID, String> scriptIdToNameMap = new ConcurrentHashMap<>();
  40 + protected Map<UUID, DisableListInfo> disabledFunctions = new ConcurrentHashMap<>();
  41 +
  42 +
  43 +
  44 + public void init(long maxRequestsTimeout) {
  45 + if (maxRequestsTimeout > 0) {
  46 + timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nashorn-js-timeout"));
  47 + }
  48 + }
  49 +
  50 + public void stop() {
  51 + if (timeoutExecutorService != null) {
  52 + timeoutExecutorService.shutdownNow();
  53 + }
  54 + }
  55 +
  56 + @Override
  57 + public ListenableFuture<UUID> eval(YtScriptType scriptType, String scriptBody, String... argNames) {
  58 + UUID scriptId = UUID.randomUUID();
  59 + String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
  60 + String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
  61 + return doEval(scriptId, functionName, jsScript);
  62 + }
  63 +
  64 + @Override
  65 + public ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args) {
  66 + String functionName = scriptIdToNameMap.get(scriptId);
  67 + if (functionName == null) {
  68 + return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!"));
  69 + }
  70 + if (!isDisabled(scriptId)) {
  71 + return doInvokeFunction(scriptId, functionName, args);
  72 + } else {
  73 + String message = "Script invocation is blocked due to maximum error count "
  74 + + getMaxErrors() + ", scriptId " + scriptId + "!";
  75 + log.warn(message);
  76 + return Futures.immediateFailedFuture(new RuntimeException(message));
  77 + }
  78 + }
  79 +
  80 + @Override
  81 + public ListenableFuture<Void> release(UUID scriptId) {
  82 + String functionName = scriptIdToNameMap.get(scriptId);
  83 + if (functionName != null) {
  84 + try {
  85 + scriptIdToNameMap.remove(scriptId);
  86 + disabledFunctions.remove(scriptId);
  87 + doRelease(scriptId, functionName);
  88 + } catch (Exception e) {
  89 + return Futures.immediateFailedFuture(e);
  90 + }
  91 + }
  92 + return Futures.immediateFuture(null);
  93 + }
  94 +
  95 + protected abstract ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String scriptBody);
  96 +
  97 + protected abstract ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args);
  98 +
  99 + protected abstract void doRelease(UUID scriptId, String functionName) throws Exception;
  100 +
  101 + protected abstract int getMaxErrors();
  102 +
  103 + protected abstract long getMaxBlacklistDuration();
  104 +
  105 + protected void onScriptExecutionError(UUID scriptId, Throwable t, String scriptBody) {
  106 + DisableListInfo disableListInfo = disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo());
  107 + log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}, scriptBody {}",
  108 + disableListInfo.get(), scriptId, t, t.getCause(), scriptBody);
  109 + disableListInfo.incrementAndGet();
  110 + }
  111 +
  112 + private String generateJsScript(YtScriptType scriptType, String functionName, String scriptBody, String... argNames) {
  113 + if (scriptType == YtScriptType.TCP_TRANSPORT_SCRIPT) {
  114 + return YtScriptFactory.generateRuleNodeScript(functionName, scriptBody, argNames);
  115 +
  116 + }
  117 + throw new RuntimeException("No script factory implemented for scriptType: " + scriptType);
  118 + }
  119 +
  120 + private boolean isDisabled(UUID scriptId) {
  121 + DisableListInfo errorCount = disabledFunctions.get(scriptId);
  122 + if (errorCount != null) {
  123 + if (errorCount.getExpirationTime() <= System.currentTimeMillis()) {
  124 + disabledFunctions.remove(scriptId);
  125 + return false;
  126 + } else {
  127 + return errorCount.get() >= getMaxErrors();
  128 + }
  129 + } else {
  130 + return false;
  131 + }
  132 + }
  133 +
  134 + private class DisableListInfo {
  135 + private final AtomicInteger counter;
  136 + private long expirationTime;
  137 +
  138 + private DisableListInfo() {
  139 + this.counter = new AtomicInteger(0);
  140 + }
  141 +
  142 + public int get() {
  143 + return counter.get();
  144 + }
  145 +
  146 + public int incrementAndGet() {
  147 + int result = counter.incrementAndGet();
  148 + expirationTime = System.currentTimeMillis() + getMaxBlacklistDuration();
  149 + return result;
  150 + }
  151 +
  152 + public long getExpirationTime() {
  153 + return expirationTime;
  154 + }
  155 + }
  156 +}
... ...
  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.common.yunteng.script;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Value;
  20 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  21 +import org.springframework.stereotype.Service;
  22 +import org.thingsboard.server.common.yunteng.script.AbstractNashornYtScriptInvokeService;
  23 +import org.thingsboard.server.common.yunteng.script.YtScriptExecutorService;
  24 +import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
  25 +
  26 +import java.util.concurrent.TimeUnit;
  27 +
  28 +@Slf4j
  29 +@ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "local", matchIfMissing = true)
  30 +@Service
  31 +public class
  32 +NashornYtScriptInvokeService extends AbstractNashornYtScriptInvokeService {
  33 +
  34 + @Value("${js.local.use_js_sandbox}")
  35 + private boolean useJsSandbox;
  36 +
  37 + @Value("${js.local.monitor_thread_pool_size}")
  38 + private int monitorThreadPoolSize;
  39 +
  40 + @Value("${js.local.max_cpu_time}")
  41 + private long maxCpuTime;
  42 +
  43 + @Value("${js.local.max_errors}")
  44 + private int maxErrors;
  45 +
  46 + @Value("${js.local.max_black_list_duration_sec:60}")
  47 + private int maxBlackListDurationSec;
  48 +
  49 + public NashornYtScriptInvokeService(TbApiUsageClient apiUsageClient, YtScriptExecutorService jsExecutor) {
  50 + super(apiUsageClient, jsExecutor);
  51 + }
  52 +
  53 + @Override
  54 + protected boolean useJsSandbox() {
  55 + return useJsSandbox;
  56 + }
  57 +
  58 + @Override
  59 + protected int getMonitorThreadPoolSize() {
  60 + return monitorThreadPoolSize;
  61 + }
  62 +
  63 + @Override
  64 + protected long getMaxCpuTime() {
  65 + return maxCpuTime;
  66 + }
  67 +
  68 + @Override
  69 + protected int getMaxErrors() {
  70 + return maxErrors;
  71 + }
  72 +
  73 + @Override
  74 + protected long getMaxBlacklistDuration() {
  75 + return TimeUnit.SECONDS.toMillis(maxBlackListDurationSec);
  76 + }
  77 +}
... ...
  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.common.yunteng.script;
  17 +
  18 +import com.google.protobuf.InvalidProtocolBufferException;
  19 +import com.google.protobuf.util.JsonFormat;
  20 +import org.thingsboard.server.gen.js.JsInvokeProtos;
  21 +import org.thingsboard.server.queue.common.TbProtoQueueMsg;
  22 +import org.thingsboard.server.queue.kafka.TbKafkaEncoder;
  23 +
  24 +import java.nio.charset.StandardCharsets;
  25 +
  26 +/**
  27 + * Created by ashvayka on 25.09.18.
  28 + */
  29 +public class RemoteJsRequestEncoder implements TbKafkaEncoder<TbProtoQueueMsg<JsInvokeProtos.RemoteJsRequest>> {
  30 + @Override
  31 + public byte[] encode(TbProtoQueueMsg<JsInvokeProtos.RemoteJsRequest> value) {
  32 + try {
  33 + return JsonFormat.printer().print(value.getValue()).getBytes(StandardCharsets.UTF_8);
  34 + } catch (InvalidProtocolBufferException e) {
  35 + throw new RuntimeException(e);
  36 + }
  37 + }
  38 +}
... ...
  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.common.yunteng.script;
  17 +
  18 +import com.google.protobuf.util.JsonFormat;
  19 +import org.thingsboard.server.gen.js.JsInvokeProtos;
  20 +import org.thingsboard.server.queue.TbQueueMsg;
  21 +import org.thingsboard.server.queue.common.TbProtoQueueMsg;
  22 +import org.thingsboard.server.queue.kafka.TbKafkaDecoder;
  23 +
  24 +import java.io.IOException;
  25 +import java.nio.charset.StandardCharsets;
  26 +
  27 +/**
  28 + * Created by ashvayka on 25.09.18.
  29 + */
  30 +public class RemoteJsResponseDecoder implements TbKafkaDecoder<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> {
  31 +
  32 + @Override
  33 + public TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> decode(TbQueueMsg msg) throws IOException {
  34 + JsInvokeProtos.RemoteJsResponse.Builder builder = JsInvokeProtos.RemoteJsResponse.newBuilder();
  35 + JsonFormat.parser().ignoringUnknownFields().merge(new String(msg.getData(), StandardCharsets.UTF_8), builder);
  36 + return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders());
  37 + }
  38 +}
... ...
  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.common.yunteng.script;
  17 +
  18 +import com.google.common.util.concurrent.FutureCallback;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import lombok.Getter;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.springframework.beans.factory.annotation.Autowired;
  24 +import org.springframework.beans.factory.annotation.Value;
  25 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  26 +import org.springframework.scheduling.annotation.Scheduled;
  27 +import org.springframework.stereotype.Service;
  28 +import org.springframework.util.StopWatch;
  29 +import org.thingsboard.common.util.ThingsBoardThreadFactory;
  30 +import org.thingsboard.server.gen.js.JsInvokeProtos;
  31 +import org.thingsboard.server.queue.TbQueueRequestTemplate;
  32 +import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
  33 +import org.thingsboard.server.queue.common.TbProtoQueueMsg;
  34 +import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
  35 +
  36 +import javax.annotation.Nullable;
  37 +import javax.annotation.PostConstruct;
  38 +import javax.annotation.PreDestroy;
  39 +import java.util.Map;
  40 +import java.util.UUID;
  41 +import java.util.concurrent.*;
  42 +import java.util.concurrent.atomic.AtomicInteger;
  43 +
  44 +@Slf4j
  45 +@ConditionalOnExpression("'${js.evaluator:null}'=='remote' && ('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core' || '${service.type:null}'=='tb-transport')")
  46 +@Service
  47 +public class YtRemoteJsInvokeService extends AbstractYtScriptInvokeService {
  48 +
  49 + @Value("${queue.js.max_eval_requests_timeout}")
  50 + private long maxEvalRequestsTimeout;
  51 +
  52 + @Value("${queue.js.max_requests_timeout}")
  53 + private long maxRequestsTimeout;
  54 +
  55 + @Getter
  56 + @Value("${js.remote.max_errors}")
  57 + private int maxErrors;
  58 +
  59 + @Value("${js.remote.max_black_list_duration_sec:60}")
  60 + private int maxBlackListDurationSec;
  61 +
  62 + @Value("${js.remote.stats.enabled:false}")
  63 + private boolean statsEnabled;
  64 +
  65 + private final AtomicInteger queuePushedMsgs = new AtomicInteger(0);
  66 + private final AtomicInteger queueInvokeMsgs = new AtomicInteger(0);
  67 + private final AtomicInteger queueEvalMsgs = new AtomicInteger(0);
  68 + private final AtomicInteger queueFailedMsgs = new AtomicInteger(0);
  69 + private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0);
  70 + private final ExecutorService callbackExecutor = Executors.newFixedThreadPool(
  71 + Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("js-executor-remote-callback"));
  72 +
  73 + public YtRemoteJsInvokeService(TbApiUsageClient apiUsageClient) {
  74 + super();
  75 + }
  76 +
  77 + @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}")
  78 + public void printStats() {
  79 + if (statsEnabled) {
  80 + int pushedMsgs = queuePushedMsgs.getAndSet(0);
  81 + int invokeMsgs = queueInvokeMsgs.getAndSet(0);
  82 + int evalMsgs = queueEvalMsgs.getAndSet(0);
  83 + int failed = queueFailedMsgs.getAndSet(0);
  84 + int timedOut = queueTimeoutMsgs.getAndSet(0);
  85 + if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) {
  86 + log.info("Queue JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]",
  87 + pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut);
  88 + }
  89 + }
  90 + }
  91 +
  92 + @Autowired
  93 + private TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> requestTemplate;
  94 +
  95 + private Map<UUID, String> scriptIdToBodysMap = new ConcurrentHashMap<>();
  96 +
  97 + @PostConstruct
  98 + public void init() {
  99 + super.init(maxRequestsTimeout);
  100 + requestTemplate.init();
  101 + }
  102 +
  103 + @PreDestroy
  104 + public void destroy() {
  105 + super.stop();
  106 + if (requestTemplate != null) {
  107 + requestTemplate.stop();
  108 + }
  109 + }
  110 +
  111 + @Override
  112 + protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String scriptBody) {
  113 + JsInvokeProtos.JsCompileRequest jsRequest = JsInvokeProtos.JsCompileRequest.newBuilder()
  114 + .setScriptIdMSB(scriptId.getMostSignificantBits())
  115 + .setScriptIdLSB(scriptId.getLeastSignificantBits())
  116 + .setFunctionName(functionName)
  117 + .setScriptBody(scriptBody).build();
  118 +
  119 + JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder()
  120 + .setCompileRequest(jsRequest)
  121 + .build();
  122 +
  123 + log.trace("Post compile request for scriptId [{}]", scriptId);
  124 + ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper));
  125 + if (maxEvalRequestsTimeout > 0) {
  126 + future = Futures.withTimeout(future, maxEvalRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
  127 + }
  128 + queuePushedMsgs.incrementAndGet();
  129 + Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() {
  130 + @Override
  131 + public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) {
  132 + queueEvalMsgs.incrementAndGet();
  133 + }
  134 +
  135 + @Override
  136 + public void onFailure(Throwable t) {
  137 + if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
  138 + queueTimeoutMsgs.incrementAndGet();
  139 + }
  140 + queueFailedMsgs.incrementAndGet();
  141 + }
  142 + }, callbackExecutor);
  143 + return Futures.transform(future, response -> {
  144 + JsInvokeProtos.JsCompileResponse compilationResult = response.getValue().getCompileResponse();
  145 + UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB());
  146 + if (compilationResult.getSuccess()) {
  147 + scriptIdToNameMap.put(scriptId, functionName);
  148 + scriptIdToBodysMap.put(scriptId, scriptBody);
  149 + return compiledScriptId;
  150 + } else {
  151 + log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails());
  152 + throw new RuntimeException(compilationResult.getErrorDetails());
  153 + }
  154 + }, callbackExecutor);
  155 + }
  156 +
  157 + @Override
  158 + protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) {
  159 + log.trace("doInvokeFunction js-request for uuid {} with timeout {}ms", scriptId, maxRequestsTimeout);
  160 + final String scriptBody = scriptIdToBodysMap.get(scriptId);
  161 + if (scriptBody == null) {
  162 + return Futures.immediateFailedFuture(new RuntimeException("No script body found for scriptId: [" + scriptId + "]!"));
  163 + }
  164 + JsInvokeProtos.JsInvokeRequest.Builder jsRequestBuilder = JsInvokeProtos.JsInvokeRequest.newBuilder()
  165 + .setScriptIdMSB(scriptId.getMostSignificantBits())
  166 + .setScriptIdLSB(scriptId.getLeastSignificantBits())
  167 + .setFunctionName(functionName)
  168 + .setTimeout((int) maxRequestsTimeout)
  169 + .setScriptBody(scriptBody);
  170 +
  171 + for (Object arg : args) {
  172 + jsRequestBuilder.addArgs(arg.toString());
  173 + }
  174 +
  175 + JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder()
  176 + .setInvokeRequest(jsRequestBuilder.build())
  177 + .build();
  178 +
  179 + StopWatch stopWatch = new StopWatch();
  180 + stopWatch.start();
  181 +
  182 + ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper));
  183 + if (maxRequestsTimeout > 0) {
  184 + future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
  185 + }
  186 + queuePushedMsgs.incrementAndGet();
  187 + Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() {
  188 + @Override
  189 + public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) {
  190 + queueInvokeMsgs.incrementAndGet();
  191 + }
  192 +
  193 + @Override
  194 + public void onFailure(Throwable t) {
  195 + onScriptExecutionError(scriptId, t, scriptBody);
  196 + if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
  197 + queueTimeoutMsgs.incrementAndGet();
  198 + }
  199 + queueFailedMsgs.incrementAndGet();
  200 + }
  201 + }, callbackExecutor);
  202 + return Futures.transform(future, response -> {
  203 + stopWatch.stop();
  204 + log.trace("doInvokeFunction js-response took {}ms for uuid {}", stopWatch.getTotalTimeMillis(), response.getKey());
  205 + JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse();
  206 + if (invokeResult.getSuccess()) {
  207 + return invokeResult.getResult();
  208 + } else {
  209 + final RuntimeException e = new RuntimeException(invokeResult.getErrorDetails());
  210 + onScriptExecutionError(scriptId, e, scriptBody);
  211 + log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails());
  212 + throw e;
  213 + }
  214 + }, callbackExecutor);
  215 + }
  216 +
  217 + @Override
  218 + protected void doRelease(UUID scriptId, String functionName) throws Exception {
  219 + JsInvokeProtos.JsReleaseRequest jsRequest = JsInvokeProtos.JsReleaseRequest.newBuilder()
  220 + .setScriptIdMSB(scriptId.getMostSignificantBits())
  221 + .setScriptIdLSB(scriptId.getLeastSignificantBits())
  222 + .setFunctionName(functionName).build();
  223 +
  224 + JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder()
  225 + .setReleaseRequest(jsRequest)
  226 + .build();
  227 +
  228 + ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper));
  229 + if (maxRequestsTimeout > 0) {
  230 + future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
  231 + }
  232 + JsInvokeProtos.RemoteJsResponse response = future.get().getValue();
  233 +
  234 + JsInvokeProtos.JsReleaseResponse compilationResult = response.getReleaseResponse();
  235 + UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB());
  236 + if (compilationResult.getSuccess()) {
  237 + scriptIdToBodysMap.remove(scriptId);
  238 + } else {
  239 + log.debug("[{}] Failed to release script due", compiledScriptId);
  240 + }
  241 + }
  242 +
  243 + @Override
  244 + protected long getMaxBlacklistDuration() {
  245 + return TimeUnit.SECONDS.toMillis(maxBlackListDurationSec);
  246 + }
  247 +
  248 +}
... ...
  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.common.yunteng.script;
  17 +
  18 +import org.springframework.beans.factory.annotation.Value;
  19 +import org.springframework.stereotype.Component;
  20 +import org.thingsboard.common.util.AbstractListeningExecutor;
  21 +
  22 +@Component
  23 +public class YtScriptExecutorService extends AbstractListeningExecutor {
  24 +
  25 + @Value("${actors.rule.js_thread_pool_size}")
  26 + private int jsExecutorThreadPoolSize;
  27 +
  28 + @Override
  29 + protected int getThreadPollSize() {
  30 + return Math.max(jsExecutorThreadPoolSize, 1);
  31 + }
  32 +
  33 +}
... ...
  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.common.yunteng.script;
  17 +
  18 +public class YtScriptFactory {
  19 +
  20 + public static final String MSG = "params";
  21 +
  22 + public static final String RULE_NODE_FUNCTION_NAME = "tcpTransportFunc";
  23 +
  24 + private static final String JS_WRAPPER_PREFIX_TEMPLATE = "function %s(params) { " +
  25 + " var out = new Object(); " +
  26 + " return JSON.stringify(%s(params));" +
  27 + " function %s(%s) {";
  28 + private static final String JS_WRAPPER_SUFFIX = "return out; \n }" +
  29 + "\n}";
  30 +
  31 +
  32 + public static String generateRuleNodeScript(String functionName, String scriptBody, String... argNames) {
  33 + String msgArg;
  34 +
  35 + msgArg = (argNames != null && argNames.length >= 1) ? argNames[0]:MSG;
  36 +
  37 + String jsWrapperPrefix = String.format(JS_WRAPPER_PREFIX_TEMPLATE
  38 + , functionName
  39 + ,RULE_NODE_FUNCTION_NAME
  40 + , RULE_NODE_FUNCTION_NAME, msgArg);
  41 + String result = jsWrapperPrefix + scriptBody + JS_WRAPPER_SUFFIX;
  42 + return result;
  43 + }
  44 +
  45 +}
... ...
  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.common.yunteng.script;
  17 +
  18 +import java.util.List;
  19 +
  20 +/**
  21 + * Created by ashvayka on 25.09.18.
  22 + */
  23 +public class YtScriptInvokeRequest {
  24 +
  25 + private String scriptId;
  26 + private String scriptBody;
  27 + private List<String> args;
  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.common.yunteng.script;
  17 +
  18 +import java.util.List;
  19 +
  20 +/**
  21 + * Created by ashvayka on 25.09.18.
  22 + */
  23 +public class YtScriptInvokeResponse {
  24 +
  25 + private String scriptId;
  26 + private String scriptBody;
  27 + private List<String> args;
  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.common.yunteng.script;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +
  20 +import java.util.UUID;
  21 +
  22 +public interface YtScriptInvokeService {
  23 +
  24 + ListenableFuture<UUID> eval(YtScriptType scriptType, String scriptBody, String... argNames);
  25 +
  26 + ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args);
  27 +
  28 + ListenableFuture<Void> release(UUID scriptId);
  29 +
  30 +}
... ...
  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.common.yunteng.script;
  17 +
  18 +import com.google.common.util.concurrent.FutureCallback;
  19 +import lombok.AllArgsConstructor;
  20 +
  21 +import javax.annotation.Nullable;
  22 +import java.util.concurrent.TimeoutException;
  23 +import java.util.concurrent.atomic.AtomicInteger;
  24 +
  25 +@AllArgsConstructor
  26 +public class YtScriptStatCallback<T> implements FutureCallback<T> {
  27 +
  28 + private final AtomicInteger jsSuccessMsgs;
  29 + private final AtomicInteger jsTimeoutMsgs;
  30 + private final AtomicInteger jsFailedMsgs;
  31 +
  32 +
  33 + @Override
  34 + public void onSuccess(@Nullable T result) {
  35 + jsSuccessMsgs.incrementAndGet();
  36 + }
  37 +
  38 + @Override
  39 + public void onFailure(Throwable t) {
  40 + if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
  41 + jsTimeoutMsgs.incrementAndGet();
  42 + } else {
  43 + jsFailedMsgs.incrementAndGet();
  44 + }
  45 + }
  46 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.yunteng.script;
  17 +
  18 +public enum YtScriptType {
  19 + TCP_TRANSPORT_SCRIPT
  20 +}
... ...
... ... @@ -31,29 +31,12 @@ import org.springframework.transaction.annotation.Transactional;
31 31 import org.springframework.util.CollectionUtils;
32 32 import org.springframework.util.StringUtils;
33 33 import org.thingsboard.common.util.JacksonUtil;
34   -import org.thingsboard.server.common.data.Device;
35   -import org.thingsboard.server.common.data.DeviceInfo;
36   -import org.thingsboard.server.common.data.DeviceProfile;
37   -import org.thingsboard.server.common.data.DeviceTransportType;
38   -import org.thingsboard.server.common.data.EntitySubtype;
39   -import org.thingsboard.server.common.data.EntityType;
40   -import org.thingsboard.server.common.data.EntityView;
  34 +import org.thingsboard.server.common.data.*;
41 35 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
42 36 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
43   -import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration;
44   -import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
45   -import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
46   -import org.thingsboard.server.common.data.device.data.DeviceData;
47   -import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration;
48   -import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration;
49   -import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
  37 +import org.thingsboard.server.common.data.device.data.*;
50 38 import org.thingsboard.server.common.data.edge.Edge;
51   -import org.thingsboard.server.common.data.id.CustomerId;
52   -import org.thingsboard.server.common.data.id.DeviceId;
53   -import org.thingsboard.server.common.data.id.DeviceProfileId;
54   -import org.thingsboard.server.common.data.id.EdgeId;
55   -import org.thingsboard.server.common.data.id.EntityId;
56   -import org.thingsboard.server.common.data.id.TenantId;
  39 +import org.thingsboard.server.common.data.id.*;
57 40 import org.thingsboard.server.common.data.ota.OtaPackageType;
58 41 import org.thingsboard.server.common.data.page.PageData;
59 42 import org.thingsboard.server.common.data.page.PageLink;
... ... @@ -73,21 +56,13 @@ import org.thingsboard.server.dao.service.DataValidator;
73 56 import org.thingsboard.server.dao.service.PaginatedRemover;
74 57
75 58 import javax.annotation.Nullable;
76   -import java.util.ArrayList;
77   -import java.util.Collections;
78   -import java.util.Comparator;
79   -import java.util.List;
80   -import java.util.Optional;
81   -import java.util.UUID;
  59 +import java.util.*;
82 60 import java.util.concurrent.ExecutionException;
83 61 import java.util.stream.Collectors;
84 62
85 63 import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE;
86 64 import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
87   -import static org.thingsboard.server.dao.service.Validator.validateId;
88   -import static org.thingsboard.server.dao.service.Validator.validateIds;
89   -import static org.thingsboard.server.dao.service.Validator.validatePageLink;
90   -import static org.thingsboard.server.dao.service.Validator.validateString;
  65 +import static org.thingsboard.server.dao.service.Validator.*;
91 66
92 67 @Service
93 68 @Slf4j
... ... @@ -288,6 +263,12 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
288 263 case SNMP:
289 264 deviceData.setTransportConfiguration(new SnmpDeviceTransportConfiguration());
290 265 break;
  266 +
  267 + //Thingskit function
  268 + case TCP:
  269 + deviceData.setTransportConfiguration(new YtTcpDeviceTransportConfiguration());
  270 + break;
  271 +
291 272 }
292 273 }
293 274 return deviceData;
... ...
... ... @@ -19,12 +19,15 @@ import com.google.common.util.concurrent.ListenableFuture;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.springframework.beans.factory.annotation.Autowired;
21 21 import org.springframework.stereotype.Service;
  22 +import org.thingsboard.server.common.data.DeviceProfile;
22 23 import org.thingsboard.server.common.data.Tenant;
23 24 import org.thingsboard.server.common.data.TenantInfo;
24 25 import org.thingsboard.server.common.data.TenantProfile;
25 26 import org.thingsboard.server.common.data.id.TenantId;
26 27 import org.thingsboard.server.common.data.page.PageData;
27 28 import org.thingsboard.server.common.data.page.PageLink;
  29 +import org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO;
  30 +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum;
28 31 import org.thingsboard.server.dao.asset.AssetService;
29 32 import org.thingsboard.server.dao.customer.CustomerService;
30 33 import org.thingsboard.server.dao.dashboard.DashboardService;
... ... @@ -42,6 +45,7 @@ import org.thingsboard.server.dao.service.Validator;
42 45 import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
43 46 import org.thingsboard.server.dao.user.UserService;
44 47 import org.thingsboard.server.dao.widget.WidgetsBundleService;
  48 +import org.thingsboard.server.dao.yunteng.service.YtDeviceProfileService;
45 49
46 50 import static org.thingsboard.server.dao.service.Validator.validateId;
47 51
... ... @@ -99,7 +103,8 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
99 103
100 104 @Autowired
101 105 private DataValidator<Tenant> tenantValidator;
102   -
  106 + @Autowired
  107 + private YtDeviceProfileService ytDeviceProfileService;
103 108 @Override
104 109 public Tenant findTenantById(TenantId tenantId) {
105 110 log.trace("Executing findTenantById [{}]", tenantId);
... ... @@ -132,8 +137,13 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
132 137 tenantValidator.validate(tenant, Tenant::getId);
133 138 Tenant savedTenant = tenantDao.save(tenant.getId(), tenant);
134 139 if (tenant.getId() == null) {
135   - deviceProfileService.createDefaultDeviceProfile(savedTenant.getId());
  140 +
  141 + //Thingskit function
  142 + DeviceProfile deviceProfile = deviceProfileService.createDefaultDeviceProfile(savedTenant.getId());
  143 + DeviceProfileDTO profileDTO = new DeviceProfileDTO(deviceProfile.getName(),deviceProfile.getTenantId(),deviceProfile.getId(), DeviceTypeEnum.DIRECT_CONNECTION);
  144 + ytDeviceProfileService.insertOrUpdate(profileDTO);
136 145 apiUsageStateService.createDefaultApiUsageState(savedTenant.getId(), null);
  146 +
137 147 }
138 148 return savedTenant;
139 149 }
... ...
... ... @@ -8,12 +8,8 @@ import lombok.Data;
8 8 import lombok.EqualsAndHashCode;
9 9 import org.apache.ibatis.type.EnumTypeHandler;
10 10 import org.thingsboard.server.common.data.yunteng.constant.ModelConstants;
11   -import org.thingsboard.server.common.data.yunteng.enums.DeviceCredentialsEnum;
12   -import org.thingsboard.server.common.data.yunteng.enums.DeviceState;
13 11 import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum;
14 12
15   -import java.time.LocalDateTime;
16   -
17 13 @Data
18 14 @EqualsAndHashCode(callSuper = true)
19 15 @TableName(value = ModelConstants.Table.IOTFS_DEVICE_TABLE_NAME, autoResultMap = true)
... ...
  1 +package org.thingsboard.server.dao.yunteng.entities;
  2 +
  3 +import com.baomidou.mybatisplus.annotation.TableField;
  4 +import com.baomidou.mybatisplus.annotation.TableName;
  5 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  6 +import com.fasterxml.jackson.databind.JsonNode;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +import org.apache.ibatis.type.EnumTypeHandler;
  10 +import org.thingsboard.server.common.data.yunteng.constant.ModelConstants;
  11 +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum;
  12 +
  13 +@Data
  14 +@EqualsAndHashCode(callSuper = true)
  15 +@TableName(value = ModelConstants.Table.IOTFS_DEVICE_PROFILE_TABLE_NAME, autoResultMap = true)
  16 +public class YtDeviceProfileEntity extends TenantBaseEntity {
  17 + private String name;
  18 +
  19 + private String scriptId;
  20 + private String tbProfileId;
  21 + @TableField(typeHandler = EnumTypeHandler.class)
  22 + private DeviceTypeEnum deviceType;
  23 + private String description;
  24 +}
... ...
  1 +package org.thingsboard.server.dao.yunteng.entities;
  2 +
  3 +import com.baomidou.mybatisplus.annotation.TableField;
  4 +import com.baomidou.mybatisplus.annotation.TableName;
  5 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  6 +import com.fasterxml.jackson.databind.JsonNode;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +import org.apache.ibatis.type.EnumTypeHandler;
  10 +import org.thingsboard.server.common.data.yunteng.constant.ModelConstants;
  11 +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum;
  12 +
  13 +@Data
  14 +@TableName(value = ModelConstants.Table.IOTFS_DEVICE_SCRIPT_TABLE_NAME, autoResultMap = true)
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class YtDeviceScriptEntity extends TenantBaseEntity {
  17 + private String name;
  18 + private String convertJs;
  19 + /**
  20 + * 告警状态:0:正常 1:告警
  21 + */
  22 + private Integer status;
  23 + private String description;
  24 +}
... ...
... ... @@ -2,6 +2,7 @@ package org.thingsboard.server.dao.yunteng.impl;
2 2
3 3 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4 4 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  5 +import com.baomidou.mybatisplus.core.metadata.IPage;
5 6 import lombok.RequiredArgsConstructor;
6 7 import lombok.extern.slf4j.Slf4j;
7 8 import org.apache.commons.lang3.StringUtils;
... ... @@ -17,30 +18,33 @@ import org.thingsboard.server.common.data.yunteng.constant.ModelConstants;
17 18 import org.thingsboard.server.common.data.yunteng.core.cache.CacheUtils;
18 19 import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException;
19 20 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
20   -import org.thingsboard.server.common.data.yunteng.dto.AlarmProfileDTO;
21   -import org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO;
  21 +import org.thingsboard.server.common.data.yunteng.dto.*;
  22 +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;
22 23 import org.thingsboard.server.common.data.yunteng.utils.ReflectUtils;
23 24 import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData;
24 25 import org.thingsboard.server.dao.service.Validator;
25   -import org.thingsboard.server.dao.yunteng.entities.Menu;
  26 +import org.thingsboard.server.dao.yunteng.entities.*;
26 27 import org.thingsboard.server.dao.yunteng.jpa.dao.YtJpaDeviceProfileDao;
27   -import org.thingsboard.server.dao.yunteng.entities.AlarmProfile;
28   -import org.thingsboard.server.dao.yunteng.entities.YtDevice;
29 28 import org.thingsboard.server.dao.yunteng.mapper.AlarmProfileMapper;
30 29 import org.thingsboard.server.dao.yunteng.mapper.DeviceMapper;
  30 +import org.thingsboard.server.dao.yunteng.mapper.YtDeviceProfileMapper;
  31 +import org.thingsboard.server.dao.yunteng.service.AbstractBaseService;
31 32 import org.thingsboard.server.dao.yunteng.service.YtDeviceProfileService;
32 33
33 34 import java.util.*;
  35 +import java.util.stream.Collectors;
34 36
35 37 @Service
36 38 @RequiredArgsConstructor
37 39 @Slf4j
38   -public class YtDeviceProfileServiceImpl implements YtDeviceProfileService {
  40 +public class YtDeviceProfileServiceImpl extends AbstractBaseService<YtDeviceProfileMapper, YtDeviceProfileEntity>
  41 + implements YtDeviceProfileService {
39 42
40 43 private final DeviceMapper deviceMapper;
41 44
42 45 private final YtJpaDeviceProfileDao deviceProfileDao;
43 46
  47 +
44 48 @Override
45 49 public boolean validateFormdata(DeviceProfileDTO ytDeviceProfileDTO) {
46 50 TenantId tenantId = TenantId.fromUUID(UUID.fromString(ytDeviceProfileDTO.getTenantId()));
... ... @@ -63,6 +67,61 @@ public class YtDeviceProfileServiceImpl implements YtDeviceProfileService {
63 67 }
64 68
65 69 @Override
  70 + public DeviceProfileDTO insertOrUpdate( DeviceProfileDTO deviceDTO) {
  71 + if (StringUtils.isBlank(deviceDTO.getId())) {
  72 + return insert(deviceDTO);
  73 + } else {
  74 + return update(deviceDTO);
  75 + }
  76 + }
  77 +
  78 + private DeviceProfileDTO insert(DeviceProfileDTO deviceDTO) {
  79 +
  80 + YtDeviceProfileEntity profile = new YtDeviceProfileEntity();
  81 + deviceDTO.copyToEntity(
  82 + profile,
  83 + ModelConstants.TablePropertyMapping.ACTIVE_TIME,
  84 + ModelConstants.TablePropertyMapping.DEVICE_STATE,
  85 + ModelConstants.TablePropertyMapping.CREATOR,
  86 + ModelConstants.TablePropertyMapping.UPDATER,
  87 + ModelConstants.TablePropertyMapping.CREATE_TIME,
  88 + ModelConstants.TablePropertyMapping.UPDATE,
  89 + ModelConstants.TablePropertyMapping.UPDATE_TIME);
  90 +
  91 +
  92 + baseMapper.insert(profile);
  93 + return profile.getDTO(DeviceProfileDTO.class);
  94 + }
  95 +
  96 + private DeviceProfileDTO update(DeviceProfileDTO deviceDTO) {
  97 + YtDeviceProfileEntity device = new YtDeviceProfileEntity();
  98 + deviceDTO.copyToEntity(
  99 + device,
  100 + ModelConstants.TablePropertyMapping.ACTIVE_TIME,
  101 + ModelConstants.TablePropertyMapping.DEVICE_STATE,
  102 + ModelConstants.TablePropertyMapping.TB_DEVICE_ID,
  103 + ModelConstants.TablePropertyMapping.TENANT_CODE,
  104 + ModelConstants.TablePropertyMapping.CREATOR,
  105 + ModelConstants.TablePropertyMapping.UPDATER,
  106 + ModelConstants.TablePropertyMapping.CREATE_TIME,
  107 + ModelConstants.TablePropertyMapping.UPDATE,
  108 + ModelConstants.TablePropertyMapping.UPDATE_TIME);
  109 + baseMapper.updateById(device);
  110 + return device.getDTO(DeviceProfileDTO.class);
  111 + }
  112 +
  113 + @Override
  114 + public void deleteDeviceProfiles(String tenantId, Set<String> ids) {
  115 + LambdaQueryWrapper<YtDeviceProfileEntity> queryWrapper =
  116 + new QueryWrapper<YtDeviceProfileEntity>()
  117 + .lambda()
  118 + .eq(YtDeviceProfileEntity::getTenantId, tenantId)
  119 + .in(YtDeviceProfileEntity::getId, ids);
  120 +
  121 + baseMapper.delete(queryWrapper);
  122 + }
  123 +
  124 + @Override
66 125 @Transactional
67 126 public void checkDeviceProfiles(String tenantId, Set<String> ids) {
68 127 // check if ids bind to device
... ... @@ -76,28 +135,17 @@ public class YtDeviceProfileServiceImpl implements YtDeviceProfileService {
76 135
77 136 @Override
78 137 public Optional<DeviceProfileDTO> getDeviceProfile(String tenantId, String id) {
79   - TenantId tenant = TenantId.fromUUID(UUID.fromString(tenantId));
80   - DeviceProfile profile = deviceProfileDao.findById(tenant, UUID.fromString(id));
81   - return Optional.ofNullable(profile)
82   - .map(
83   - entity -> {
84   - DeviceProfileDTO result = ReflectUtils.getDeviceProfileDTO(entity);
85   - return result;
86   - });
  138 + return Optional.ofNullable(baseMapper.selectDetail(tenantId,id));
87 139 }
88 140
89 141 @Override
90   - public YtPageData<DeviceProfileDTO> page(
91   - PageLink pageLink, String tenantIdStr, String transportType) {
  142 + public YtPageData<DeviceProfileDTO> page(Integer page, Integer pageSize, String orderField, OrderTypeEnum orderType
  143 + , String tenantIdStr, String profileName, String transportType) {
92 144
93   - TenantId tenantId = TenantId.fromUUID(UUID.fromString(tenantIdStr));
94   - Validator.validatePageLink(pageLink);
95   - PageData<DeviceProfileDTO> tbDatas =
96   - deviceProfileDao.findDeviceProfileInfos(pageLink, tenantId, transportType);
97   - YtPageData<DeviceProfileDTO> result =
98   - new YtPageData<>(tbDatas.getData(), tbDatas.getTotalElements());
  145 + IPage<YtDeviceProfileEntity> currentPage = getCurrentPage(page,pageSize,orderField,orderType);
  146 + IPage<DeviceProfileDTO> result = baseMapper.getProfilePage(currentPage,tenantIdStr,profileName,transportType);
99 147
100   - return result;
  148 + return getPageData(result, DeviceProfileDTO.class);
101 149 }
102 150
103 151 @Override
... ... @@ -107,4 +155,17 @@ public class YtDeviceProfileServiceImpl implements YtDeviceProfileService {
107 155 deviceProfileDao.findDeviceProfileByTenantId(TenantId.fromUUID(profileId));
108 156 return results;
109 157 }
  158 +
  159 + @Override
  160 + public List<DeviceProfileDTO> findDeviceProfile(String tenantId, String scriptId) {
  161 + LambdaQueryWrapper<YtDeviceProfileEntity> queryWrapper =
  162 + new QueryWrapper<YtDeviceProfileEntity>()
  163 + .lambda()
  164 + .eq(YtDeviceProfileEntity::getTenantId, tenantId)
  165 + .eq(YtDeviceProfileEntity::getScriptId, scriptId);
  166 + List<DeviceProfileDTO> results = baseMapper.selectList(queryWrapper).stream()
  167 + .map(item -> item.getDTO(DeviceProfileDTO.class))
  168 + .collect(Collectors.toList());
  169 + return results;
  170 + }
110 171 }
... ...
  1 +package org.thingsboard.server.dao.yunteng.impl;
  2 +
  3 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  4 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  5 +import com.baomidou.mybatisplus.core.metadata.IPage;
  6 +import lombok.RequiredArgsConstructor;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.apache.commons.lang3.StringUtils;
  9 +import org.springframework.stereotype.Service;
  10 +import org.springframework.transaction.annotation.Transactional;
  11 +import org.thingsboard.server.common.data.yunteng.constant.ModelConstants;
  12 +import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException;
  13 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  14 +import org.thingsboard.server.common.data.yunteng.dto.YtDeviceScriptDTO;
  15 +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;
  16 +import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData;
  17 +import org.thingsboard.server.dao.yunteng.entities.YtDeviceProfileEntity;
  18 +import org.thingsboard.server.dao.yunteng.entities.YtDeviceScriptEntity;
  19 +import org.thingsboard.server.dao.yunteng.mapper.YtDeviceProfileMapper;
  20 +import org.thingsboard.server.dao.yunteng.mapper.YtDeviceScriptMapper;
  21 +import org.thingsboard.server.dao.yunteng.service.AbstractBaseService;
  22 +import org.thingsboard.server.dao.yunteng.service.YtDeviceScriptService;
  23 +
  24 +import java.util.List;
  25 +import java.util.Optional;
  26 +import java.util.Set;
  27 +import java.util.stream.Collectors;
  28 +
  29 +@Service
  30 +@RequiredArgsConstructor
  31 +@Slf4j
  32 +public class YtDeviceScriptServiceImpl extends AbstractBaseService<YtDeviceScriptMapper, YtDeviceScriptEntity>
  33 + implements YtDeviceScriptService {
  34 +
  35 +
  36 + private final YtDeviceProfileMapper profileMapper;
  37 +
  38 +
  39 + @Override
  40 + public boolean validateFormdata(YtDeviceScriptDTO scriptDTO,boolean created) {
  41 + String tenantId = scriptDTO.getTenantId();
  42 + if (created) {
  43 + // 判断数据库是否已存在名字相同的设备配置
  44 + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper =
  45 + new QueryWrapper<YtDeviceScriptEntity>()
  46 + .lambda()
  47 + .eq(YtDeviceScriptEntity::getTenantId, tenantId)
  48 + .eq(YtDeviceScriptEntity::getName, scriptDTO.getName());
  49 + int results = baseMapper.selectCount(queryWrapper);
  50 + if (results > 0) {
  51 + throw new YtDataValidationException(ErrorMessage.NAME_ALREADY_EXISTS.getMessage());
  52 + }
  53 + } else {
  54 + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper =
  55 + new QueryWrapper<YtDeviceScriptEntity>()
  56 + .lambda()
  57 + .eq(YtDeviceScriptEntity::getTenantId, tenantId)
  58 + .and(
  59 + second ->
  60 + second
  61 + .eq(YtDeviceScriptEntity::getId, scriptDTO.getId())
  62 + .or(f -> f.eq(YtDeviceScriptEntity::getName, scriptDTO.getName())));
  63 +
  64 +
  65 + List<YtDeviceScriptEntity> results = baseMapper.selectList(queryWrapper);
  66 + for(YtDeviceScriptEntity item:results){
  67 + if(item.getId().equals(scriptDTO.getId()) && !item.getTenantId().equals(scriptDTO.getTenantId())){
  68 + throw new YtDataValidationException(ErrorMessage.TENANT_MISMATCHING.getMessage());
  69 + }
  70 + if(!item.getId().equals(scriptDTO.getId()) && item.getName().equals(scriptDTO.getName())){
  71 + throw new YtDataValidationException(ErrorMessage.NAME_ALREADY_EXISTS.getMessage());
  72 + }
  73 + }
  74 + }
  75 +
  76 + return false;
  77 + }
  78 +
  79 + @Override
  80 + public String getScriptText(String tenantId, String scriptId) {
  81 + return null;
  82 + }
  83 +
  84 + @Override
  85 + public YtDeviceScriptDTO insertOrUpdate( YtDeviceScriptDTO deviceDTO) {
  86 + if (StringUtils.isBlank(deviceDTO.getId())) {
  87 + return insert(deviceDTO);
  88 + } else {
  89 + return update(deviceDTO);
  90 + }
  91 + }
  92 +
  93 + @Override
  94 + public void checkDeviceScriptes(String tenantId, Set<String> ids) {
  95 + // check if ids bind to device
  96 + List<YtDeviceProfileEntity> usedList =
  97 + profileMapper.selectList(
  98 + new QueryWrapper<YtDeviceProfileEntity>().lambda().in(YtDeviceProfileEntity::getScriptId, ids));
  99 + if (usedList !=null &&usedList.size() > 0) {
  100 + List<String> names = usedList.stream()
  101 + .map(i -> i.getName())
  102 + .collect(Collectors.toList());
  103 + throw new YtDataValidationException(String.format(ErrorMessage.PROJECT_USED_SCRIPT.getMessage(),names));
  104 + }
  105 + }
  106 + @Override
  107 + @Transactional
  108 + public void deleteScriptes(String tenantId, Set<String> ids) {
  109 + checkDeviceScriptes(tenantId, ids);
  110 + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper =
  111 + new QueryWrapper<YtDeviceScriptEntity>()
  112 + .lambda()
  113 + .eq(YtDeviceScriptEntity::getTenantId, tenantId)
  114 + .in(YtDeviceScriptEntity::getId, ids);
  115 +
  116 + baseMapper.delete(queryWrapper);
  117 +
  118 + }
  119 +
  120 + private YtDeviceScriptDTO insert(YtDeviceScriptDTO deviceDTO) {
  121 +
  122 + YtDeviceScriptEntity profile = new YtDeviceScriptEntity();
  123 + deviceDTO.copyToEntity(
  124 + profile,
  125 + ModelConstants.TablePropertyMapping.ACTIVE_TIME,
  126 + ModelConstants.TablePropertyMapping.DEVICE_STATE,
  127 + ModelConstants.TablePropertyMapping.UPDATER,
  128 + ModelConstants.TablePropertyMapping.CREATE_TIME,
  129 + ModelConstants.TablePropertyMapping.UPDATE_TIME);
  130 +
  131 + baseMapper.insert(profile);
  132 + return profile.getDTO(YtDeviceScriptDTO.class);
  133 + }
  134 +
  135 + private YtDeviceScriptDTO update(YtDeviceScriptDTO deviceDTO) {
  136 + YtDeviceScriptEntity device = new YtDeviceScriptEntity();
  137 + deviceDTO.copyToEntity(
  138 + device,
  139 + ModelConstants.TablePropertyMapping.ACTIVE_TIME,
  140 + ModelConstants.TablePropertyMapping.DEVICE_STATE,
  141 + ModelConstants.TablePropertyMapping.TB_DEVICE_ID,
  142 + ModelConstants.TablePropertyMapping.TENANT_CODE,
  143 + ModelConstants.TablePropertyMapping.CREATOR,
  144 + ModelConstants.TablePropertyMapping.UPDATER,
  145 + ModelConstants.TablePropertyMapping.CREATE_TIME,
  146 + ModelConstants.TablePropertyMapping.UPDATE,
  147 + ModelConstants.TablePropertyMapping.UPDATE_TIME);
  148 + baseMapper.updateById(device);
  149 + return device.getDTO(YtDeviceScriptDTO.class);
  150 + }
  151 +
  152 +
  153 +
  154 +
  155 + @Override
  156 + public Optional<YtDeviceScriptDTO> getDeviceScript(String tenantId, String id) {
  157 + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper =
  158 + new QueryWrapper<YtDeviceScriptEntity>()
  159 + .lambda()
  160 + .eq(YtDeviceScriptEntity::getTenantId, tenantId)
  161 + .eq(YtDeviceScriptEntity::getId, id);
  162 + YtDeviceScriptEntity profile = baseMapper.selectOne(queryWrapper);
  163 + return Optional.ofNullable(profile)
  164 + .map(
  165 + entity -> {
  166 + YtDeviceScriptDTO result = entity.getDTO(YtDeviceScriptDTO.class);
  167 + return result;
  168 + });
  169 + }
  170 +
  171 + @Override
  172 + public YtPageData<YtDeviceScriptDTO> page(Integer page, Integer pageSize, String orderField, OrderTypeEnum orderType,String tenantId) {
  173 + IPage<YtDeviceScriptEntity> currentPage = getCurrentPage(page,pageSize,orderField,orderType);
  174 +
  175 + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper =
  176 + new QueryWrapper<YtDeviceScriptEntity>()
  177 + .lambda()
  178 + .eq(YtDeviceScriptEntity::getTenantId, tenantId);
  179 +
  180 +
  181 +
  182 +
  183 + IPage<YtDeviceScriptEntity> scriptes = baseMapper.selectPage(currentPage, queryWrapper);
  184 + List<YtDeviceScriptDTO> records = scriptes.getRecords().stream()
  185 + .map(entity -> entity.getDTO(YtDeviceScriptDTO.class))
  186 + .collect(Collectors.toList());
  187 + return new YtPageData<>(records, scriptes.getTotal());
  188 + }
  189 +
  190 + @Override
  191 + public List<YtDeviceScriptDTO> findDeviceScript(String tenantId) {
  192 + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper =
  193 + new QueryWrapper<YtDeviceScriptEntity>()
  194 + .lambda()
  195 + .eq(YtDeviceScriptEntity::getTenantId, tenantId);
  196 + List<YtDeviceScriptDTO> results = baseMapper.selectList(queryWrapper).stream()
  197 + .map(item -> item.getDTO(YtDeviceScriptDTO.class))
  198 + .collect(Collectors.toList());
  199 + return results;
  200 + }
  201 +
  202 +
  203 +}
... ...
  1 +package org.thingsboard.server.dao.yunteng.mapper;
  2 +
  3 +import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  4 +import com.baomidou.mybatisplus.core.metadata.IPage;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +import org.apache.ibatis.annotations.Param;
  7 +import org.thingsboard.server.common.data.yunteng.dto.*;
  8 +import org.thingsboard.server.dao.yunteng.entities.YtDevice;
  9 +import org.thingsboard.server.dao.yunteng.entities.YtDeviceProfileEntity;
  10 +
  11 +import java.util.List;
  12 +import java.util.Map;
  13 +
  14 +@Mapper
  15 +public interface YtDeviceProfileMapper extends BaseMapper<YtDeviceProfileEntity> {
  16 +
  17 + /**
  18 + * 获取产品(设备配置)详情
  19 + * @return
  20 + */
  21 + public DeviceProfileDTO selectDetail( @Param("tenantId") String tenantId, @Param("id") String id);
  22 +
  23 + public IPage<DeviceProfileDTO> getProfilePage(IPage<?> page, @Param("tenantId") String tenantId, @Param("profileName") String profileName, @Param("transportType") String transportType);
  24 +
  25 +}
... ...
  1 +package org.thingsboard.server.dao.yunteng.mapper;
  2 +
  3 +import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  4 +import org.apache.ibatis.annotations.Mapper;
  5 +import org.thingsboard.server.dao.yunteng.entities.YtDeviceScriptEntity;
  6 +
  7 +@Mapper
  8 +public interface YtDeviceScriptMapper extends BaseMapper<YtDeviceScriptEntity> {
  9 +}
... ...
... ... @@ -76,6 +76,33 @@ public abstract class AbstractBaseService<M extends BaseMapper<T>, T extends Bas
76 76 }
77 77 }
78 78
  79 + protected IPage<T> getCurrentPage(Integer page, Integer pageSize, String orderField, OrderTypeEnum orderType) {
  80 + if(page == null){
  81 + page = 1;
  82 + }
  83 + if(pageSize == null){
  84 + pageSize=10;
  85 + }
  86 + if(StringUtils.isEmpty(orderField)){
  87 + orderField = FastIotConstants.DefaultOrder.CREATE_TIME;
  88 + }
  89 + if(orderType == null){
  90 + orderType = OrderTypeEnum.DESC;
  91 + }
  92 +
  93 + Page<T> pageInform = new Page<>(page, pageSize);
  94 +
  95 + switch (orderType){
  96 + case ASC:
  97 + pageInform.addOrder(OrderItem.asc(orderField));
  98 + break;
  99 + default:
  100 + pageInform.addOrder(OrderItem.desc(orderField));
  101 + }
  102 +
  103 + return pageInform;
  104 + }
  105 +
79 106 /**
80 107 * 分页信息
81 108 * @param pageNum 第几页
... ...
1 1 package org.thingsboard.server.dao.yunteng.service;
2 2
  3 +import com.baomidou.mybatisplus.core.metadata.IPage;
3 4 import org.thingsboard.server.common.data.page.PageLink;
  5 +import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO;
4 6 import org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO;
  7 +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;
5 8 import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData;
6 9
7 10 import java.util.List;
... ... @@ -10,13 +13,19 @@ import java.util.Set;
10 13
11 14 public interface YtDeviceProfileService {
12 15
  16 + DeviceProfileDTO insertOrUpdate(DeviceProfileDTO deviceDTO);
  17 +
  18 + void deleteDeviceProfiles(String tenantId, Set<String> ids);
  19 +
13 20 void checkDeviceProfiles(String tenantId, Set<String> ids);
14 21
15 22 Optional<DeviceProfileDTO> getDeviceProfile(String tenantId, String id);
16 23
17   - YtPageData<DeviceProfileDTO> page(PageLink pageLink, String tenantId, String transportType);
  24 + YtPageData<DeviceProfileDTO> page(Integer page, Integer pageSize, String orderField, OrderTypeEnum orderType
  25 + ,String tenantIdStr, String profileName, String transportType);
18 26
19 27 List<DeviceProfileDTO> findDeviceProfile(String tenantId);
  28 + List<DeviceProfileDTO> findDeviceProfile(String tenantId,String scriptId);
20 29
21 30
22 31 /**
... ...
  1 +package org.thingsboard.server.dao.yunteng.service;
  2 +
  3 +import org.thingsboard.server.common.data.page.PageLink;
  4 +import org.thingsboard.server.common.data.yunteng.dto.YtDeviceScriptDTO;
  5 +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;
  6 +import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData;
  7 +import org.thingsboard.server.dao.yunteng.entities.Trigger;
  8 +import org.thingsboard.server.dao.yunteng.entities.YtDeviceScriptEntity;
  9 +
  10 +import java.util.List;
  11 +import java.util.Map;
  12 +import java.util.Optional;
  13 +import java.util.Set;
  14 +
  15 +public interface YtDeviceScriptService extends BaseService<YtDeviceScriptEntity>{
  16 +
  17 + /**
  18 + * 查找脚本解析的脚本内容
  19 + * @param tenantId
  20 + * @param scriptId
  21 + * @return
  22 + */
  23 + String getScriptText(String tenantId, String scriptId);
  24 +
  25 + YtDeviceScriptDTO insertOrUpdate(YtDeviceScriptDTO deviceDTO);
  26 +
  27 + void deleteScriptes(String tenantId, Set<String> ids);
  28 +
  29 + void checkDeviceScriptes(String tenantId, Set<String> ids);
  30 +
  31 + Optional<YtDeviceScriptDTO> getDeviceScript(String tenantId, String id);
  32 +
  33 + YtPageData<YtDeviceScriptDTO> page(Integer page, Integer pageSize, String orderField, OrderTypeEnum orderType, String tenantId);
  34 +
  35 + List<YtDeviceScriptDTO> findDeviceScript(String tenantId);
  36 +
  37 +
  38 + /**
  39 + * 验证表单数据有效性
  40 + * @param scriptDTO
  41 + * @param created
  42 + * @return
  43 + */
  44 + boolean validateFormdata( YtDeviceScriptDTO scriptDTO,boolean created);
  45 +}
... ...
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3 +
  4 +<mapper namespace="org.thingsboard.server.dao.yunteng.mapper.YtDeviceProfileMapper">
  5 + <resultMap type="org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO" id="detail">
  6 + <result property="id" column="id"/>
  7 + <result property="createTime" column="create_time"/>
  8 + <result property="updateTime" column="update_time"/>
  9 + <result property="creator" column="creator"/>
  10 + <result property="updater" column="updater"/>
  11 + <result property="name" column="name"/>
  12 + <result property="image" column="image"/>
  13 + <result property="description" column="description"/>
  14 + <result property="tenantId" column="tenant_id"/>
  15 + <result property="scriptId" column="script_id"/>
  16 + <result property="transportType" column="transport_type"/>
  17 + <result property="provisionType" column="provision_type"/>
  18 + <result property="deviceType" column="device_type" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
  19 + <result property="tbProfileId" column="tb_profile_id"/>
  20 + <result property="profileData" column="profile_data" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
  21 + <result property="defaultQueueName" column="default_queue_name"/>
  22 + <result property="defaultRuleChainId" column="default_rule_chain_id"/>
  23 + <result property="default" column="is_default"/>
  24 + <result property="type" column="type"/>
  25 + </resultMap>
  26 +
  27 +
  28 + <sql id="basicColumns">
  29 + base.name,base.image,base.description,base.tenant_id,base.transport_type,base.provision_type,base.profile_data,base.default_queue_name,base.default_rule_chain_id,base.is_default,base.type
  30 + ,iot.id,iot.script_id,iot.device_type,iot.create_time,iot.update_time,iot.creator,iot.updater
  31 + </sql>
  32 +
  33 + <select id="getProfilePage" resultMap="detail">
  34 + SELECT
  35 + <include refid="basicColumns"/>
  36 + FROM device_profile base
  37 + LEFT JOIN iotfs_device_profile iot ON iot.tb_profile_id = base.id::TEXT
  38 + <where>
  39 + <if test="tenantId !=null and tenantId !=''">
  40 + AND base.tenant_id::TEXT = #{tenantId}
  41 + </if>
  42 + <if test="profileName !=null and profileName !=''">
  43 + AND base.name = #{profileName}
  44 + </if>
  45 + <if test="transportType !=null and transportType !=''">
  46 + AND base.transport_type = #{transportType}
  47 + </if>
  48 + </where>
  49 + </select>
  50 +
  51 +
  52 + <select id="selectDetail" resultMap="detail">
  53 + SELECT
  54 + <include refid="basicColumns"/>,d.customer_id::TEXT AS customer_id,cus.title AS cusotomer_name
  55 + FROM device_profile base
  56 + LEFT JOIN iotfs_device_profile iot ON iot.tb_profile_id = base.id::TEXT
  57 + <where>
  58 + <if test="tenantId !=null and tenantId !=''">
  59 + AND iot.tenant_id = #{tenantId}
  60 + </if>
  61 + <if test="id !=null and id !=''">
  62 + AND base.id = #{id}
  63 + </if>
  64 + </where>
  65 + </select>
  66 +</mapper>
... ...
... ... @@ -939,6 +939,11 @@
939 939 </dependency>
940 940 <dependency>
941 941 <groupId>org.thingsboard.common.transport</groupId>
  942 + <artifactId>tcp</artifactId>
  943 + <version>${project.version}</version>
  944 + </dependency>
  945 + <dependency>
  946 + <groupId>org.thingsboard.common.transport</groupId>
942 947 <artifactId>http</artifactId>
943 948 <version>${project.version}</version>
944 949 </dependency>
... ...
... ... @@ -41,7 +41,7 @@
41 41 <pkg.copyInstallScripts>false</pkg.copyInstallScripts>
42 42 <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
43 43 <pkg.implementationTitle>ThingsBoard MQTT Transport Service</pkg.implementationTitle>
44   - <pkg.mainClass>org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication</pkg.mainClass>
  44 + <pkg.mainClass>org.thingsboard.server.tcp.ThingsboardMqttTransportApplication</pkg.mainClass>
45 45 </properties>
46 46
47 47 <dependencies>
... ...
... ... @@ -36,6 +36,7 @@
36 36 <modules>
37 37 <module>http</module>
38 38 <module>mqtt</module>
  39 + <module>tcp</module>
39 40 <module>coap</module>
40 41 <module>lwm2m</module>
41 42 <module>snmp</module>
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2022 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  20 + <modelVersion>4.0.0</modelVersion>
  21 + <parent>
  22 + <groupId>org.thingsboard</groupId>
  23 + <version>3.3.4-SNAPSHOT</version>
  24 + <artifactId>transport</artifactId>
  25 + </parent>
  26 + <groupId>org.thingsboard.transport</groupId>
  27 + <artifactId>tcp</artifactId>
  28 + <packaging>jar</packaging>
  29 +
  30 + <name>Thingsboard TCP Transport Service</name>
  31 + <url>https://thingsboard.io</url>
  32 +
  33 + <properties>
  34 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  35 + <main.dir>${basedir}/../..</main.dir>
  36 + <pkg.type>java</pkg.type>
  37 + <pkg.disabled>false</pkg.disabled>
  38 + <pkg.process-resources.phase>process-resources</pkg.process-resources.phase>
  39 + <pkg.package.phase>package</pkg.package.phase>
  40 + <pkg.name>tb-tcp-transport</pkg.name>
  41 + <pkg.copyInstallScripts>false</pkg.copyInstallScripts>
  42 + <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
  43 + <pkg.implementationTitle>ThingsBoard TCP Transport Service</pkg.implementationTitle>
  44 + <pkg.mainClass>org.thingsboard.server.tcp.ThingsboardTcpTransportApplication</pkg.mainClass>
  45 + </properties>
  46 +
  47 + <dependencies>
  48 + <dependency>
  49 + <groupId>org.thingsboard.common.transport</groupId>
  50 + <artifactId>tcp</artifactId>
  51 + </dependency>
  52 + <dependency>
  53 + <groupId>org.thingsboard.common</groupId>
  54 + <artifactId>queue</artifactId>
  55 + </dependency>
  56 + <dependency>
  57 + <groupId>org.springframework.boot</groupId>
  58 + <artifactId>spring-boot-starter-web</artifactId>
  59 + </dependency>
  60 + <dependency>
  61 + <groupId>com.sun.winsw</groupId>
  62 + <artifactId>winsw</artifactId>
  63 + <classifier>bin</classifier>
  64 + <type>exe</type>
  65 + <scope>provided</scope>
  66 + </dependency>
  67 + <dependency>
  68 + <groupId>org.springframework.boot</groupId>
  69 + <artifactId>spring-boot-starter-test</artifactId>
  70 + <scope>test</scope>
  71 + </dependency>
  72 + <dependency>
  73 + <groupId>org.junit.vintage</groupId>
  74 + <artifactId>junit-vintage-engine</artifactId>
  75 + <scope>test</scope>
  76 + </dependency>
  77 + <dependency>
  78 + <groupId>org.awaitility</groupId>
  79 + <artifactId>awaitility</artifactId>
  80 + <scope>test</scope>
  81 + </dependency>
  82 + </dependencies>
  83 +
  84 + <build>
  85 + <finalName>${pkg.name}-${project.version}</finalName>
  86 + <resources>
  87 + <resource>
  88 + <directory>${project.basedir}/src/main/resources</directory>
  89 + </resource>
  90 + </resources>
  91 + <plugins>
  92 + <plugin>
  93 + <groupId>org.apache.maven.plugins</groupId>
  94 + <artifactId>maven-resources-plugin</artifactId>
  95 + </plugin>
  96 + <plugin>
  97 + <groupId>org.apache.maven.plugins</groupId>
  98 + <artifactId>maven-dependency-plugin</artifactId>
  99 + </plugin>
  100 + <plugin>
  101 + <groupId>org.apache.maven.plugins</groupId>
  102 + <artifactId>maven-jar-plugin</artifactId>
  103 + </plugin>
  104 + <plugin>
  105 + <groupId>org.springframework.boot</groupId>
  106 + <artifactId>spring-boot-maven-plugin</artifactId>
  107 + </plugin>
  108 + <plugin>
  109 + <groupId>org.thingsboard</groupId>
  110 + <artifactId>gradle-maven-plugin</artifactId>
  111 + </plugin>
  112 + <plugin>
  113 + <groupId>org.apache.maven.plugins</groupId>
  114 + <artifactId>maven-assembly-plugin</artifactId>
  115 + </plugin>
  116 + <plugin>
  117 + <groupId>org.apache.maven.plugins</groupId>
  118 + <artifactId>maven-install-plugin</artifactId>
  119 + </plugin>
  120 + </plugins>
  121 + </build>
  122 + <repositories>
  123 + <repository>
  124 + <id>jenkins</id>
  125 + <name>Jenkins Repository</name>
  126 + <url>https://repo.jenkins-ci.org/releases</url>
  127 + <snapshots>
  128 + <enabled>false</enabled>
  129 + </snapshots>
  130 + </repository>
  131 + </repositories>
  132 +</project>
... ...
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2022 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<!DOCTYPE configuration>
  20 +<configuration>
  21 +
  22 + <appender name="fileLogAppender"
  23 + class="ch.qos.logback.core.rolling.RollingFileAppender">
  24 + <file>${pkg.logFolder}/${pkg.name}.log</file>
  25 + <rollingPolicy
  26 + class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
  27 + <fileNamePattern>${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
  28 + <maxFileSize>100MB</maxFileSize>
  29 + <maxHistory>30</maxHistory>
  30 + <totalSizeCap>3GB</totalSizeCap>
  31 + </rollingPolicy>
  32 + <encoder>
  33 + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
  34 + </encoder>
  35 + </appender>
  36 +
  37 + <logger name="org.thingsboard.server" level="INFO" />
  38 +
  39 + <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
  40 +
  41 + <root level="INFO">
  42 + <appender-ref ref="fileLogAppender"/>
  43 + </root>
  44 +
  45 +</configuration>
... ...
  1 +#
  2 +# Copyright © 2016-2022 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  18 +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError"
  19 +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  20 +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
  21 +export LOG_FILENAME=${pkg.name}.out
  22 +export LOADER_PATH=${pkg.installFolder}/conf
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.tcp;
  17 +
  18 +import org.springframework.boot.SpringApplication;
  19 +import org.springframework.boot.SpringBootConfiguration;
  20 +import org.springframework.context.annotation.ComponentScan;
  21 +import org.springframework.scheduling.annotation.EnableAsync;
  22 +import org.springframework.scheduling.annotation.EnableScheduling;
  23 +
  24 +import java.util.Arrays;
  25 +
  26 +@SpringBootConfiguration
  27 +@EnableAsync
  28 +@EnableScheduling
  29 +@ComponentScan({"org.thingsboard.server.tcp", "org.thingsboard.server.common", "org.thingsboard.server.transport.tcp", "org.thingsboard.server.queue", "org.thingsboard.server.cache"})
  30 +public class ThingsboardTcpTransportApplication {
  31 +
  32 + private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
  33 + private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-tcp-transport";
  34 +
  35 + public static void main(String[] args) {
  36 + SpringApplication.run(ThingsboardTcpTransportApplication.class, updateArguments(args));
  37 + }
  38 +
  39 + private static String[] updateArguments(String[] args) {
  40 + if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) {
  41 + String[] modifiedArgs = new String[args.length + 1];
  42 + System.arraycopy(args, 0, modifiedArgs, 0, args.length);
  43 + modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM;
  44 + return modifiedArgs;
  45 + }
  46 + return args;
  47 + }
  48 +}
... ...
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2022 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<!DOCTYPE configuration>
  20 +<configuration scan="true" scanPeriod="10 seconds">
  21 +
  22 + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  23 + <encoder>
  24 + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
  25 + </encoder>
  26 + </appender>
  27 +
  28 + <logger name="org.thingsboard.server" level="TRACE" />
  29 +
  30 + <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
  31 +
  32 + <root level="INFO">
  33 + <appender-ref ref="STDOUT"/>
  34 + </root>
  35 +
  36 +</configuration>
\ No newline at end of file
... ...
  1 +#
  2 +# Copyright © 2016-2022 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +# If you enabled process metrics you should also enable 'web-environment'.
  18 +spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
  19 +# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
  20 +spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
  21 +
  22 +server:
  23 + # Server bind address (has no effect if web-environment is disabled).
  24 + address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
  25 + # Server bind port (has no effect if web-environment is disabled).
  26 + port: "${HTTP_BIND_PORT:8083}"
  27 +
  28 +# Zookeeper connection parameters. Used for service discovery.
  29 +zk:
  30 + # Enable/disable zookeeper discovery service.
  31 + enabled: "${ZOOKEEPER_ENABLED:true}"
  32 + # Zookeeper connect string
  33 + url: "${ZOOKEEPER_URL:47.99.141.212: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:redis@6379}"
  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:47.99.141.212: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}'
... ...
... ... @@ -44,6 +44,7 @@ export enum DeviceProfileType {
44 44 export enum DeviceTransportType {
45 45 DEFAULT = 'DEFAULT',
46 46 MQTT = 'MQTT',
  47 + TCP = 'TCP',
47 48 COAP = 'COAP',
48 49 LWM2M = 'LWM2M',
49 50 SNMP = 'SNMP'
... ... @@ -99,6 +100,7 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st
99 100 [
100 101 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default'],
101 102 [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt'],
  103 + [DeviceTransportType.TCP, 'device-profile.transport-type-tcp'],
102 104 [DeviceTransportType.COAP, 'device-profile.transport-type-coap'],
103 105 [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m'],
104 106 [DeviceTransportType.SNMP, 'device-profile.transport-type-snmp']
... ... @@ -118,6 +120,7 @@ export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>(
118 120 [
119 121 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'],
120 122 [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'],
  123 + [DeviceTransportType.TCP, 'device-profile.transport-type-mqtt-hint'],
121 124 [DeviceTransportType.COAP, 'device-profile.transport-type-coap-hint'],
122 125 [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'],
123 126 [DeviceTransportType.SNMP, 'device-profile.transport-type-snmp-hint']
... ... @@ -202,6 +205,13 @@ export const deviceTransportTypeConfigurationInfoMap = new Map<DeviceTransportTy
202 205 }
203 206 ],
204 207 [
  208 + DeviceTransportType.TCP,
  209 + {
  210 + hasProfileConfiguration: true,
  211 + hasDeviceConfiguration: false,
  212 + }
  213 + ],
  214 + [
205 215 DeviceTransportType.LWM2M,
206 216 {
207 217 hasProfileConfiguration: true,
... ... @@ -250,6 +260,11 @@ export interface MqttDeviceProfileTransportConfiguration {
250 260 [key: string]: any;
251 261 }
252 262
  263 +//Thingskit function
  264 +export interface YtTcpDeviceProfileTransportConfiguration {
  265 + [key: string]: any;
  266 +}
  267 +
253 268 export interface CoapClientSetting {
254 269 powerMode?: PowerMode | null;
255 270 edrxCycle?: number;
... ... @@ -306,6 +321,7 @@ export interface SnmpMapping {
306 321
307 322 export type DeviceProfileTransportConfigurations = DefaultDeviceProfileTransportConfiguration &
308 323 MqttDeviceProfileTransportConfiguration &
  324 + YtTcpDeviceProfileTransportConfiguration &
309 325 CoapDeviceProfileTransportConfiguration &
310 326 Lwm2mDeviceProfileTransportConfiguration &
311 327 SnmpDeviceProfileTransportConfiguration;
... ... @@ -366,6 +382,10 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT
366 382 };
367 383 transportConfiguration = {...mqttTransportConfiguration, type: DeviceTransportType.MQTT};
368 384 break;
  385 + case DeviceTransportType.TCP:
  386 + const tcpTransportConfiguration: YtTcpDeviceProfileTransportConfiguration = {};
  387 + transportConfiguration = {...tcpTransportConfiguration, type: DeviceTransportType.TCP};
  388 + break;
369 389 case DeviceTransportType.COAP:
370 390 const coapTransportConfiguration: CoapDeviceProfileTransportConfiguration = {
371 391 coapDeviceTypeConfiguration: {
... ... @@ -411,6 +431,10 @@ export function createDeviceTransportConfiguration(type: DeviceTransportType): D
411 431 const mqttTransportConfiguration: MqttDeviceTransportConfiguration = {};
412 432 transportConfiguration = {...mqttTransportConfiguration, type: DeviceTransportType.MQTT};
413 433 break;
  434 + case DeviceTransportType.TCP:
  435 + const tcpTransportConfiguration: YtTcpDeviceTransportConfiguration = {};
  436 + transportConfiguration = {...tcpTransportConfiguration, type: DeviceTransportType.TCP};
  437 + break;
414 438 case DeviceTransportType.COAP:
415 439 const coapTransportConfiguration: CoapDeviceTransportConfiguration = {
416 440 powerMode: null
... ... @@ -600,6 +624,10 @@ export interface MqttDeviceTransportConfiguration {
600 624 [key: string]: any;
601 625 }
602 626
  627 +export interface YtTcpDeviceTransportConfiguration {
  628 + [key: string]: any;
  629 +}
  630 +
603 631 export interface CoapDeviceTransportConfiguration {
604 632 powerMode?: PowerMode | null;
605 633 edrxCycle?: number;
... ... @@ -725,6 +753,9 @@ export const credentialTypesByTransportType = new Map<DeviceTransportType, Devic
725 753 [DeviceTransportType.MQTT, [
726 754 DeviceCredentialsType.ACCESS_TOKEN, DeviceCredentialsType.X509_CERTIFICATE, DeviceCredentialsType.MQTT_BASIC
727 755 ]],
  756 + [DeviceTransportType.TCP, [
  757 + DeviceCredentialsType.ACCESS_TOKEN
  758 + ]],
728 759 [DeviceTransportType.COAP, [DeviceCredentialsType.ACCESS_TOKEN, DeviceCredentialsType.X509_CERTIFICATE]],
729 760 [DeviceTransportType.LWM2M, [DeviceCredentialsType.LWM2M_CREDENTIALS]],
730 761 [DeviceTransportType.SNMP, [DeviceCredentialsType.ACCESS_TOKEN]]
... ...
... ... @@ -999,6 +999,7 @@
999 999 "transport-type-lwm2m": "LWM2M",
1000 1000 "transport-type-lwm2m-hint": "LWM2M传输类型",
1001 1001 "transport-type-mqtt": "MQTT",
  1002 + "transport-type-tcp": "TCP",
1002 1003 "transport-type-mqtt-hint": "启用高级MQTT传输设置",
1003 1004 "transport-type-required": "传输方式必填。",
1004 1005 "transport-type-snmp-hint": "指定 SNMP 传输配置",
... ...