Commit 9d89fc70564a58cc0ee9ffc4a7f928da6437aaa7
Merge branch '20221019' into 'master'
refactor: 脚本引擎模板调整 See merge request huang/thingsboard3.3.2!139
Showing
45 changed files
with
4541 additions
and
65 deletions
Too many changes to show.
To preserve performance only 45 of 75 files are displayed.
@@ -83,6 +83,10 @@ | @@ -83,6 +83,10 @@ | ||
83 | </dependency> | 83 | </dependency> |
84 | <dependency> | 84 | <dependency> |
85 | <groupId>org.thingsboard.common.transport</groupId> | 85 | <groupId>org.thingsboard.common.transport</groupId> |
86 | + <artifactId>tcp</artifactId> | ||
87 | + </dependency> | ||
88 | + <dependency> | ||
89 | + <groupId>org.thingsboard.common.transport</groupId> | ||
86 | <artifactId>http</artifactId> | 90 | <artifactId>http</artifactId> |
87 | </dependency> | 91 | </dependency> |
88 | <dependency> | 92 | <dependency> |
@@ -28,30 +28,13 @@ import org.springframework.security.core.Authentication; | @@ -28,30 +28,13 @@ import org.springframework.security.core.Authentication; | ||
28 | import org.springframework.security.core.context.SecurityContextHolder; | 28 | import org.springframework.security.core.context.SecurityContextHolder; |
29 | import org.springframework.web.bind.annotation.ExceptionHandler; | 29 | import org.springframework.web.bind.annotation.ExceptionHandler; |
30 | import org.thingsboard.server.cluster.TbClusterService; | 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 | import org.thingsboard.server.common.data.alarm.Alarm; | 32 | import org.thingsboard.server.common.data.alarm.Alarm; |
51 | import org.thingsboard.server.common.data.alarm.AlarmInfo; | 33 | import org.thingsboard.server.common.data.alarm.AlarmInfo; |
52 | import org.thingsboard.server.common.data.asset.Asset; | 34 | import org.thingsboard.server.common.data.asset.Asset; |
53 | import org.thingsboard.server.common.data.asset.AssetInfo; | 35 | import org.thingsboard.server.common.data.asset.AssetInfo; |
54 | import org.thingsboard.server.common.data.audit.ActionType; | 36 | import org.thingsboard.server.common.data.audit.ActionType; |
37 | +import org.thingsboard.server.common.data.device.profile.*; | ||
55 | import org.thingsboard.server.common.data.edge.Edge; | 38 | import org.thingsboard.server.common.data.edge.Edge; |
56 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; | 39 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
57 | import org.thingsboard.server.common.data.edge.EdgeEventType; | 40 | import org.thingsboard.server.common.data.edge.EdgeEventType; |
@@ -919,4 +902,29 @@ public abstract class BaseController { | @@ -919,4 +902,29 @@ public abstract class BaseController { | ||
919 | return MediaType.APPLICATION_OCTET_STREAM; | 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,6 +59,7 @@ public class YtAdminController extends BaseController { | ||
59 | private final UserService tbUserService; | 59 | private final UserService tbUserService; |
60 | 60 | ||
61 | @PostMapping("/tenant") | 61 | @PostMapping("/tenant") |
62 | + @Deprecated | ||
62 | @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{'api:yt:admin:tenant:post'})") | 63 | @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{'api:yt:admin:tenant:post'})") |
63 | public ResponseEntity<TenantDTO> saveTenant(@RequestBody TenantReqDTO tenantReqDTO) { | 64 | public ResponseEntity<TenantDTO> saveTenant(@RequestBody TenantReqDTO tenantReqDTO) { |
64 | TenantDTO newTenant = ytTenantService.createNewTenant(tenantReqDTO); | 65 | TenantDTO newTenant = ytTenantService.createNewTenant(tenantReqDTO); |
@@ -13,10 +13,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; | @@ -13,10 +13,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; | ||
13 | import org.thingsboard.server.common.data.DeviceProfileType; | 13 | import org.thingsboard.server.common.data.DeviceProfileType; |
14 | import org.thingsboard.server.common.data.DeviceTransportType; | 14 | import org.thingsboard.server.common.data.DeviceTransportType; |
15 | import org.thingsboard.server.common.data.audit.ActionType; | 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 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; | 17 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
21 | import org.thingsboard.server.common.data.exception.ThingsboardException; | 18 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
22 | import org.thingsboard.server.common.data.id.DeviceProfileId; | 19 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
@@ -24,6 +21,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; | @@ -24,6 +21,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; | ||
24 | import org.thingsboard.server.common.data.id.TenantId; | 21 | import org.thingsboard.server.common.data.id.TenantId; |
25 | import org.thingsboard.server.common.data.page.PageLink; | 22 | import org.thingsboard.server.common.data.page.PageLink; |
26 | import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | 23 | import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
24 | +import org.thingsboard.server.common.data.rule.RuleChain; | ||
27 | import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; | 25 | import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; |
28 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; | 26 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; |
29 | import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException; | 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,6 +32,7 @@ import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum; | ||
34 | import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; | 32 | import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; |
35 | import org.thingsboard.server.common.msg.queue.ServiceQueue; | 33 | import org.thingsboard.server.common.msg.queue.ServiceQueue; |
36 | import org.thingsboard.server.controller.BaseController; | 34 | import org.thingsboard.server.controller.BaseController; |
35 | +import org.thingsboard.server.dao.yunteng.service.YtDeviceScriptService; | ||
37 | import org.thingsboard.server.dao.yunteng.service.YtDeviceProfileService; | 36 | import org.thingsboard.server.dao.yunteng.service.YtDeviceProfileService; |
38 | import org.thingsboard.server.service.security.permission.Operation; | 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,7 +50,7 @@ import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant. | ||
51 | @Api(tags = {"设备配置管理"}) | 50 | @Api(tags = {"设备配置管理"}) |
52 | public class YtDeviceProfileController extends BaseController { | 51 | public class YtDeviceProfileController extends BaseController { |
53 | private final YtDeviceProfileService ytDeviceProfileService; | 52 | private final YtDeviceProfileService ytDeviceProfileService; |
54 | - | 53 | + private final YtDeviceScriptService javaScriptService; |
55 | @PostMapping() | 54 | @PostMapping() |
56 | @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:post','api:yt:deviceProfile:update'})") | 55 | @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:post','api:yt:deviceProfile:update'})") |
57 | @ApiOperation("创建 | 编辑") | 56 | @ApiOperation("创建 | 编辑") |
@@ -66,11 +65,14 @@ public class YtDeviceProfileController extends BaseController { | @@ -66,11 +65,14 @@ public class YtDeviceProfileController extends BaseController { | ||
66 | * 2/3.处理TB业务逻辑 | 65 | * 2/3.处理TB业务逻辑 |
67 | * 3/3.处理业务平台的业务逻辑 | 66 | * 3/3.处理业务平台的业务逻辑 |
68 | */ | 67 | */ |
69 | - | ||
70 | - deviceProfileDTO.setTenantId(getCurrentUser().getCurrentTenantId()); | 68 | + String tenantId = getCurrentUser().getCurrentTenantId(); |
69 | + deviceProfileDTO.setTenantId(tenantId); | ||
71 | DeviceProfile tbDeviceProfile = buildTbDeviceProfileFromDeviceProfileDTO(deviceProfileDTO); | 70 | DeviceProfile tbDeviceProfile = buildTbDeviceProfileFromDeviceProfileDTO(deviceProfileDTO); |
71 | + | ||
72 | updateTbDeviceProfile(tbDeviceProfile, created); | 72 | updateTbDeviceProfile(tbDeviceProfile, created); |
73 | 73 | ||
74 | + ytDeviceProfileService.insertOrUpdate(deviceProfileDTO); | ||
75 | + | ||
74 | return ResponseEntity.ok(deviceProfileDTO); | 76 | return ResponseEntity.ok(deviceProfileDTO); |
75 | } | 77 | } |
76 | 78 | ||
@@ -130,9 +132,7 @@ public class YtDeviceProfileController extends BaseController { | @@ -130,9 +132,7 @@ public class YtDeviceProfileController extends BaseController { | ||
130 | @RequestParam(value = "transportType", required = false) String transportType, | 132 | @RequestParam(value = "transportType", required = false) String transportType, |
131 | @RequestParam(value = ORDER_FILED, required = false) String orderBy, | 133 | @RequestParam(value = ORDER_FILED, required = false) String orderBy, |
132 | @RequestParam(value = ORDER_TYPE, required = false) OrderTypeEnum orderType) throws ThingsboardException { | 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 | @GetMapping("/me/list") | 138 | @GetMapping("/me/list") |
@@ -146,11 +146,13 @@ public class YtDeviceProfileController extends BaseController { | @@ -146,11 +146,13 @@ public class YtDeviceProfileController extends BaseController { | ||
146 | @ApiOperation("删除") | 146 | @ApiOperation("删除") |
147 | @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:delete'})") | 147 | @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:delete'})") |
148 | public void deleteDevices(@Validated({DeleteGroup.class}) @RequestBody DeleteDTO deleteDTO) throws ThingsboardException { | 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 | for (String id : deleteDTO.getIds()) { | 152 | for (String id : deleteDTO.getIds()) { |
152 | deleteTbDeviceProfile(id); | 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,33 +219,50 @@ public class YtDeviceProfileController extends BaseController { | ||
217 | tbDeviceProfile.setTenantId(TenantId.fromUUID(tenantId)); | 219 | tbDeviceProfile.setTenantId(TenantId.fromUUID(tenantId)); |
218 | tbDeviceProfile.setDefault(deviceProfileDTO.isDefault()); | 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 | if (StringUtils.isNotBlank(deviceProfileDTO.getDefaultRuleChainId())) { | 234 | if (StringUtils.isNotBlank(deviceProfileDTO.getDefaultRuleChainId())) { |
222 | - UUID chainId = UUID.fromString(deviceProfileDTO.getDefaultRuleChainId()); | ||
223 | tbDeviceProfile.setDefaultRuleChainId(new RuleChainId(chainId)); | 235 | tbDeviceProfile.setDefaultRuleChainId(new RuleChainId(chainId)); |
224 | } | 236 | } |
225 | 237 | ||
226 | tbDeviceProfile.setDefaultQueueName(ServiceQueue.MAIN); | 238 | tbDeviceProfile.setDefaultQueueName(ServiceQueue.MAIN); |
227 | tbDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); | 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 | // 传输类型默认都是Default | 242 | // 传输类型默认都是Default |
234 | String transportType = deviceProfileDTO.getTransportType(); | 243 | String transportType = deviceProfileDTO.getTransportType(); |
244 | + String scriptText=null; | ||
235 | if(transportType ==null || DeviceTransportType.DEFAULT.name().equals(transportType)){ | 245 | if(transportType ==null || DeviceTransportType.DEFAULT.name().equals(transportType)){ |
236 | tbDeviceProfile.setTransportType(DeviceTransportType.DEFAULT); | 246 | tbDeviceProfile.setTransportType(DeviceTransportType.DEFAULT); |
237 | - deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); | ||
238 | }else{ | 247 | }else{ |
239 | tbDeviceProfile.setTransportType(DeviceTransportType.valueOf(transportType)); | 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 | if(deviceProfileDTO.getProfileData()!=null | 261 | if(deviceProfileDTO.getProfileData()!=null |
244 | && deviceProfileDTO.getProfileData().getAlarms() !=null){ | 262 | && deviceProfileDTO.getProfileData().getAlarms() !=null){ |
245 | deviceProfileData.setAlarms(deviceProfileDTO.getProfileData().getAlarms()); | 263 | deviceProfileData.setAlarms(deviceProfileDTO.getProfileData().getAlarms()); |
246 | } | 264 | } |
265 | + | ||
247 | tbDeviceProfile.setProfileData(deviceProfileData); | 266 | tbDeviceProfile.setProfileData(deviceProfileData); |
248 | 267 | ||
249 | return tbDeviceProfile; | 268 | return tbDeviceProfile; |
application/src/main/java/org/thingsboard/server/controller/yunteng/YtDeviceScriptController.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
@@ -176,7 +176,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { | @@ -176,7 +176,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { | ||
176 | 176 | ||
177 | @Override | 177 | @Override |
178 | public void createSysAdmin() { | 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 | @Override | 182 | @Override |
@@ -170,9 +170,9 @@ ui: | @@ -170,9 +170,9 @@ ui: | ||
170 | database: | 170 | database: |
171 | ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records | 171 | ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records |
172 | ts: | 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 | ts_latest: | 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 | # note: timescale works only with postgreSQL database for DATABASE_ENTITIES_TYPE. | 177 | # note: timescale works only with postgreSQL database for DATABASE_ENTITIES_TYPE. |
178 | 178 | ||
@@ -539,9 +539,9 @@ spring: | @@ -539,9 +539,9 @@ spring: | ||
539 | database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" | 539 | database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" |
540 | datasource: | 540 | datasource: |
541 | driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" | 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 | username: "${SPRING_DATASOURCE_USERNAME:postgres}" | 543 | username: "${SPRING_DATASOURCE_USERNAME:postgres}" |
544 | - password: "${SPRING_DATASOURCE_PASSWORD:Vrr861!@waja}" | 544 | + password: "${SPRING_DATASOURCE_PASSWORD:Xga751++nqnk}" |
545 | # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://101.133.234.90:28776/thingsboard-3.3.2}" | 545 | # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://101.133.234.90:28776/thingsboard-3.3.2}" |
546 | # username: "${SPRING_DATASOURCE_USERNAME:postgres}" | 546 | # username: "${SPRING_DATASOURCE_USERNAME:postgres}" |
547 | # password: "${SPRING_DATASOURCE_PASSWORD:Bua312!!iwcw}" | 547 | # password: "${SPRING_DATASOURCE_PASSWORD:Bua312!!iwcw}" |
@@ -708,6 +708,59 @@ transport: | @@ -708,6 +708,59 @@ transport: | ||
708 | # Skip certificate validity check for client certificates. | 708 | # Skip certificate validity check for client certificates. |
709 | skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" | 709 | skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" |
710 | # Local CoAP transport parameters | 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 | coap: | 764 | coap: |
712 | # Enable/disable coap transport protocol. | 765 | # Enable/disable coap transport protocol. |
713 | enabled: "${COAP_ENABLED:true}" | 766 | enabled: "${COAP_ENABLED:true}" |
@@ -1143,8 +1196,8 @@ file: | @@ -1143,8 +1196,8 @@ file: | ||
1143 | staticUrl: /oss/files/** #oss静态访问路径 只有type = local需要 | 1196 | staticUrl: /oss/files/** #oss静态访问路径 只有type = local需要 |
1144 | randomFileName: ${file.storage.randomFileName} | 1197 | randomFileName: ${file.storage.randomFileName} |
1145 | minio: | 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 | minioName: ${MINIO_NAME:YunTeng} #minio账户 | 1201 | minioName: ${MINIO_NAME:YunTeng} #minio账户 |
1149 | minioPass: ${MINIO_PWD:YunTeng123456} #minio访问密码 | 1202 | minioPass: ${MINIO_PWD:YunTeng123456} #minio访问密码 |
1150 | bucketName: yunteng #minio储存桶名称 | 1203 | bucketName: yunteng #minio储存桶名称 |
@@ -43,6 +43,8 @@ public class DataConstants { | @@ -43,6 +43,8 @@ public class DataConstants { | ||
43 | public static final String COAP_TRANSPORT_NAME = "COAP"; | 43 | public static final String COAP_TRANSPORT_NAME = "COAP"; |
44 | public static final String LWM2M_TRANSPORT_NAME = "LWM2M"; | 44 | public static final String LWM2M_TRANSPORT_NAME = "LWM2M"; |
45 | public static final String MQTT_TRANSPORT_NAME = "MQTT"; | 45 | public static final String MQTT_TRANSPORT_NAME = "MQTT"; |
46 | + //Thingskit function | ||
47 | + public static final String TCP_TRANSPORT_NAME = "TCP"; | ||
46 | public static final String HTTP_TRANSPORT_NAME = "HTTP"; | 48 | public static final String HTTP_TRANSPORT_NAME = "HTTP"; |
47 | public static final String SNMP_TRANSPORT_NAME = "SNMP"; | 49 | public static final String SNMP_TRANSPORT_NAME = "SNMP"; |
48 | 50 |
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.data; | @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data; | ||
18 | public enum DeviceTransportType { | 18 | public enum DeviceTransportType { |
19 | DEFAULT, | 19 | DEFAULT, |
20 | MQTT, | 20 | MQTT, |
21 | + TCP, | ||
21 | COAP, | 22 | COAP, |
22 | LWM2M, | 23 | LWM2M, |
23 | SNMP | 24 | SNMP |
@@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; | @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; | ||
21 | import com.fasterxml.jackson.annotation.JsonTypeInfo; | 21 | import com.fasterxml.jackson.annotation.JsonTypeInfo; |
22 | import io.swagger.annotations.ApiModel; | 22 | import io.swagger.annotations.ApiModel; |
23 | import org.thingsboard.server.common.data.DeviceTransportType; | 23 | import org.thingsboard.server.common.data.DeviceTransportType; |
24 | +import org.thingsboard.server.common.data.device.profile.YtTcpDeviceProfileTransportConfiguration; | ||
24 | 25 | ||
25 | import java.io.Serializable; | 26 | import java.io.Serializable; |
26 | 27 | ||
@@ -33,6 +34,7 @@ import java.io.Serializable; | @@ -33,6 +34,7 @@ import java.io.Serializable; | ||
33 | @JsonSubTypes({ | 34 | @JsonSubTypes({ |
34 | @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"), | 35 | @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"), |
35 | @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"), | 36 | @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"), |
37 | + @JsonSubTypes.Type(value = YtTcpDeviceTransportConfiguration.class, name = "TCP"), | ||
36 | @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP"), | 38 | @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP"), |
37 | @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"), | 39 | @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"), |
38 | @JsonSubTypes.Type(value = SnmpDeviceTransportConfiguration.class, name = "SNMP")}) | 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,6 +31,7 @@ import java.io.Serializable; | ||
31 | @JsonSubTypes({ | 31 | @JsonSubTypes({ |
32 | @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), | 32 | @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), |
33 | @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), | 33 | @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), |
34 | + @JsonSubTypes.Type(value = YtTcpDeviceProfileTransportConfiguration.class, name = "TCP"), | ||
34 | @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"), | 35 | @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"), |
35 | @JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP"), | 36 | @JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP"), |
36 | @JsonSubTypes.Type(value = SnmpDeviceProfileTransportConfiguration.class, name = "SNMP") | 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,6 +49,11 @@ public final class ModelConstants { | ||
49 | "iotfs_user_organization_mapping"; | 49 | "iotfs_user_organization_mapping"; |
50 | /** 设备表 */ | 50 | /** 设备表 */ |
51 | public static final String IOTFS_DEVICE_TABLE_NAME = "iotfs_device"; | 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 | public static final String IOTFS_DEVICE_TYPE_TABLE_NAME = "iotfs_device_type"; | 58 | public static final String IOTFS_DEVICE_TYPE_TABLE_NAME = "iotfs_device_type"; |
54 | /** 告警配置表 */ | 59 | /** 告警配置表 */ |
@@ -81,7 +81,9 @@ public enum ErrorMessage { | @@ -81,7 +81,9 @@ public enum ErrorMessage { | ||
81 | DATA_BOARD_IS_PRIVATE(400057,"该数据看板不是公有视图"), | 81 | DATA_BOARD_IS_PRIVATE(400057,"该数据看板不是公有视图"), |
82 | MESSAGE_SEND_TOO_FAST(400058,"1分钟内请求次数过多,请休息一下!"), | 82 | MESSAGE_SEND_TOO_FAST(400058,"1分钟内请求次数过多,请休息一下!"), |
83 | PASSWORD_INCORRECT(4010059, "密码错误!"), | 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 | HAVE_NO_PERMISSION(500002,"没有修改权限"); | 87 | HAVE_NO_PERMISSION(500002,"没有修改权限"); |
86 | private final int code; | 88 | private final int code; |
87 | private String message; | 89 | private String message; |
@@ -7,11 +7,15 @@ import lombok.EqualsAndHashCode; | @@ -7,11 +7,15 @@ import lombok.EqualsAndHashCode; | ||
7 | import org.thingsboard.server.common.data.DeviceProfileType; | 7 | import org.thingsboard.server.common.data.DeviceProfileType; |
8 | import org.thingsboard.server.common.data.DeviceTransportType; | 8 | import org.thingsboard.server.common.data.DeviceTransportType; |
9 | import org.thingsboard.server.common.data.device.profile.DeviceProfileData; | 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 | import org.thingsboard.server.common.data.yunteng.common.AddGroup; | 12 | import org.thingsboard.server.common.data.yunteng.common.AddGroup; |
13 | +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | ||
11 | import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil; | 14 | import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil; |
12 | 15 | ||
13 | import javax.validation.Valid; | 16 | import javax.validation.Valid; |
14 | import javax.validation.constraints.NotEmpty; | 17 | import javax.validation.constraints.NotEmpty; |
18 | +import javax.validation.constraints.NotNull; | ||
15 | import java.time.Instant; | 19 | import java.time.Instant; |
16 | import java.time.LocalDateTime; | 20 | import java.time.LocalDateTime; |
17 | import java.time.ZoneId; | 21 | import java.time.ZoneId; |
@@ -21,27 +25,38 @@ import java.util.UUID; | @@ -21,27 +25,38 @@ import java.util.UUID; | ||
21 | 25 | ||
22 | @EqualsAndHashCode(callSuper = true) | 26 | @EqualsAndHashCode(callSuper = true) |
23 | @Data | 27 | @Data |
24 | -public class DeviceProfileDTO extends TenantDTO { | 28 | +public class DeviceProfileDTO extends BaseDTO { |
25 | @NotEmpty(message = "设备配置名称不能为空或者空字符串", groups = AddGroup.class) | 29 | @NotEmpty(message = "设备配置名称不能为空或者空字符串", groups = AddGroup.class) |
26 | - @ApiModelProperty(value = "设备配置名称") | 30 | + @ApiModelProperty(value = "产品(设备配置)名称") |
27 | private String name; | 31 | private String name; |
28 | 32 | ||
29 | private String description; | 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 | @NotEmpty(message = "传输协议不能为空或者空字符串", groups = AddGroup.class) | 42 | @NotEmpty(message = "传输协议不能为空或者空字符串", groups = AddGroup.class) |
36 | @ApiModelProperty(value = "传输协议", required = true) | 43 | @ApiModelProperty(value = "传输协议", required = true) |
37 | - @NotEmpty(message = "传输协议不能为空或者二空字符串") | ||
38 | private String transportType; | 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 | * TB的设备配置文件 | 53 | * TB的设备配置文件 |
41 | */ | 54 | */ |
42 | @Deprecated | 55 | @Deprecated |
43 | private String tbProfileId; | 56 | private String tbProfileId; |
44 | 57 | ||
58 | + | ||
59 | + | ||
45 | @Valid | 60 | @Valid |
46 | private transient DeviceProfileData profileData; | 61 | private transient DeviceProfileData profileData; |
47 | @ApiModelProperty(value = "关联规则链,默认关联根规则链", required = false) | 62 | @ApiModelProperty(value = "关联规则链,默认关联根规则链", required = false) |
@@ -76,17 +91,11 @@ public class DeviceProfileDTO extends TenantDTO { | @@ -76,17 +91,11 @@ public class DeviceProfileDTO extends TenantDTO { | ||
76 | this.profileData = JacksonUtil.convertValue(profileData, DeviceProfileData.class); | 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 | this.name = name; | 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 | } |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/YtDeviceScriptDTO.java
0 → 100644
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,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,6 +37,7 @@ | ||
37 | <modules> | 37 | <modules> |
38 | <module>transport-api</module> | 38 | <module>transport-api</module> |
39 | <module>mqtt</module> | 39 | <module>mqtt</module> |
40 | + <module>tcp</module> | ||
40 | <module>http</module> | 41 | <module>http</module> |
41 | <module>coap</module> | 42 | <module>coap</module> |
42 | <module>lwm2m</module> | 43 | <module>lwm2m</module> |
common/transport/tcp/pom.xml
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2022 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
19 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
20 | + <modelVersion>4.0.0</modelVersion> | ||
21 | + <parent> | ||
22 | + <groupId>org.thingsboard.common</groupId> | ||
23 | + <version>3.3.4-SNAPSHOT</version> | ||
24 | + <artifactId>transport</artifactId> | ||
25 | + </parent> | ||
26 | + <groupId>org.thingsboard.common.transport</groupId> | ||
27 | + <artifactId>tcp</artifactId> | ||
28 | + <packaging>jar</packaging> | ||
29 | + | ||
30 | + <name>Thingsboard 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> |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpSslHandlerProvider.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp; | ||
17 | + | ||
18 | +import io.netty.handler.ssl.SslHandler; | ||
19 | +import lombok.extern.slf4j.Slf4j; | ||
20 | +import org.springframework.beans.factory.annotation.Autowired; | ||
21 | +import org.springframework.beans.factory.annotation.Qualifier; | ||
22 | +import org.springframework.beans.factory.annotation.Value; | ||
23 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
24 | +import org.springframework.boot.context.properties.ConfigurationProperties; | ||
25 | +import org.springframework.context.annotation.Bean; | ||
26 | +import org.springframework.stereotype.Component; | ||
27 | +import org.springframework.util.StringUtils; | ||
28 | +import org.thingsboard.server.common.data.DeviceTransportType; | ||
29 | +import org.thingsboard.server.common.msg.EncryptionUtil; | ||
30 | +import org.thingsboard.server.common.transport.TransportService; | ||
31 | +import org.thingsboard.server.common.transport.TransportServiceCallback; | ||
32 | +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; | ||
33 | +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; | ||
34 | +import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; | ||
35 | +import org.thingsboard.server.common.transport.util.SslUtil; | ||
36 | +import org.thingsboard.server.gen.transport.TransportProtos; | ||
37 | + | ||
38 | +import javax.net.ssl.KeyManager; | ||
39 | +import javax.net.ssl.KeyManagerFactory; | ||
40 | +import javax.net.ssl.SSLContext; | ||
41 | +import javax.net.ssl.SSLEngine; | ||
42 | +import javax.net.ssl.TrustManager; | ||
43 | +import javax.net.ssl.TrustManagerFactory; | ||
44 | +import javax.net.ssl.X509TrustManager; | ||
45 | +import java.security.cert.CertificateEncodingException; | ||
46 | +import java.security.cert.CertificateException; | ||
47 | +import java.security.cert.X509Certificate; | ||
48 | +import java.util.concurrent.CountDownLatch; | ||
49 | +import java.util.concurrent.TimeUnit; | ||
50 | + | ||
51 | +/** | ||
52 | + * Created by valerii.sosliuk on 11/6/16. | ||
53 | + */ | ||
54 | +@Slf4j | ||
55 | +@Component("TcpSslHandlerProvider") | ||
56 | +@ConditionalOnProperty(prefix = "transport.tcp.ssl", value = "enabled", havingValue = "true", matchIfMissing = false) | ||
57 | +public class TcpSslHandlerProvider { | ||
58 | + | ||
59 | + @Value("${transport.tcp.ssl.protocol}") | ||
60 | + private String sslProtocol; | ||
61 | + | ||
62 | + @Autowired | ||
63 | + private TransportService transportService; | ||
64 | + | ||
65 | + @Bean | ||
66 | + @ConfigurationProperties(prefix = "transport.tcp.ssl.credentials") | ||
67 | + public SslCredentialsConfig tcpSslCredentials() { | ||
68 | + return new SslCredentialsConfig("tcp SSL Credentials", false); | ||
69 | + } | ||
70 | + | ||
71 | + @Autowired | ||
72 | + @Qualifier("tcpSslCredentials") | ||
73 | + private SslCredentialsConfig tcpSslCredentialsConfig; | ||
74 | + | ||
75 | + private SSLContext sslContext; | ||
76 | + | ||
77 | + public SslHandler getSslHandler() { | ||
78 | + if (sslContext == null) { | ||
79 | + sslContext = createSslContext(); | ||
80 | + } | ||
81 | + SSLEngine sslEngine = sslContext.createSSLEngine(); | ||
82 | + sslEngine.setUseClientMode(false); | ||
83 | + sslEngine.setNeedClientAuth(false); | ||
84 | + sslEngine.setWantClientAuth(true); | ||
85 | + sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols()); | ||
86 | + sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites()); | ||
87 | + sslEngine.setEnableSessionCreation(true); | ||
88 | + return new SslHandler(sslEngine); | ||
89 | + } | ||
90 | + | ||
91 | + private SSLContext createSslContext() { | ||
92 | + try { | ||
93 | + SslCredentials sslCredentials = this.tcpSslCredentialsConfig.getCredentials(); | ||
94 | + TrustManagerFactory tmFactory = sslCredentials.createTrustManagerFactory(); | ||
95 | + KeyManagerFactory kmf = sslCredentials.createKeyManagerFactory(); | ||
96 | + | ||
97 | + KeyManager[] km = kmf.getKeyManagers(); | ||
98 | + TrustManager x509wrapped = getX509TrustManager(tmFactory); | ||
99 | + TrustManager[] tm = {x509wrapped}; | ||
100 | + if (StringUtils.isEmpty(sslProtocol)) { | ||
101 | + sslProtocol = "TLS"; | ||
102 | + } | ||
103 | + SSLContext sslContext = SSLContext.getInstance(sslProtocol); | ||
104 | + sslContext.init(km, tm, null); | ||
105 | + return sslContext; | ||
106 | + } catch (Exception e) { | ||
107 | + log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e); | ||
108 | + throw new RuntimeException("Failed to get SSL context", e); | ||
109 | + } | ||
110 | + } | ||
111 | + | ||
112 | + private TrustManager getX509TrustManager(TrustManagerFactory tmf) throws Exception { | ||
113 | + X509TrustManager x509Tm = null; | ||
114 | + for (TrustManager tm : tmf.getTrustManagers()) { | ||
115 | + if (tm instanceof X509TrustManager) { | ||
116 | + x509Tm = (X509TrustManager) tm; | ||
117 | + break; | ||
118 | + } | ||
119 | + } | ||
120 | + return new ThingsboardTcpX509TrustManager(x509Tm, transportService); | ||
121 | + } | ||
122 | + | ||
123 | + static class ThingsboardTcpX509TrustManager implements X509TrustManager { | ||
124 | + | ||
125 | + private final X509TrustManager trustManager; | ||
126 | + private TransportService transportService; | ||
127 | + | ||
128 | + ThingsboardTcpX509TrustManager(X509TrustManager trustManager, TransportService transportService) { | ||
129 | + this.trustManager = trustManager; | ||
130 | + this.transportService = transportService; | ||
131 | + } | ||
132 | + | ||
133 | + @Override | ||
134 | + public X509Certificate[] getAcceptedIssuers() { | ||
135 | + return trustManager.getAcceptedIssuers(); | ||
136 | + } | ||
137 | + | ||
138 | + @Override | ||
139 | + public void checkServerTrusted(X509Certificate[] chain, | ||
140 | + String authType) throws CertificateException { | ||
141 | + trustManager.checkServerTrusted(chain, authType); | ||
142 | + } | ||
143 | + | ||
144 | + @Override | ||
145 | + public void checkClientTrusted(X509Certificate[] chain, | ||
146 | + String authType) throws CertificateException { | ||
147 | + String credentialsBody = null; | ||
148 | + for (X509Certificate cert : chain) { | ||
149 | + try { | ||
150 | + String strCert = SslUtil.getCertificateString(cert); | ||
151 | + String sha3Hash = EncryptionUtil.getSha3Hash(strCert); | ||
152 | + final String[] credentialsBodyHolder = new String[1]; | ||
153 | + CountDownLatch latch = new CountDownLatch(1); | ||
154 | + transportService.process(DeviceTransportType.TCP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), | ||
155 | + new TransportServiceCallback<ValidateDeviceCredentialsResponse>() { | ||
156 | + @Override | ||
157 | + public void onSuccess(ValidateDeviceCredentialsResponse msg) { | ||
158 | + if (!StringUtils.isEmpty(msg.getCredentials())) { | ||
159 | + credentialsBodyHolder[0] = msg.getCredentials(); | ||
160 | + } | ||
161 | + latch.countDown(); | ||
162 | + } | ||
163 | + | ||
164 | + @Override | ||
165 | + public void onError(Throwable e) { | ||
166 | + log.error(e.getMessage(), e); | ||
167 | + latch.countDown(); | ||
168 | + } | ||
169 | + }); | ||
170 | + latch.await(10, TimeUnit.SECONDS); | ||
171 | + if (strCert.equals(credentialsBodyHolder[0])) { | ||
172 | + credentialsBody = credentialsBodyHolder[0]; | ||
173 | + break; | ||
174 | + } | ||
175 | + } catch (InterruptedException | CertificateEncodingException e) { | ||
176 | + log.error(e.getMessage(), e); | ||
177 | + } | ||
178 | + } | ||
179 | + if (credentialsBody == null) { | ||
180 | + throw new CertificateException("Invalid Device Certificate"); | ||
181 | + } | ||
182 | + } | ||
183 | + } | ||
184 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpTransportContext.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp; | ||
17 | + | ||
18 | +import io.netty.handler.ssl.SslHandler; | ||
19 | +import lombok.Getter; | ||
20 | +import lombok.Setter; | ||
21 | +import lombok.extern.slf4j.Slf4j; | ||
22 | +import org.springframework.beans.factory.annotation.Autowired; | ||
23 | +import org.springframework.beans.factory.annotation.Value; | ||
24 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | ||
25 | +import org.springframework.stereotype.Component; | ||
26 | +import org.thingsboard.server.common.transport.TransportContext; | ||
27 | +import org.thingsboard.server.transport.tcp.adaptors.JsonTcpAdaptor; | ||
28 | + | ||
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 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpTransportHandler.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp; | ||
17 | + | ||
18 | +import com.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 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpTransportService.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp; | ||
17 | + | ||
18 | +import io.netty.bootstrap.ServerBootstrap; | ||
19 | +import io.netty.channel.Channel; | ||
20 | +import io.netty.channel.ChannelOption; | ||
21 | +import io.netty.channel.EventLoopGroup; | ||
22 | +import io.netty.channel.nio.NioEventLoopGroup; | ||
23 | +import io.netty.channel.socket.nio.NioServerSocketChannel; | ||
24 | +import io.netty.util.AttributeKey; | ||
25 | +import io.netty.util.ResourceLeakDetector; | ||
26 | +import lombok.extern.slf4j.Slf4j; | ||
27 | +import org.springframework.beans.factory.annotation.Autowired; | ||
28 | +import org.springframework.beans.factory.annotation.Value; | ||
29 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | ||
30 | +import org.springframework.stereotype.Service; | ||
31 | +import org.thingsboard.server.common.data.DataConstants; | ||
32 | +import org.thingsboard.server.common.data.TbTransportService; | ||
33 | + | ||
34 | +import javax.annotation.PostConstruct; | ||
35 | +import javax.annotation.PreDestroy; | ||
36 | +import java.net.InetSocketAddress; | ||
37 | + | ||
38 | +/** | ||
39 | + * @author Andrew Shvayka | ||
40 | + */ | ||
41 | +@Service("TcpTransportService") | ||
42 | +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.tcp.enabled}'=='true')") | ||
43 | +@Slf4j | ||
44 | +public class TcpTransportService implements TbTransportService { | ||
45 | + | ||
46 | + public static AttributeKey<InetSocketAddress> ADDRESS = AttributeKey.newInstance("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 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/adaptors/JsonTcpAdaptor.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp.adaptors; | ||
17 | + | ||
18 | +import com.google.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 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/limits/IpFilter.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp.limits; | ||
17 | + | ||
18 | +import io.netty.channel.ChannelHandlerContext; | ||
19 | +import io.netty.handler.ipfilter.AbstractRemoteAddressFilter; | ||
20 | +import lombok.extern.slf4j.Slf4j; | ||
21 | +import org.thingsboard.server.transport.tcp.TcpTransportContext; | ||
22 | +import org.thingsboard.server.transport.tcp.TcpTransportService; | ||
23 | + | ||
24 | +import java.net.InetSocketAddress; | ||
25 | + | ||
26 | +@Slf4j | ||
27 | +public class IpFilter extends AbstractRemoteAddressFilter<InetSocketAddress> { | ||
28 | + | ||
29 | + private TcpTransportContext context; | ||
30 | + | ||
31 | + public IpFilter(TcpTransportContext context) { | ||
32 | + this.context = context; | ||
33 | + } | ||
34 | + | ||
35 | + @Override | ||
36 | + protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception { | ||
37 | + log.trace("[{}] Received msg: {}", ctx.channel().id(), remoteAddress); | ||
38 | + if(context.checkAddress(remoteAddress)){ | ||
39 | + log.trace("[{}] Setting address: {}", ctx.channel().id(), remoteAddress); | ||
40 | + ctx.channel().attr(TcpTransportService.ADDRESS).set(remoteAddress); | ||
41 | + return true; | ||
42 | + } else { | ||
43 | + return false; | ||
44 | + } | ||
45 | + } | ||
46 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/limits/ProxyIpFilter.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp.limits; | ||
17 | + | ||
18 | +import io.netty.channel.ChannelHandler; | ||
19 | +import io.netty.channel.ChannelHandlerContext; | ||
20 | +import io.netty.channel.ChannelInboundHandlerAdapter; | ||
21 | +import io.netty.handler.codec.haproxy.HAProxyMessage; | ||
22 | +import lombok.extern.slf4j.Slf4j; | ||
23 | +import org.thingsboard.server.transport.tcp.TcpTransportContext; | ||
24 | +import org.thingsboard.server.transport.tcp.TcpTransportService; | ||
25 | + | ||
26 | +import java.net.InetSocketAddress; | ||
27 | + | ||
28 | +@Slf4j | ||
29 | +public class ProxyIpFilter extends ChannelInboundHandlerAdapter { | ||
30 | + | ||
31 | + | ||
32 | + private TcpTransportContext context; | ||
33 | + | ||
34 | + public ProxyIpFilter(TcpTransportContext context) { | ||
35 | + this.context = context; | ||
36 | + } | ||
37 | + | ||
38 | + @Override | ||
39 | + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { | ||
40 | + log.trace("[{}] Received msg: {}", ctx.channel().id(), msg); | ||
41 | + if (msg instanceof HAProxyMessage) { | ||
42 | + HAProxyMessage proxyMsg = (HAProxyMessage) msg; | ||
43 | + if (proxyMsg.sourceAddress() != null && proxyMsg.sourcePort() > 0) { | ||
44 | + InetSocketAddress address = new InetSocketAddress(proxyMsg.sourceAddress(), proxyMsg.sourcePort()); | ||
45 | + if (!context.checkAddress(address)) { | ||
46 | + closeChannel(ctx); | ||
47 | + } else { | ||
48 | + log.trace("[{}] Setting address: {}", ctx.channel().id(), address); | ||
49 | + ctx.channel().attr(TcpTransportService.ADDRESS).set(address); | ||
50 | + // We no longer need this channel in the pipeline. Similar to HAProxyMessageDecoder | ||
51 | + ctx.pipeline().remove(this); | ||
52 | + } | ||
53 | + } else { | ||
54 | + log.trace("Received local health-check connection message: {}", proxyMsg); | ||
55 | + closeChannel(ctx); | ||
56 | + } | ||
57 | + } | ||
58 | + } | ||
59 | + | ||
60 | + private void closeChannel(ChannelHandlerContext ctx) { | ||
61 | + while (ctx.pipeline().last() != this) { | ||
62 | + ChannelHandler handler = ctx.pipeline().removeLast(); | ||
63 | + if (handler instanceof ChannelInboundHandlerAdapter) { | ||
64 | + try { | ||
65 | + ((ChannelInboundHandlerAdapter) handler).channelUnregistered(ctx); | ||
66 | + } catch (Exception e) { | ||
67 | + log.error("Failed to unregister channel: [{}]", ctx, e); | ||
68 | + } | ||
69 | + } | ||
70 | + | ||
71 | + } | ||
72 | + ctx.pipeline().remove(this); | ||
73 | + ctx.close(); | ||
74 | + } | ||
75 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * <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 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/session/TCPMessage.java
0 → 100644
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 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/ByteUtils.java
0 → 100644
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 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/EqualsTopicFilter.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp.util; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +@Data | ||
21 | +public class EqualsTopicFilter implements TcpTopicFilter { | ||
22 | + | ||
23 | + private final String filter; | ||
24 | + | ||
25 | + @Override | ||
26 | + public boolean filter(String topic) { | ||
27 | + return filter.equals(topic); | ||
28 | + } | ||
29 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp.util; | ||
17 | + | ||
18 | +import lombok.extern.slf4j.Slf4j; | ||
19 | +import org.thingsboard.server.common.data.device.profile.MqttTopics; | ||
20 | + | ||
21 | +import java.util.concurrent.ConcurrentHashMap; | ||
22 | +import java.util.concurrent.ConcurrentMap; | ||
23 | + | ||
24 | +@Slf4j | ||
25 | +public class MqttTopicFilterFactory { | ||
26 | + | ||
27 | + private static final ConcurrentMap<String, TcpTopicFilter> filters = new ConcurrentHashMap<>(); | ||
28 | + private static final TcpTopicFilter DEFAULT_TELEMETRY_TOPIC_FILTER = toFilter(MqttTopics.DEVICE_TELEMETRY_TOPIC); | ||
29 | + private static final TcpTopicFilter DEFAULT_ATTRIBUTES_TOPIC_FILTER = toFilter(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); | ||
30 | + | ||
31 | + public static TcpTopicFilter toFilter(String topicFilter) { | ||
32 | + if (topicFilter == null || topicFilter.isEmpty()) { | ||
33 | + throw new IllegalArgumentException("Topic filter can't be empty!"); | ||
34 | + } | ||
35 | + return filters.computeIfAbsent(topicFilter, filter -> { | ||
36 | + if (filter.equals("#")) { | ||
37 | + return new AlwaysTrueTopicFilter(); | ||
38 | + } else if (filter.contains("+") || filter.contains("#")) { | ||
39 | + String regex = filter | ||
40 | + .replace("\\", "\\\\") | ||
41 | + .replace("+", "[^/]+") | ||
42 | + .replace("/#", "($|/.*)"); | ||
43 | + log.debug("Converting [{}] to [{}]", filter, regex); | ||
44 | + return new RegexTopicFilter(regex); | ||
45 | + } else { | ||
46 | + return new EqualsTopicFilter(filter); | ||
47 | + } | ||
48 | + }); | ||
49 | + } | ||
50 | + | ||
51 | + public static TcpTopicFilter getDefaultTelemetryFilter() { | ||
52 | + return DEFAULT_TELEMETRY_TOPIC_FILTER; | ||
53 | + } | ||
54 | + | ||
55 | + public static TcpTopicFilter getDefaultAttributesFilter() { | ||
56 | + return DEFAULT_ATTRIBUTES_TOPIC_FILTER; | ||
57 | + } | ||
58 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/RegexTopicFilter.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp.util; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +import java.util.regex.Pattern; | ||
21 | + | ||
22 | +@Data | ||
23 | +public class RegexTopicFilter implements TcpTopicFilter { | ||
24 | + | ||
25 | + private final Pattern regex; | ||
26 | + | ||
27 | + public RegexTopicFilter(String regex) { | ||
28 | + this.regex = Pattern.compile(regex); | ||
29 | + } | ||
30 | + | ||
31 | + @Override | ||
32 | + public boolean filter(String topic) { | ||
33 | + return regex.matcher(topic).matches(); | ||
34 | + } | ||
35 | +} |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/TcpTopicFilter.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2022 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.tcp.util; | ||
17 | + | ||
18 | +public interface TcpTopicFilter { | ||
19 | + | ||
20 | + boolean filter(String topic); | ||
21 | + | ||
22 | +} |
@@ -137,6 +137,11 @@ | @@ -137,6 +137,11 @@ | ||
137 | <groupId>org.bouncycastle</groupId> | 137 | <groupId>org.bouncycastle</groupId> |
138 | <artifactId>bcpkix-jdk15on</artifactId> | 138 | <artifactId>bcpkix-jdk15on</artifactId> |
139 | </dependency> | 139 | </dependency> |
140 | + | ||
141 | + <dependency> | ||
142 | + <groupId>org.javadelight</groupId> | ||
143 | + <artifactId>delight-nashorn-sandbox</artifactId> | ||
144 | + </dependency> | ||
140 | </dependencies> | 145 | </dependencies> |
141 | 146 | ||
142 | <build> | 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 | +} |