Showing
64 changed files
with
1480 additions
and
1943 deletions
Too many changes to show.
To preserve performance only 64 of 252 files are displayed.
1 | +{ | |
2 | + "providerId": "Apple", | |
3 | + "additionalInfo": null, | |
4 | + "accessTokenUri": "https://appleid.apple.com/auth/token", | |
5 | + "authorizationUri": "https://appleid.apple.com/auth/authorize?response_mode=form_post", | |
6 | + "scope": ["email","openid","name"], | |
7 | + "jwkSetUri": "https://appleid.apple.com/auth/keys", | |
8 | + "userInfoUri": null, | |
9 | + "clientAuthenticationMethod": "POST", | |
10 | + "userNameAttributeName": "email", | |
11 | + "mapperConfig": { | |
12 | + "type": "APPLE", | |
13 | + "basic": { | |
14 | + "emailAttributeKey": "email", | |
15 | + "firstNameAttributeKey": "firstName", | |
16 | + "lastNameAttributeKey": "lastName", | |
17 | + "tenantNameStrategy": "DOMAIN" | |
18 | + } | |
19 | + }, | |
20 | + "comment": null, | |
21 | + "loginButtonIcon": "apple-logo", | |
22 | + "loginButtonLabel": "Apple", | |
23 | + "helpLink": "https://developer.apple.com/sign-in-with-apple/get-started/" | |
24 | +} | ... | ... |
... | ... | @@ -18,8 +18,8 @@ |
18 | 18 | "resources": [], |
19 | 19 | "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>", |
20 | 20 | "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n", |
21 | - "controllerScript": "var requestTimeout = 500;\nvar multiParams = false;\nvar useRowStyleFunction = false;\nvar styleObj = {};\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.multiParams) {\n multiParams = self.ctx.settings.multiParams;\n }\n if (self.ctx.settings.useRowStyleFunction && self.ctx.settings.rowStyleFunction) {\n try {\n var style = self.ctx.settings.rowStyleFunction;\n styleObj = JSON.parse(style);\n if ((typeof styleObj !== \"object\")) {\n styleObj = null;\n throw new URIError(`${style === null ? 'null' : typeof style} instead of style object`);\n }\n else if (typeof styleObj === \"object\" && (typeof styleObj.length) === \"number\") {\n styleObj = null;\n throw new URIError('Array instead of style object');\n }\n }\n catch (e) {\n console.log(`Row style function in widget ` +\n `returns '${e}'. Please check your row style function.`); \n }\n useRowStyleFunction = self.ctx.settings.useRowStyleFunction;\n \n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args) {\n if (!multiParams && cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n }\n else {\n if (cmdObj.args.length) {\n var params = getMultiParams(cmdObj.args);\n }\n performRpc(this, cmdObj.name, params, requestUUID);\n }\n }\n \n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (styleObj && styleObj !== null) {\n terminal.css(styleObj);\n }\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1 (multiParams===false):]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2 (multiParams===false):]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\":2,\\\\\"key2\\\\\":\\\\\"myVal\\\\\"}\"\\n\\n'; \n commandsListText += '[[b;#fff;]Example 3 (multiParams===true)]\\n'; \n commandsListText += ' <method> [params body] = \"all the string after the method, including spaces\"]\\n';\n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": \"battery level\", \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\nfunction getMultiParams(cmdObj) {\n var params = \"\";\n cmdObj.forEach((element) => {\n try {\n params += \" \" + JSON.strigify(JSON.parse(element));\n } catch (e) {\n params += \" \" + element;\n }\n })\n return params.trim();\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n}", | |
22 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"multiParams\": {\n \"title\": \"RPC params All line\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"multiParams\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
21 | + "controllerScript": "var requestTimeout = 500;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n}", | |
22 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}", | |
23 | 23 | "dataKeySettingsSchema": "{}\n", |
24 | 24 | "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" |
25 | 25 | } |
... | ... | @@ -151,4 +151,4 @@ |
151 | 151 | } |
152 | 152 | } |
153 | 153 | ] |
154 | -} | |
\ No newline at end of file | ||
154 | +} | ... | ... |
... | ... | @@ -92,7 +92,7 @@ CREATE TABLE IF NOT EXISTS oauth2_registration ( |
92 | 92 | created_time bigint NOT NULL, |
93 | 93 | additional_info varchar, |
94 | 94 | client_id varchar(255), |
95 | - client_secret varchar(255), | |
95 | + client_secret varchar(2048), | |
96 | 96 | authorization_uri varchar(255), |
97 | 97 | token_uri varchar(255), |
98 | 98 | scope varchar(255), |
... | ... | @@ -136,7 +136,7 @@ CREATE TABLE IF NOT EXISTS oauth2_mobile ( |
136 | 136 | oauth2_params_id uuid NOT NULL, |
137 | 137 | created_time bigint NOT NULL, |
138 | 138 | pkg_name varchar(255), |
139 | - callback_url_scheme varchar(255), | |
139 | + app_secret varchar(2048), | |
140 | 140 | CONSTRAINT fk_mobile_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE, |
141 | 141 | CONSTRAINT oauth2_mobile_unq_key UNIQUE (oauth2_params_id, pkg_name) |
142 | 142 | ); | ... | ... |
... | ... | @@ -39,6 +39,7 @@ import org.springframework.web.util.UriComponentsBuilder; |
39 | 39 | import org.thingsboard.server.dao.oauth2.OAuth2Configuration; |
40 | 40 | import org.thingsboard.server.dao.oauth2.OAuth2Service; |
41 | 41 | import org.thingsboard.server.service.security.auth.oauth2.TbOAuth2ParameterNames; |
42 | +import org.thingsboard.server.service.security.model.token.OAuth2AppTokenFactory; | |
42 | 43 | import org.thingsboard.server.utils.MiscUtils; |
43 | 44 | |
44 | 45 | import javax.servlet.http.HttpServletRequest; |
... | ... | @@ -69,6 +70,9 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza |
69 | 70 | @Autowired |
70 | 71 | private OAuth2Service oAuth2Service; |
71 | 72 | |
73 | + @Autowired | |
74 | + private OAuth2AppTokenFactory oAuth2AppTokenFactory; | |
75 | + | |
72 | 76 | @Autowired(required = false) |
73 | 77 | private OAuth2Configuration oauth2Configuration; |
74 | 78 | |
... | ... | @@ -78,7 +82,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza |
78 | 82 | String registrationId = this.resolveRegistrationId(request); |
79 | 83 | String redirectUriAction = getAction(request, "login"); |
80 | 84 | String appPackage = getAppPackage(request); |
81 | - return resolve(request, registrationId, redirectUriAction, appPackage); | |
85 | + String appToken = getAppToken(request); | |
86 | + return resolve(request, registrationId, redirectUriAction, appPackage, appToken); | |
82 | 87 | } |
83 | 88 | |
84 | 89 | @Override |
... | ... | @@ -88,7 +93,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza |
88 | 93 | } |
89 | 94 | String redirectUriAction = getAction(request, "authorize"); |
90 | 95 | String appPackage = getAppPackage(request); |
91 | - return resolve(request, registrationId, redirectUriAction, appPackage); | |
96 | + String appToken = getAppToken(request); | |
97 | + return resolve(request, registrationId, redirectUriAction, appPackage, appToken); | |
92 | 98 | } |
93 | 99 | |
94 | 100 | private String getAction(HttpServletRequest request, String defaultAction) { |
... | ... | @@ -103,8 +109,12 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza |
103 | 109 | return request.getParameter("pkg"); |
104 | 110 | } |
105 | 111 | |
112 | + private String getAppToken(HttpServletRequest request) { | |
113 | + return request.getParameter("appToken"); | |
114 | + } | |
115 | + | |
106 | 116 | @SuppressWarnings("deprecation") |
107 | - private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction, String appPackage) { | |
117 | + private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction, String appPackage, String appToken) { | |
108 | 118 | if (registrationId == null) { |
109 | 119 | return null; |
110 | 120 | } |
... | ... | @@ -117,10 +127,14 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza |
117 | 127 | Map<String, Object> attributes = new HashMap<>(); |
118 | 128 | attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); |
119 | 129 | if (!StringUtils.isEmpty(appPackage)) { |
120 | - String callbackUrlScheme = this.oAuth2Service.findCallbackUrlScheme(UUID.fromString(registrationId), appPackage); | |
121 | - if (StringUtils.isEmpty(callbackUrlScheme)) { | |
122 | - throw new IllegalArgumentException("Invalid package: " + appPackage + ". No package info found for Client Registration."); | |
130 | + if (StringUtils.isEmpty(appToken)) { | |
131 | + throw new IllegalArgumentException("Invalid application token."); | |
123 | 132 | } else { |
133 | + String appSecret = this.oAuth2Service.findAppSecret(UUID.fromString(registrationId), appPackage); | |
134 | + if (StringUtils.isEmpty(appSecret)) { | |
135 | + throw new IllegalArgumentException("Invalid package: " + appPackage + ". No application secret found for Client Registration with given application package."); | |
136 | + } | |
137 | + String callbackUrlScheme = this.oAuth2AppTokenFactory.validateTokenAndGetCallbackUrlScheme(appPackage, appToken, appSecret); | |
124 | 138 | attributes.put(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME, callbackUrlScheme); |
125 | 139 | } |
126 | 140 | } | ... | ... |
... | ... | @@ -782,15 +782,17 @@ public class DeviceController extends BaseController { |
782 | 782 | } |
783 | 783 | |
784 | 784 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
785 | - @RequestMapping(value = "/devices/count/{otaPackageType}", method = RequestMethod.GET) | |
785 | + @RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET) | |
786 | 786 | @ResponseBody |
787 | - public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType, | |
788 | - @RequestParam String deviceProfileId) throws ThingsboardException { | |
787 | + public Long countByDeviceProfileAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType, | |
788 | + @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException { | |
789 | 789 | checkParameter("OtaPackageType", otaPackageType); |
790 | 790 | checkParameter("DeviceProfileId", deviceProfileId); |
791 | 791 | try { |
792 | 792 | return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage( |
793 | - getCurrentUser().getTenantId(), new DeviceProfileId(UUID.fromString(deviceProfileId)), OtaPackageType.valueOf(otaPackageType)); | |
793 | + getTenantId(), | |
794 | + new DeviceProfileId(UUID.fromString(deviceProfileId)), | |
795 | + OtaPackageType.valueOf(otaPackageType)); | |
794 | 796 | } catch (Exception e) { |
795 | 797 | throw handleException(e); |
796 | 798 | } | ... | ... |
... | ... | @@ -17,7 +17,6 @@ package org.thingsboard.server.controller; |
17 | 17 | |
18 | 18 | import com.fasterxml.jackson.databind.ObjectMapper; |
19 | 19 | import lombok.extern.slf4j.Slf4j; |
20 | -import org.eclipse.leshan.core.SecurityMode; | |
21 | 20 | import org.springframework.security.access.prepost.PreAuthorize; |
22 | 21 | import org.springframework.web.bind.annotation.PathVariable; |
23 | 22 | import org.springframework.web.bind.annotation.RequestBody; |
... | ... | @@ -45,14 +44,11 @@ import java.util.Map; |
45 | 44 | public class Lwm2mController extends BaseController { |
46 | 45 | |
47 | 46 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
48 | - @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET) | |
47 | + @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{isBootstrapServer}", method = RequestMethod.GET) | |
49 | 48 | @ResponseBody |
50 | - public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String strSecurityMode, | |
51 | - @PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException { | |
52 | - checkNotNull(strSecurityMode); | |
49 | + public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("isBootstrapServer") boolean bootstrapServer) throws ThingsboardException { | |
53 | 50 | try { |
54 | - SecurityMode securityMode = SecurityMode.valueOf(strSecurityMode); | |
55 | - return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer); | |
51 | + return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(bootstrapServer); | |
56 | 52 | } catch (Exception e) { |
57 | 53 | throw handleException(e); |
58 | 54 | } | ... | ... |
... | ... | @@ -128,7 +128,7 @@ public class OtaPackageController extends BaseController { |
128 | 128 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
129 | 129 | @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST) |
130 | 130 | @ResponseBody |
131 | - public OtaPackage saveOtaPackageData(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId, | |
131 | + public OtaPackageInfo saveOtaPackageData(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId, | |
132 | 132 | @RequestParam(required = false) String checksum, |
133 | 133 | @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, |
134 | 134 | @RequestBody MultipartFile file) throws ThingsboardException { |
... | ... | @@ -160,7 +160,7 @@ public class OtaPackageController extends BaseController { |
160 | 160 | otaPackage.setContentType(file.getContentType()); |
161 | 161 | otaPackage.setData(ByteBuffer.wrap(bytes)); |
162 | 162 | otaPackage.setDataSize((long) bytes.length); |
163 | - OtaPackage savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage); | |
163 | + OtaPackageInfo savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage); | |
164 | 164 | logEntityAction(savedOtaPackage.getId(), savedOtaPackage, null, ActionType.UPDATED, null); |
165 | 165 | return savedOtaPackage; |
166 | 166 | } catch (Exception e) { | ... | ... |
... | ... | @@ -74,6 +74,7 @@ import java.util.List; |
74 | 74 | import java.util.Map; |
75 | 75 | import java.util.Set; |
76 | 76 | import java.util.concurrent.ConcurrentMap; |
77 | +import java.util.concurrent.TimeUnit; | |
77 | 78 | import java.util.stream.Collectors; |
78 | 79 | |
79 | 80 | @Slf4j |
... | ... | @@ -86,6 +87,7 @@ public class RuleChainController extends BaseController { |
86 | 87 | public static final String RULE_NODE_ID = "ruleNodeId"; |
87 | 88 | |
88 | 89 | private static final ObjectMapper objectMapper = new ObjectMapper(); |
90 | + public static final int TIMEOUT = 20; | |
89 | 91 | |
90 | 92 | @Autowired |
91 | 93 | private InstallScripts installScripts; |
... | ... | @@ -388,25 +390,25 @@ public class RuleChainController extends BaseController { |
388 | 390 | TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); |
389 | 391 | switch (scriptType) { |
390 | 392 | case "update": |
391 | - output = msgToOutput(engine.executeUpdate(inMsg)); | |
393 | + output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); | |
392 | 394 | break; |
393 | 395 | case "generate": |
394 | - output = msgToOutput(engine.executeGenerate(inMsg)); | |
396 | + output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); | |
395 | 397 | break; |
396 | 398 | case "filter": |
397 | - boolean result = engine.executeFilter(inMsg); | |
399 | + boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); | |
398 | 400 | output = Boolean.toString(result); |
399 | 401 | break; |
400 | 402 | case "switch": |
401 | - Set<String> states = engine.executeSwitch(inMsg); | |
403 | + Set<String> states = engine.executeSwitchAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); | |
402 | 404 | output = objectMapper.writeValueAsString(states); |
403 | 405 | break; |
404 | 406 | case "json": |
405 | - JsonNode json = engine.executeJson(inMsg); | |
407 | + JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); | |
406 | 408 | output = objectMapper.writeValueAsString(json); |
407 | 409 | break; |
408 | 410 | case "string": |
409 | - output = engine.executeToString(inMsg); | |
411 | + output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); | |
410 | 412 | break; |
411 | 413 | default: |
412 | 414 | throw new IllegalArgumentException("Unsupported script type: " + scriptType); | ... | ... |
... | ... | @@ -18,7 +18,6 @@ package org.thingsboard.server.service.lwm2m; |
18 | 18 | |
19 | 19 | import lombok.RequiredArgsConstructor; |
20 | 20 | import lombok.extern.slf4j.Slf4j; |
21 | -import org.eclipse.leshan.core.SecurityMode; | |
22 | 21 | import org.eclipse.leshan.core.util.Hex; |
23 | 22 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
24 | 23 | import org.springframework.stereotype.Service; |
... | ... | @@ -50,40 +49,20 @@ public class LwM2MServerSecurityInfoRepository { |
50 | 49 | private final LwM2MTransportServerConfig serverConfig; |
51 | 50 | private final LwM2MTransportBootstrapConfig bootstrapConfig; |
52 | 51 | |
53 | - /** | |
54 | - * @param securityMode | |
55 | - * @param bootstrapServer | |
56 | - * @return ServerSecurityConfig more value is default: Important - port, host, publicKey | |
57 | - */ | |
58 | - public ServerSecurityConfig getServerSecurityInfo(SecurityMode securityMode, boolean bootstrapServer) { | |
59 | - ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, securityMode); | |
52 | + public ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer) { | |
53 | + ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig); | |
60 | 54 | result.setBootstrapServerIs(bootstrapServer); |
61 | 55 | return result; |
62 | 56 | } |
63 | 57 | |
64 | - private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, SecurityMode securityMode) { | |
58 | + private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig) { | |
65 | 59 | ServerSecurityConfig bsServ = new ServerSecurityConfig(); |
66 | 60 | bsServ.setServerId(serverConfig.getId()); |
67 | - switch (securityMode) { | |
68 | - case NO_SEC: | |
69 | - bsServ.setHost(serverConfig.getHost()); | |
70 | - bsServ.setPort(serverConfig.getPort()); | |
71 | - bsServ.setServerPublicKey(""); | |
72 | - break; | |
73 | - case PSK: | |
74 | - bsServ.setHost(serverConfig.getSecureHost()); | |
75 | - bsServ.setPort(serverConfig.getSecurePort()); | |
76 | - bsServ.setServerPublicKey(""); | |
77 | - break; | |
78 | - case RPK: | |
79 | - case X509: | |
80 | - bsServ.setHost(serverConfig.getSecureHost()); | |
81 | - bsServ.setPort(serverConfig.getSecurePort()); | |
82 | - bsServ.setServerPublicKey(getPublicKey(serverConfig.getCertificateAlias(), this.serverConfig.getPublicX(), this.serverConfig.getPublicY())); | |
83 | - break; | |
84 | - default: | |
85 | - break; | |
86 | - } | |
61 | + bsServ.setHost(serverConfig.getHost()); | |
62 | + bsServ.setPort(serverConfig.getPort()); | |
63 | + bsServ.setSecurityHost(serverConfig.getSecureHost()); | |
64 | + bsServ.setSecurityPort(serverConfig.getSecurePort()); | |
65 | + bsServ.setServerPublicKey(getPublicKey(serverConfig.getCertificateAlias(), this.serverConfig.getPublicX(), this.serverConfig.getPublicY())); | |
87 | 66 | return bsServ; |
88 | 67 | } |
89 | 68 | ... | ... |
... | ... | @@ -30,6 +30,7 @@ import java.util.UUID; |
30 | 30 | import java.util.concurrent.ConcurrentHashMap; |
31 | 31 | import java.util.concurrent.Executors; |
32 | 32 | import java.util.concurrent.ScheduledExecutorService; |
33 | +import java.util.concurrent.TimeoutException; | |
33 | 34 | import java.util.concurrent.atomic.AtomicInteger; |
34 | 35 | |
35 | 36 | /** |
... | ... | @@ -84,8 +85,10 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { |
84 | 85 | apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); |
85 | 86 | return doInvokeFunction(scriptId, functionName, args); |
86 | 87 | } else { |
87 | - return Futures.immediateFailedFuture( | |
88 | - new RuntimeException("Script invocation is blocked due to maximum error count " + getMaxErrors() + "!")); | |
88 | + String message = "Script invocation is blocked due to maximum error count " | |
89 | + + getMaxErrors() + ", scriptId " + scriptId + "!"; | |
90 | + log.warn(message); | |
91 | + return Futures.immediateFailedFuture(new RuntimeException(message)); | |
89 | 92 | } |
90 | 93 | } else { |
91 | 94 | return Futures.immediateFailedFuture(new RuntimeException("JS Execution is disabled due to API limits!")); |
... | ... | @@ -117,8 +120,11 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { |
117 | 120 | |
118 | 121 | protected abstract long getMaxBlacklistDuration(); |
119 | 122 | |
120 | - protected void onScriptExecutionError(UUID scriptId) { | |
121 | - disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()).incrementAndGet(); | |
123 | + protected void onScriptExecutionError(UUID scriptId, Throwable t, String scriptBody) { | |
124 | + DisableListInfo disableListInfo = disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()); | |
125 | + log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}, scriptBody {}", | |
126 | + disableListInfo.get(), scriptId, t, t.getCause(), scriptBody); | |
127 | + disableListInfo.incrementAndGet(); | |
122 | 128 | } |
123 | 129 | |
124 | 130 | private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) { | ... | ... |
... | ... | @@ -160,7 +160,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer |
160 | 160 | return ((Invocable) engine).invokeFunction(functionName, args); |
161 | 161 | } |
162 | 162 | } catch (Exception e) { |
163 | - onScriptExecutionError(scriptId); | |
163 | + onScriptExecutionError(scriptId, e, functionName); | |
164 | 164 | throw new ExecutionException(e); |
165 | 165 | } |
166 | 166 | }); | ... | ... |
... | ... | @@ -18,7 +18,6 @@ package org.thingsboard.server.service.script; |
18 | 18 | import com.google.common.util.concurrent.FutureCallback; |
19 | 19 | import com.google.common.util.concurrent.Futures; |
20 | 20 | import com.google.common.util.concurrent.ListenableFuture; |
21 | -import com.google.common.util.concurrent.MoreExecutors; | |
22 | 21 | import lombok.Getter; |
23 | 22 | import lombok.extern.slf4j.Slf4j; |
24 | 23 | import org.springframework.beans.factory.annotation.Autowired; |
... | ... | @@ -26,6 +25,7 @@ import org.springframework.beans.factory.annotation.Value; |
26 | 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
27 | 26 | import org.springframework.scheduling.annotation.Scheduled; |
28 | 27 | import org.springframework.stereotype.Service; |
28 | +import org.springframework.util.StopWatch; | |
29 | 29 | import org.thingsboard.common.util.ThingsBoardThreadFactory; |
30 | 30 | import org.thingsboard.server.gen.js.JsInvokeProtos; |
31 | 31 | import org.thingsboard.server.queue.TbQueueRequestTemplate; |
... | ... | @@ -161,7 +161,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { |
161 | 161 | |
162 | 162 | @Override |
163 | 163 | protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) { |
164 | - String scriptBody = scriptIdToBodysMap.get(scriptId); | |
164 | + log.trace("doInvokeFunction js-request for uuid {} with timeout {}ms", scriptId, maxRequestsTimeout); | |
165 | + final String scriptBody = scriptIdToBodysMap.get(scriptId); | |
165 | 166 | if (scriptBody == null) { |
166 | 167 | return Futures.immediateFailedFuture(new RuntimeException("No script body found for scriptId: [" + scriptId + "]!")); |
167 | 168 | } |
... | ... | @@ -170,7 +171,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { |
170 | 171 | .setScriptIdLSB(scriptId.getLeastSignificantBits()) |
171 | 172 | .setFunctionName(functionName) |
172 | 173 | .setTimeout((int) maxRequestsTimeout) |
173 | - .setScriptBody(scriptIdToBodysMap.get(scriptId)); | |
174 | + .setScriptBody(scriptBody); | |
174 | 175 | |
175 | 176 | for (Object arg : args) { |
176 | 177 | jsRequestBuilder.addArgs(arg.toString()); |
... | ... | @@ -180,6 +181,9 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { |
180 | 181 | .setInvokeRequest(jsRequestBuilder.build()) |
181 | 182 | .build(); |
182 | 183 | |
184 | + StopWatch stopWatch = new StopWatch(); | |
185 | + stopWatch.start(); | |
186 | + | |
183 | 187 | ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); |
184 | 188 | if (maxRequestsTimeout > 0) { |
185 | 189 | future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); |
... | ... | @@ -193,7 +197,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { |
193 | 197 | |
194 | 198 | @Override |
195 | 199 | public void onFailure(Throwable t) { |
196 | - onScriptExecutionError(scriptId); | |
200 | + onScriptExecutionError(scriptId, t, scriptBody); | |
197 | 201 | if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { |
198 | 202 | queueTimeoutMsgs.incrementAndGet(); |
199 | 203 | } |
... | ... | @@ -201,13 +205,16 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { |
201 | 205 | } |
202 | 206 | }, callbackExecutor); |
203 | 207 | return Futures.transform(future, response -> { |
208 | + stopWatch.stop(); | |
209 | + log.trace("doInvokeFunction js-response took {}ms for uuid {}", stopWatch.getTotalTimeMillis(), response.getKey()); | |
204 | 210 | JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse(); |
205 | 211 | if (invokeResult.getSuccess()) { |
206 | 212 | return invokeResult.getResult(); |
207 | 213 | } else { |
208 | - onScriptExecutionError(scriptId); | |
214 | + final RuntimeException e = new RuntimeException(invokeResult.getErrorDetails()); | |
215 | + onScriptExecutionError(scriptId, e, scriptBody); | |
209 | 216 | log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); |
210 | - throw new RuntimeException(invokeResult.getErrorDetails()); | |
217 | + throw e; | |
211 | 218 | } |
212 | 219 | }, callbackExecutor); |
213 | 220 | } | ... | ... |
... | ... | @@ -18,12 +18,12 @@ package org.thingsboard.server.service.script; |
18 | 18 | import com.fasterxml.jackson.core.type.TypeReference; |
19 | 19 | import com.fasterxml.jackson.databind.JsonNode; |
20 | 20 | import com.fasterxml.jackson.databind.ObjectMapper; |
21 | -import com.google.common.collect.Sets; | |
22 | 21 | import com.google.common.util.concurrent.Futures; |
23 | 22 | import com.google.common.util.concurrent.ListenableFuture; |
24 | 23 | import com.google.common.util.concurrent.MoreExecutors; |
25 | 24 | import lombok.extern.slf4j.Slf4j; |
26 | 25 | import org.apache.commons.lang3.StringUtils; |
26 | +import org.thingsboard.server.common.data.id.CustomerId; | |
27 | 27 | import org.thingsboard.server.common.data.id.EntityId; |
28 | 28 | import org.thingsboard.server.common.data.id.TenantId; |
29 | 29 | import org.thingsboard.server.common.msg.TbMsg; |
... | ... | @@ -32,6 +32,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; |
32 | 32 | import javax.script.ScriptException; |
33 | 33 | import java.util.ArrayList; |
34 | 34 | import java.util.Collections; |
35 | +import java.util.HashSet; | |
35 | 36 | import java.util.List; |
36 | 37 | import java.util.Map; |
37 | 38 | import java.util.Set; |
... | ... | @@ -102,140 +103,115 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S |
102 | 103 | String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); |
103 | 104 | return TbMsg.transformMsg(msg, newMessageType, msg.getOriginator(), newMetadata, newData); |
104 | 105 | } catch (Throwable th) { |
105 | - th.printStackTrace(); | |
106 | 106 | throw new RuntimeException("Failed to unbind message data from javascript result", th); |
107 | 107 | } |
108 | 108 | } |
109 | 109 | |
110 | 110 | @Override |
111 | - public List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException { | |
112 | - JsonNode result = executeScript(msg); | |
113 | - if (result.isObject()) { | |
114 | - return Collections.singletonList(unbindMsg(result, msg)); | |
115 | - } else if (result.isArray()){ | |
116 | - List<TbMsg> res = new ArrayList<>(result.size()); | |
117 | - result.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); | |
118 | - return res; | |
119 | - } else { | |
120 | - log.warn("Wrong result type: {}", result.getNodeType()); | |
121 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | |
111 | + public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) { | |
112 | + ListenableFuture<JsonNode> result = executeScriptAsync(msg); | |
113 | + return Futures.transformAsync(result, | |
114 | + json -> executeUpdateTransform(msg, json), | |
115 | + MoreExecutors.directExecutor()); | |
116 | + } | |
117 | + | |
118 | + ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, JsonNode json) { | |
119 | + if (json.isObject()) { | |
120 | + return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg))); | |
121 | + } else if (json.isArray()) { | |
122 | + List<TbMsg> res = new ArrayList<>(json.size()); | |
123 | + json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); | |
124 | + return Futures.immediateFuture(res); | |
122 | 125 | } |
126 | + log.warn("Wrong result type: {}", json.getNodeType()); | |
127 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | |
123 | 128 | } |
124 | 129 | |
125 | 130 | @Override |
126 | - public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) { | |
127 | - ListenableFuture<JsonNode> result = executeScriptAsync(msg); | |
128 | - return Futures.transformAsync(result, json -> { | |
129 | - if (json.isObject()) { | |
130 | - return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg))); | |
131 | - } else if (json.isArray()){ | |
132 | - List<TbMsg> res = new ArrayList<>(json.size()); | |
133 | - json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); | |
134 | - return Futures.immediateFuture(res); | |
135 | - } | |
136 | - else{ | |
137 | - log.warn("Wrong result type: {}", json.getNodeType()); | |
138 | - return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | |
139 | - } | |
140 | - }, MoreExecutors.directExecutor()); | |
131 | + public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) { | |
132 | + return Futures.transformAsync(executeScriptAsync(prevMsg), | |
133 | + result -> executeGenerateTransform(prevMsg, result), | |
134 | + MoreExecutors.directExecutor()); | |
141 | 135 | } |
142 | 136 | |
143 | - @Override | |
144 | - public TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException { | |
145 | - JsonNode result = executeScript(prevMsg); | |
137 | + ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, JsonNode result) { | |
146 | 138 | if (!result.isObject()) { |
147 | 139 | log.warn("Wrong result type: {}", result.getNodeType()); |
148 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | |
140 | + Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); | |
149 | 141 | } |
150 | - return unbindMsg(result, prevMsg); | |
142 | + return Futures.immediateFuture(unbindMsg(result, prevMsg)); | |
151 | 143 | } |
152 | 144 | |
153 | 145 | @Override |
154 | - public JsonNode executeJson(TbMsg msg) throws ScriptException { | |
155 | - return executeScript(msg); | |
156 | - } | |
157 | - | |
158 | - @Override | |
159 | - public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException { | |
146 | + public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) { | |
160 | 147 | return executeScriptAsync(msg); |
161 | 148 | } |
162 | 149 | |
163 | 150 | @Override |
164 | - public String executeToString(TbMsg msg) throws ScriptException { | |
165 | - JsonNode result = executeScript(msg); | |
166 | - if (!result.isTextual()) { | |
167 | - log.warn("Wrong result type: {}", result.getNodeType()); | |
168 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | |
169 | - } | |
170 | - return result.asText(); | |
151 | + public ListenableFuture<String> executeToStringAsync(TbMsg msg) { | |
152 | + return Futures.transformAsync(executeScriptAsync(msg), | |
153 | + this::executeToStringTransform, | |
154 | + MoreExecutors.directExecutor()); | |
171 | 155 | } |
172 | 156 | |
173 | - @Override | |
174 | - public boolean executeFilter(TbMsg msg) throws ScriptException { | |
175 | - JsonNode result = executeScript(msg); | |
176 | - if (!result.isBoolean()) { | |
177 | - log.warn("Wrong result type: {}", result.getNodeType()); | |
178 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | |
157 | + ListenableFuture<String> executeToStringTransform(JsonNode result) { | |
158 | + if (result.isTextual()) { | |
159 | + return Futures.immediateFuture(result.asText()); | |
179 | 160 | } |
180 | - return result.asBoolean(); | |
161 | + log.warn("Wrong result type: {}", result.getNodeType()); | |
162 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); | |
181 | 163 | } |
182 | 164 | |
183 | 165 | @Override |
184 | 166 | public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) { |
185 | - ListenableFuture<JsonNode> result = executeScriptAsync(msg); | |
186 | - return Futures.transformAsync(result, json -> { | |
187 | - if (!json.isBoolean()) { | |
188 | - log.warn("Wrong result type: {}", json.getNodeType()); | |
189 | - return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | |
190 | - } else { | |
191 | - return Futures.immediateFuture(json.asBoolean()); | |
192 | - } | |
193 | - }, MoreExecutors.directExecutor()); | |
167 | + return Futures.transformAsync(executeScriptAsync(msg), | |
168 | + this::executeFilterTransform, | |
169 | + MoreExecutors.directExecutor()); | |
194 | 170 | } |
195 | 171 | |
196 | - @Override | |
197 | - public Set<String> executeSwitch(TbMsg msg) throws ScriptException { | |
198 | - JsonNode result = executeScript(msg); | |
172 | + ListenableFuture<Boolean> executeFilterTransform(JsonNode json) { | |
173 | + if (json.isBoolean()) { | |
174 | + return Futures.immediateFuture(json.asBoolean()); | |
175 | + } | |
176 | + log.warn("Wrong result type: {}", json.getNodeType()); | |
177 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | |
178 | + } | |
179 | + | |
180 | + ListenableFuture<Set<String>> executeSwitchTransform(JsonNode result) { | |
199 | 181 | if (result.isTextual()) { |
200 | - return Collections.singleton(result.asText()); | |
201 | - } else if (result.isArray()) { | |
202 | - Set<String> nextStates = Sets.newHashSet(); | |
182 | + return Futures.immediateFuture(Collections.singleton(result.asText())); | |
183 | + } | |
184 | + if (result.isArray()) { | |
185 | + Set<String> nextStates = new HashSet<>(); | |
203 | 186 | for (JsonNode val : result) { |
204 | 187 | if (!val.isTextual()) { |
205 | 188 | log.warn("Wrong result type: {}", val.getNodeType()); |
206 | - throw new ScriptException("Wrong result type: " + val.getNodeType()); | |
189 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + val.getNodeType())); | |
207 | 190 | } else { |
208 | 191 | nextStates.add(val.asText()); |
209 | 192 | } |
210 | 193 | } |
211 | - return nextStates; | |
212 | - } else { | |
213 | - log.warn("Wrong result type: {}", result.getNodeType()); | |
214 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | |
194 | + return Futures.immediateFuture(nextStates); | |
215 | 195 | } |
196 | + log.warn("Wrong result type: {}", result.getNodeType()); | |
197 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); | |
216 | 198 | } |
217 | 199 | |
218 | - private JsonNode executeScript(TbMsg msg) throws ScriptException { | |
219 | - try { | |
220 | - String[] inArgs = prepareArgs(msg); | |
221 | - String eval = sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString(); | |
222 | - return mapper.readTree(eval); | |
223 | - } catch (ExecutionException e) { | |
224 | - if (e.getCause() instanceof ScriptException) { | |
225 | - throw (ScriptException) e.getCause(); | |
226 | - } else if (e.getCause() instanceof RuntimeException) { | |
227 | - throw new ScriptException(e.getCause().getMessage()); | |
228 | - } else { | |
229 | - throw new ScriptException(e); | |
230 | - } | |
231 | - } catch (Exception e) { | |
232 | - throw new ScriptException(e); | |
233 | - } | |
200 | + @Override | |
201 | + public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) { | |
202 | + return Futures.transformAsync(executeScriptAsync(msg), | |
203 | + this::executeSwitchTransform, | |
204 | + MoreExecutors.directExecutor()); //usually runs in a callbackExecutor | |
234 | 205 | } |
235 | 206 | |
236 | - private ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) { | |
207 | + ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) { | |
208 | + log.trace("execute script async, msg {}", msg); | |
237 | 209 | String[] inArgs = prepareArgs(msg); |
238 | - return Futures.transformAsync(sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]), | |
210 | + return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]); | |
211 | + } | |
212 | + | |
213 | + ListenableFuture<JsonNode> executeScriptAsync(CustomerId customerId, Object... args) { | |
214 | + return Futures.transformAsync(sandboxService.invokeFunction(tenantId, customerId, this.scriptId, args), | |
239 | 215 | o -> { |
240 | 216 | try { |
241 | 217 | return Futures.immediateFuture(mapper.readTree(o.toString())); | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.security.auth.oauth2; | |
17 | + | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | +import lombok.extern.slf4j.Slf4j; | |
20 | +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; | |
21 | +import org.springframework.stereotype.Service; | |
22 | +import org.springframework.util.LinkedMultiValueMap; | |
23 | +import org.springframework.util.MultiValueMap; | |
24 | +import org.springframework.util.StringUtils; | |
25 | +import org.thingsboard.common.util.JacksonUtil; | |
26 | +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; | |
27 | +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; | |
28 | +import org.thingsboard.server.dao.oauth2.OAuth2User; | |
29 | +import org.thingsboard.server.service.security.model.SecurityUser; | |
30 | + | |
31 | +import javax.servlet.http.HttpServletRequest; | |
32 | +import java.util.HashMap; | |
33 | +import java.util.Map; | |
34 | + | |
35 | +@Service(value = "appleOAuth2ClientMapper") | |
36 | +@Slf4j | |
37 | +public class AppleOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { | |
38 | + | |
39 | + private static final String USER = "user"; | |
40 | + private static final String NAME = "name"; | |
41 | + private static final String FIRST_NAME = "firstName"; | |
42 | + private static final String LAST_NAME = "lastName"; | |
43 | + private static final String EMAIL = "email"; | |
44 | + | |
45 | + @Override | |
46 | + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { | |
47 | + OAuth2MapperConfig config = registration.getMapperConfig(); | |
48 | + Map<String, Object> attributes = updateAttributesFromRequestParams(request, token.getPrincipal().getAttributes()); | |
49 | + String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); | |
50 | + OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); | |
51 | + | |
52 | + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration); | |
53 | + } | |
54 | + | |
55 | + private static Map<String, Object> updateAttributesFromRequestParams(HttpServletRequest request, Map<String, Object> attributes) { | |
56 | + Map<String, Object> updated = attributes; | |
57 | + MultiValueMap<String, String> params = toMultiMap(request.getParameterMap()); | |
58 | + String userValue = params.getFirst(USER); | |
59 | + if (StringUtils.hasText(userValue)) { | |
60 | + JsonNode user = null; | |
61 | + try { | |
62 | + user = JacksonUtil.toJsonNode(userValue); | |
63 | + } catch (Exception e) {} | |
64 | + if (user != null) { | |
65 | + updated = new HashMap<>(attributes); | |
66 | + if (user.has(NAME)) { | |
67 | + JsonNode name = user.get(NAME); | |
68 | + if (name.isObject()) { | |
69 | + JsonNode firstName = name.get(FIRST_NAME); | |
70 | + if (firstName != null && firstName.isTextual()) { | |
71 | + updated.put(FIRST_NAME, firstName.asText()); | |
72 | + } | |
73 | + JsonNode lastName = name.get(LAST_NAME); | |
74 | + if (lastName != null && lastName.isTextual()) { | |
75 | + updated.put(LAST_NAME, lastName.asText()); | |
76 | + } | |
77 | + } | |
78 | + } | |
79 | + if (user.has(EMAIL)) { | |
80 | + JsonNode email = user.get(EMAIL); | |
81 | + if (email != null && email.isTextual()) { | |
82 | + updated.put(EMAIL, email.asText()); | |
83 | + } | |
84 | + } | |
85 | + } | |
86 | + } | |
87 | + return updated; | |
88 | + } | |
89 | + | |
90 | + private static MultiValueMap<String, String> toMultiMap(Map<String, String[]> map) { | |
91 | + MultiValueMap<String, String> params = new LinkedMultiValueMap<>(map.size()); | |
92 | + map.forEach((key, values) -> { | |
93 | + if (values.length > 0) { | |
94 | + for (String value : values) { | |
95 | + params.add(key, value); | |
96 | + } | |
97 | + } | |
98 | + }); | |
99 | + return params; | |
100 | + } | |
101 | +} | ... | ... |
... | ... | @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration; |
23 | 23 | import org.thingsboard.server.dao.oauth2.OAuth2User; |
24 | 24 | import org.thingsboard.server.service.security.model.SecurityUser; |
25 | 25 | |
26 | +import javax.servlet.http.HttpServletRequest; | |
26 | 27 | import java.util.Map; |
27 | 28 | |
28 | 29 | @Service(value = "basicOAuth2ClientMapper") |
... | ... | @@ -30,7 +31,7 @@ import java.util.Map; |
30 | 31 | public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { |
31 | 32 | |
32 | 33 | @Override |
33 | - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { | |
34 | + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { | |
34 | 35 | OAuth2MapperConfig config = registration.getMapperConfig(); |
35 | 36 | Map<String, Object> attributes = token.getPrincipal().getAttributes(); |
36 | 37 | String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); | ... | ... |
... | ... | @@ -29,6 +29,8 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration; |
29 | 29 | import org.thingsboard.server.dao.oauth2.OAuth2User; |
30 | 30 | import org.thingsboard.server.service.security.model.SecurityUser; |
31 | 31 | |
32 | +import javax.servlet.http.HttpServletRequest; | |
33 | + | |
32 | 34 | @Service(value = "customOAuth2ClientMapper") |
33 | 35 | @Slf4j |
34 | 36 | public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { |
... | ... | @@ -39,7 +41,7 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme |
39 | 41 | private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); |
40 | 42 | |
41 | 43 | @Override |
42 | - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { | |
44 | + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { | |
43 | 45 | OAuth2MapperConfig config = registration.getMapperConfig(); |
44 | 46 | OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); |
45 | 47 | return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration); | ... | ... |
... | ... | @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2Configuration; |
29 | 29 | import org.thingsboard.server.dao.oauth2.OAuth2User; |
30 | 30 | import org.thingsboard.server.service.security.model.SecurityUser; |
31 | 31 | |
32 | +import javax.servlet.http.HttpServletRequest; | |
32 | 33 | import java.util.ArrayList; |
33 | 34 | import java.util.Map; |
34 | 35 | import java.util.Optional; |
... | ... | @@ -46,7 +47,7 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme |
46 | 47 | private OAuth2Configuration oAuth2Configuration; |
47 | 48 | |
48 | 49 | @Override |
49 | - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { | |
50 | + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { | |
50 | 51 | OAuth2MapperConfig config = registration.getMapperConfig(); |
51 | 52 | Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper(); |
52 | 53 | String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); | ... | ... |
... | ... | @@ -20,6 +20,8 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration; |
20 | 20 | import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; |
21 | 21 | import org.thingsboard.server.service.security.model.SecurityUser; |
22 | 22 | |
23 | +import javax.servlet.http.HttpServletRequest; | |
24 | + | |
23 | 25 | public interface OAuth2ClientMapper { |
24 | - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration); | |
26 | + SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration); | |
25 | 27 | } | ... | ... |
... | ... | @@ -37,6 +37,10 @@ public class OAuth2ClientMapperProvider { |
37 | 37 | @Qualifier("githubOAuth2ClientMapper") |
38 | 38 | private OAuth2ClientMapper githubOAuth2ClientMapper; |
39 | 39 | |
40 | + @Autowired | |
41 | + @Qualifier("appleOAuth2ClientMapper") | |
42 | + private OAuth2ClientMapper appleOAuth2ClientMapper; | |
43 | + | |
40 | 44 | public OAuth2ClientMapper getOAuth2ClientMapperByType(MapperType oauth2MapperType) { |
41 | 45 | switch (oauth2MapperType) { |
42 | 46 | case CUSTOM: |
... | ... | @@ -45,6 +49,8 @@ public class OAuth2ClientMapperProvider { |
45 | 49 | return basicOAuth2ClientMapper; |
46 | 50 | case GITHUB: |
47 | 51 | return githubOAuth2ClientMapper; |
52 | + case APPLE: | |
53 | + return appleOAuth2ClientMapper; | |
48 | 54 | default: |
49 | 55 | throw new RuntimeException("OAuth2ClientRegistrationMapper with type " + oauth2MapperType + " is not supported!"); |
50 | 56 | } | ... | ... |
... | ... | @@ -36,7 +36,6 @@ import java.net.URLEncoder; |
36 | 36 | import java.nio.charset.StandardCharsets; |
37 | 37 | |
38 | 38 | @Component(value = "oauth2AuthenticationFailureHandler") |
39 | -@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") | |
40 | 39 | public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { |
41 | 40 | |
42 | 41 | private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; | ... | ... |
... | ... | @@ -90,7 +90,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS |
90 | 90 | token.getAuthorizedClientRegistrationId(), |
91 | 91 | token.getPrincipal().getName()); |
92 | 92 | OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(registration.getMapperConfig().getType()); |
93 | - SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), | |
93 | + SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(request, token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), | |
94 | 94 | registration); |
95 | 95 | |
96 | 96 | JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.security.model.token; | |
17 | + | |
18 | +import io.jsonwebtoken.Claims; | |
19 | +import io.jsonwebtoken.ExpiredJwtException; | |
20 | +import io.jsonwebtoken.Jws; | |
21 | +import io.jsonwebtoken.Jwts; | |
22 | +import io.jsonwebtoken.MalformedJwtException; | |
23 | +import io.jsonwebtoken.SignatureException; | |
24 | +import io.jsonwebtoken.UnsupportedJwtException; | |
25 | +import io.micrometer.core.instrument.util.StringUtils; | |
26 | +import lombok.extern.slf4j.Slf4j; | |
27 | +import org.springframework.stereotype.Component; | |
28 | + | |
29 | +import java.util.Date; | |
30 | +import java.util.concurrent.TimeUnit; | |
31 | + | |
32 | +@Component | |
33 | +@Slf4j | |
34 | +public class OAuth2AppTokenFactory { | |
35 | + | |
36 | + private static final String CALLBACK_URL_SCHEME = "callbackUrlScheme"; | |
37 | + | |
38 | + private static final long MAX_EXPIRATION_TIME_DIFF_MS = TimeUnit.MINUTES.toMillis(5); | |
39 | + | |
40 | + public String validateTokenAndGetCallbackUrlScheme(String appPackage, String appToken, String appSecret) { | |
41 | + Jws<Claims> jwsClaims; | |
42 | + try { | |
43 | + jwsClaims = Jwts.parser().setSigningKey(appSecret).parseClaimsJws(appToken); | |
44 | + } | |
45 | + catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { | |
46 | + throw new IllegalArgumentException("Invalid Application token: ", ex); | |
47 | + } catch (ExpiredJwtException expiredEx) { | |
48 | + throw new IllegalArgumentException("Application token expired", expiredEx); | |
49 | + } | |
50 | + Claims claims = jwsClaims.getBody(); | |
51 | + Date expiration = claims.getExpiration(); | |
52 | + if (expiration == null) { | |
53 | + throw new IllegalArgumentException("Application token must have expiration date"); | |
54 | + } | |
55 | + long timeDiff = expiration.getTime() - System.currentTimeMillis(); | |
56 | + if (timeDiff > MAX_EXPIRATION_TIME_DIFF_MS) { | |
57 | + throw new IllegalArgumentException("Application token expiration time can't be longer than 5 minutes"); | |
58 | + } | |
59 | + if (!claims.getIssuer().equals(appPackage)) { | |
60 | + throw new IllegalArgumentException("Application token issuer doesn't match application package"); | |
61 | + } | |
62 | + String callbackUrlScheme = claims.get(CALLBACK_URL_SCHEME, String.class); | |
63 | + if (StringUtils.isEmpty(callbackUrlScheme)) { | |
64 | + throw new IllegalArgumentException("Application token doesn't have callbackUrlScheme"); | |
65 | + } | |
66 | + return callbackUrlScheme; | |
67 | + } | |
68 | + | |
69 | +} | ... | ... |
... | ... | @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.Futures; |
22 | 22 | import com.google.common.util.concurrent.ListenableFuture; |
23 | 23 | import com.google.common.util.concurrent.MoreExecutors; |
24 | 24 | import com.google.protobuf.ByteString; |
25 | +import lombok.RequiredArgsConstructor; | |
25 | 26 | import lombok.extern.slf4j.Slf4j; |
26 | 27 | import org.springframework.stereotype.Service; |
27 | 28 | import org.springframework.util.StringUtils; |
... | ... | @@ -41,13 +42,13 @@ import org.thingsboard.server.common.data.TenantProfile; |
41 | 42 | import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; |
42 | 43 | import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; |
43 | 44 | import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; |
44 | -import org.thingsboard.server.common.data.ota.OtaPackageType; | |
45 | -import org.thingsboard.server.common.data.ota.OtaPackageUtil; | |
46 | 45 | import org.thingsboard.server.common.data.id.CustomerId; |
47 | 46 | import org.thingsboard.server.common.data.id.DeviceId; |
48 | 47 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
49 | 48 | import org.thingsboard.server.common.data.id.OtaPackageId; |
50 | 49 | import org.thingsboard.server.common.data.id.TenantId; |
50 | +import org.thingsboard.server.common.data.ota.OtaPackageType; | |
51 | +import org.thingsboard.server.common.data.ota.OtaPackageUtil; | |
51 | 52 | import org.thingsboard.server.common.data.page.PageData; |
52 | 53 | import org.thingsboard.server.common.data.page.PageLink; |
53 | 54 | import org.thingsboard.server.common.data.relation.EntityRelation; |
... | ... | @@ -108,6 +109,7 @@ import java.util.stream.Collectors; |
108 | 109 | @Slf4j |
109 | 110 | @Service |
110 | 111 | @TbCoreComponent |
112 | +@RequiredArgsConstructor | |
111 | 113 | public class DefaultTransportApiService implements TransportApiService { |
112 | 114 | |
113 | 115 | private static final ObjectMapper mapper = new ObjectMapper(); |
... | ... | @@ -129,28 +131,6 @@ public class DefaultTransportApiService implements TransportApiService { |
129 | 131 | |
130 | 132 | private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>(); |
131 | 133 | |
132 | - public DefaultTransportApiService(TbDeviceProfileCache deviceProfileCache, | |
133 | - TbTenantProfileCache tenantProfileCache, TbApiUsageStateService apiUsageStateService, DeviceService deviceService, | |
134 | - RelationService relationService, DeviceCredentialsService deviceCredentialsService, | |
135 | - DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, | |
136 | - TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, | |
137 | - DeviceProvisionService deviceProvisionService, TbResourceService resourceService, OtaPackageService otaPackageService, OtaPackageDataCache otaPackageDataCache) { | |
138 | - this.deviceProfileCache = deviceProfileCache; | |
139 | - this.tenantProfileCache = tenantProfileCache; | |
140 | - this.apiUsageStateService = apiUsageStateService; | |
141 | - this.deviceService = deviceService; | |
142 | - this.relationService = relationService; | |
143 | - this.deviceCredentialsService = deviceCredentialsService; | |
144 | - this.deviceStateService = deviceStateService; | |
145 | - this.dbCallbackExecutorService = dbCallbackExecutorService; | |
146 | - this.tbClusterService = tbClusterService; | |
147 | - this.dataDecodingEncodingService = dataDecodingEncodingService; | |
148 | - this.deviceProvisionService = deviceProvisionService; | |
149 | - this.resourceService = resourceService; | |
150 | - this.otaPackageService = otaPackageService; | |
151 | - this.otaPackageDataCache = otaPackageDataCache; | |
152 | - } | |
153 | - | |
154 | 134 | @Override |
155 | 135 | public ListenableFuture<TbProtoQueueMsg<TransportApiResponseMsg>> handle(TbProtoQueueMsg<TransportApiRequestMsg> tbProtoQueueMsg) { |
156 | 136 | TransportApiRequestMsg transportApiRequestMsg = tbProtoQueueMsg.getValue(); | ... | ... |
... | ... | @@ -335,6 +335,7 @@ actors: |
335 | 335 | cache: |
336 | 336 | # caffeine or redis |
337 | 337 | type: "${CACHE_TYPE:caffeine}" |
338 | + maximumPoolSize: "${CACHE_MAXIMUM_POOL_SIZE:16}" # max pool size to process futures that calls the external cache | |
338 | 339 | attributes: |
339 | 340 | # make sure that if cache.type is 'redis' and cache.attributes.enabled is 'true' that you change 'maxmemory-policy' Redis config property to 'allkeys-lru', 'allkeys-lfu' or 'allkeys-random' |
340 | 341 | enabled: "${CACHE_ATTRIBUTES_ENABLED:true}" |
... | ... | @@ -680,12 +681,11 @@ transport: |
680 | 681 | timeout: "${LWM2M_TIMEOUT:120000}" |
681 | 682 | recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" |
682 | 683 | recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" |
683 | - response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}" | |
684 | - registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}" | |
684 | + uplink_pool_size: "${LWM2M_UPLINK_POOL_SIZE:10}" | |
685 | + downlink_pool_size: "${LWM2M_DOWNLINK_POOL_SIZE:10}" | |
686 | + ota_pool_size: "${LWM2M_OTA_POOL_SIZE:10}" | |
685 | 687 | registration_store_pool_size: "${LWM2M_REGISTRATION_STORE_POOL_SIZE:100}" |
686 | 688 | clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}" |
687 | - update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}" | |
688 | - un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}" | |
689 | 689 | log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}" |
690 | 690 | # Use redis for Security and Registration stores |
691 | 691 | redis.enabled: "${LWM2M_REDIS_ENABLED:false}" | ... | ... |
... | ... | @@ -141,10 +141,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes |
141 | 141 | |
142 | 142 | MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); |
143 | 143 | |
144 | - OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); | |
144 | + OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); | |
145 | 145 | |
146 | 146 | Assert.assertEquals(FILE_NAME, savedFirmware.getFileName()); |
147 | 147 | Assert.assertEquals(CONTENT_TYPE, savedFirmware.getContentType()); |
148 | + Assert.assertEquals(CHECKSUM_ALGORITHM, savedFirmware.getChecksumAlgorithm().name()); | |
149 | + Assert.assertEquals(CHECKSUM, savedFirmware.getChecksum()); | |
148 | 150 | } |
149 | 151 | |
150 | 152 | @Test |
... | ... | @@ -189,11 +191,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes |
189 | 191 | |
190 | 192 | MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); |
191 | 193 | |
192 | - OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); | |
194 | + OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); | |
193 | 195 | |
194 | 196 | OtaPackage foundFirmware = doGet("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString(), OtaPackage.class); |
195 | 197 | Assert.assertNotNull(foundFirmware); |
196 | - Assert.assertEquals(savedFirmware, foundFirmware); | |
198 | + Assert.assertEquals(savedFirmware, new OtaPackageInfo(foundFirmware)); | |
199 | + Assert.assertEquals(DATA, foundFirmware.getData()); | |
197 | 200 | } |
198 | 201 | |
199 | 202 | @Test |
... | ... | @@ -228,8 +231,8 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes |
228 | 231 | if (i > 100) { |
229 | 232 | MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); |
230 | 233 | |
231 | - OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); | |
232 | - otaPackages.add(new OtaPackageInfo(savedFirmware)); | |
234 | + OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); | |
235 | + otaPackages.add(savedFirmware); | |
233 | 236 | } else { |
234 | 237 | otaPackages.add(savedFirmwareInfo); |
235 | 238 | } |
... | ... | @@ -271,7 +274,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes |
271 | 274 | if (i > 100) { |
272 | 275 | MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); |
273 | 276 | |
274 | - OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); | |
277 | + OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); | |
275 | 278 | savedFirmwareInfo = new OtaPackageInfo(savedFirmware); |
276 | 279 | otaPackagesWithData.add(savedFirmwareInfo); |
277 | 280 | } |
... | ... | @@ -318,11 +321,11 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes |
318 | 321 | return doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); |
319 | 322 | } |
320 | 323 | |
321 | - protected OtaPackage savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { | |
324 | + protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { | |
322 | 325 | MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); |
323 | 326 | postRequest.file(content); |
324 | 327 | setJwtToken(postRequest); |
325 | - return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackage.class); | |
328 | + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); | |
326 | 329 | } |
327 | 330 | |
328 | 331 | } | ... | ... |
... | ... | @@ -103,7 +103,9 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { |
103 | 103 | " }\n" + |
104 | 104 | " },\n" + |
105 | 105 | " \"clientLwM2mSettings\": {\n" + |
106 | - " \"clientOnlyObserveAfterConnect\": 1\n" + | |
106 | + " \"clientOnlyObserveAfterConnect\": 1,\n" + | |
107 | + " \"fwUpdateStrategy\": 1,\n" + | |
108 | + " \"swUpdateStrategy\": 1\n" + | |
107 | 109 | " }\n" + |
108 | 110 | "}"; |
109 | 111 | ... | ... |
... | ... | @@ -17,11 +17,14 @@ package org.thingsboard.server.transport.lwm2m; |
17 | 17 | |
18 | 18 | import org.eclipse.californium.core.network.config.NetworkConfig; |
19 | 19 | import org.eclipse.leshan.client.object.Security; |
20 | -import org.jetbrains.annotations.NotNull; | |
21 | 20 | import org.junit.Assert; |
22 | 21 | import org.junit.Test; |
22 | +import org.springframework.mock.web.MockMultipartFile; | |
23 | +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; | |
24 | +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; | |
23 | 25 | import org.thingsboard.common.util.JacksonUtil; |
24 | 26 | import org.thingsboard.server.common.data.Device; |
27 | +import org.thingsboard.server.common.data.OtaPackageInfo; | |
25 | 28 | import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; |
26 | 29 | import org.thingsboard.server.common.data.query.EntityData; |
27 | 30 | import org.thingsboard.server.common.data.query.EntityDataPageLink; |
... | ... | @@ -43,15 +46,15 @@ import java.util.List; |
43 | 46 | |
44 | 47 | import static org.eclipse.leshan.client.object.Security.noSec; |
45 | 48 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
49 | +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; | |
46 | 50 | |
47 | 51 | public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
48 | 52 | |
49 | 53 | private final int PORT = 5685; |
50 | 54 | private final Security SECURITY = noSec("coap://localhost:" + PORT, 123); |
51 | 55 | private final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); |
52 | - private final String ENDPOINT = "deviceAEndpoint"; | |
56 | + private final String ENDPOINT = "noSecEndpoint"; | |
53 | 57 | |
54 | - @NotNull | |
55 | 58 | private Device createDevice() throws Exception { |
56 | 59 | Device device = new Device(); |
57 | 60 | device.setName("Device A"); |
... | ... | @@ -74,6 +77,29 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
74 | 77 | return device; |
75 | 78 | } |
76 | 79 | |
80 | + private OtaPackageInfo createFirmware() throws Exception { | |
81 | + String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; | |
82 | + | |
83 | + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); | |
84 | + firmwareInfo.setDeviceProfileId(deviceProfile.getId()); | |
85 | + firmwareInfo.setType(FIRMWARE); | |
86 | + firmwareInfo.setTitle("My firmware"); | |
87 | + firmwareInfo.setVersion("v1.0"); | |
88 | + | |
89 | + OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); | |
90 | + | |
91 | + MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1}); | |
92 | + | |
93 | + return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); | |
94 | + } | |
95 | + | |
96 | + protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { | |
97 | + MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); | |
98 | + postRequest.file(content); | |
99 | + setJwtToken(postRequest); | |
100 | + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); | |
101 | + } | |
102 | + | |
77 | 103 | @Test |
78 | 104 | public void testConnectAndObserveTelemetry() throws Exception { |
79 | 105 | createDeviceProfile(TRANSPORT_CONFIGURATION); |
... | ... | @@ -111,4 +137,53 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
111 | 137 | client.destroy(); |
112 | 138 | } |
113 | 139 | |
140 | + @Test | |
141 | + public void testFirmwareUpdateWithClientWithoutFirmwareInfo() throws Exception { | |
142 | + createDeviceProfile(TRANSPORT_CONFIGURATION); | |
143 | + | |
144 | + Device device = createDevice(); | |
145 | + | |
146 | + OtaPackageInfo firmware = createFirmware(); | |
147 | + | |
148 | + LwM2MTestClient client = new LwM2MTestClient(executor, ENDPOINT); | |
149 | + client.init(SECURITY, COAP_CONFIG); | |
150 | + | |
151 | + Thread.sleep(1000); | |
152 | + | |
153 | + device.setFirmwareId(firmware.getId()); | |
154 | + | |
155 | + device = doPost("/api/device", device, Device.class); | |
156 | + | |
157 | + Thread.sleep(1000); | |
158 | + | |
159 | + SingleEntityFilter sef = new SingleEntityFilter(); | |
160 | + sef.setSingleEntity(device.getId()); | |
161 | + LatestValueCmd latestCmd = new LatestValueCmd(); | |
162 | + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "fw_state"))); | |
163 | + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), | |
164 | + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); | |
165 | + | |
166 | + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); | |
167 | + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); | |
168 | + wrapper.setEntityDataCmds(Collections.singletonList(cmd)); | |
169 | + | |
170 | + wsClient.send(mapper.writeValueAsString(wrapper)); | |
171 | + wsClient.waitForReply(); | |
172 | + | |
173 | + wsClient.registerWaitForUpdate(); | |
174 | + | |
175 | + String msg = wsClient.waitForUpdate(); | |
176 | + | |
177 | + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); | |
178 | + Assert.assertEquals(1, update.getCmdId()); | |
179 | + List<EntityData> eData = update.getUpdate(); | |
180 | + Assert.assertNotNull(eData); | |
181 | + Assert.assertEquals(1, eData.size()); | |
182 | + Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); | |
183 | + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); | |
184 | + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("fw_state"); | |
185 | + Assert.assertEquals("FAILED", tsValue.getValue()); | |
186 | + client.destroy(); | |
187 | + } | |
188 | + | |
114 | 189 | } | ... | ... |
... | ... | @@ -19,6 +19,7 @@ import org.eclipse.californium.core.network.config.NetworkConfig; |
19 | 19 | import org.eclipse.leshan.client.object.Security; |
20 | 20 | import org.jetbrains.annotations.NotNull; |
21 | 21 | import org.junit.Assert; |
22 | +import org.junit.Ignore; | |
22 | 23 | import org.junit.Test; |
23 | 24 | import org.thingsboard.common.util.JacksonUtil; |
24 | 25 | import org.thingsboard.server.common.data.Device; |
... | ... | @@ -52,7 +53,6 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
52 | 53 | private final String endpoint = "deviceAEndpoint"; |
53 | 54 | private final String serverUri = "coaps://localhost:" + port; |
54 | 55 | |
55 | - @NotNull | |
56 | 56 | private Device createDevice(X509ClientCredentials clientCredentials) throws Exception { |
57 | 57 | Device device = new Device(); |
58 | 58 | device.setName("Device A"); |
... | ... | @@ -75,11 +75,13 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
75 | 75 | return device; |
76 | 76 | } |
77 | 77 | |
78 | + //TODO: use different endpoints to isolate tests. | |
79 | + @Ignore() | |
78 | 80 | @Test |
79 | 81 | public void testConnectAndObserveTelemetry() throws Exception { |
80 | 82 | createDeviceProfile(TRANSPORT_CONFIGURATION); |
81 | 83 | X509ClientCredentials credentials = new X509ClientCredentials(); |
82 | - credentials.setEndpoint(endpoint); | |
84 | + credentials.setEndpoint(endpoint+1); | |
83 | 85 | Device device = createDevice(credentials); |
84 | 86 | |
85 | 87 | SingleEntityFilter sef = new SingleEntityFilter(); |
... | ... | @@ -97,7 +99,7 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
97 | 99 | wsClient.waitForReply(); |
98 | 100 | |
99 | 101 | wsClient.registerWaitForUpdate(); |
100 | - LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); | |
102 | + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint+1); | |
101 | 103 | Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); |
102 | 104 | client.init(security, coapConfig); |
103 | 105 | String msg = wsClient.waitForUpdate(); | ... | ... |
... | ... | @@ -41,6 +41,7 @@ public class ActorSystemTest { |
41 | 41 | |
42 | 42 | public static final String ROOT_DISPATCHER = "root-dispatcher"; |
43 | 43 | private static final int _100K = 100 * 1024; |
44 | + public static final int TIMEOUT_AWAIT_MAX_SEC = 10; | |
44 | 45 | |
45 | 46 | private volatile TbActorSystem actorSystem; |
46 | 47 | private volatile ExecutorService submitPool; |
... | ... | @@ -52,7 +53,7 @@ public class ActorSystemTest { |
52 | 53 | parallelism = Math.max(2, cores / 2); |
53 | 54 | TbActorSystemSettings settings = new TbActorSystemSettings(5, parallelism, 42); |
54 | 55 | actorSystem = new DefaultTbActorSystem(settings); |
55 | - submitPool = Executors.newWorkStealingPool(parallelism); | |
56 | + submitPool = Executors.newFixedThreadPool(parallelism); //order guaranteed | |
56 | 57 | } |
57 | 58 | |
58 | 59 | @After |
... | ... | @@ -122,13 +123,23 @@ public class ActorSystemTest { |
122 | 123 | ActorTestCtx testCtx1 = getActorTestCtx(1); |
123 | 124 | ActorTestCtx testCtx2 = getActorTestCtx(1); |
124 | 125 | TbActorId actorId = new TbEntityActorId(new DeviceId(UUID.randomUUID())); |
125 | - submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx1))); | |
126 | - submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx2))); | |
126 | + final CountDownLatch initLatch = new CountDownLatch(1); | |
127 | + final CountDownLatch actorsReadyLatch = new CountDownLatch(2); | |
128 | + submitPool.submit(() -> { | |
129 | + actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx1, initLatch)); | |
130 | + actorsReadyLatch.countDown(); | |
131 | + }); | |
132 | + submitPool.submit(() -> { | |
133 | + actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx2, initLatch)); | |
134 | + actorsReadyLatch.countDown(); | |
135 | + }); | |
136 | + | |
137 | + initLatch.countDown(); //replacement for Thread.wait(500) in the SlowCreateActorCreator | |
138 | + Assert.assertTrue(actorsReadyLatch.await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS)); | |
127 | 139 | |
128 | - Thread.sleep(1000); | |
129 | 140 | actorSystem.tell(actorId, new IntTbActorMsg(42)); |
130 | 141 | |
131 | - Assert.assertTrue(testCtx1.getLatch().await(1, TimeUnit.SECONDS)); | |
142 | + Assert.assertTrue(testCtx1.getLatch().await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS)); | |
132 | 143 | Assert.assertFalse(testCtx2.getLatch().await(1, TimeUnit.SECONDS)); |
133 | 144 | } |
134 | 145 | |
... | ... | @@ -137,13 +148,21 @@ public class ActorSystemTest { |
137 | 148 | actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism)); |
138 | 149 | ActorTestCtx testCtx = getActorTestCtx(1); |
139 | 150 | TbActorId actorId = new TbEntityActorId(new DeviceId(UUID.randomUUID())); |
140 | - for (int i = 0; i < 1000; i++) { | |
141 | - submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx))); | |
151 | + final int actorsCount = 1000; | |
152 | + final CountDownLatch initLatch = new CountDownLatch(1); | |
153 | + final CountDownLatch actorsReadyLatch = new CountDownLatch(actorsCount); | |
154 | + for (int i = 0; i < actorsCount; i++) { | |
155 | + submitPool.submit(() -> { | |
156 | + actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx, initLatch)); | |
157 | + actorsReadyLatch.countDown(); | |
158 | + }); | |
142 | 159 | } |
143 | - Thread.sleep(1000); | |
160 | + initLatch.countDown(); | |
161 | + Assert.assertTrue(actorsReadyLatch.await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS)); | |
162 | + | |
144 | 163 | actorSystem.tell(actorId, new IntTbActorMsg(42)); |
145 | 164 | |
146 | - Assert.assertTrue(testCtx.getLatch().await(1, TimeUnit.SECONDS)); | |
165 | + Assert.assertTrue(testCtx.getLatch().await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS)); | |
147 | 166 | //One for creation and one for message |
148 | 167 | Assert.assertEquals(2, testCtx.getInvocationCount().get()); |
149 | 168 | } | ... | ... |
... | ... | @@ -17,13 +17,18 @@ package org.thingsboard.server.actors; |
17 | 17 | |
18 | 18 | import lombok.extern.slf4j.Slf4j; |
19 | 19 | |
20 | +import java.util.concurrent.CountDownLatch; | |
21 | +import java.util.concurrent.TimeUnit; | |
22 | + | |
20 | 23 | @Slf4j |
21 | 24 | public class SlowCreateActor extends TestRootActor { |
22 | 25 | |
23 | - public SlowCreateActor(TbActorId actorId, ActorTestCtx testCtx) { | |
26 | + public static final int TIMEOUT_AWAIT_MAX_MS = 5000; | |
27 | + | |
28 | + public SlowCreateActor(TbActorId actorId, ActorTestCtx testCtx, CountDownLatch initLatch) { | |
24 | 29 | super(actorId, testCtx); |
25 | 30 | try { |
26 | - Thread.sleep(500); | |
31 | + initLatch.await(TIMEOUT_AWAIT_MAX_MS, TimeUnit.MILLISECONDS); | |
27 | 32 | } catch (InterruptedException e) { |
28 | 33 | e.printStackTrace(); |
29 | 34 | } |
... | ... | @@ -34,10 +39,12 @@ public class SlowCreateActor extends TestRootActor { |
34 | 39 | |
35 | 40 | private final TbActorId actorId; |
36 | 41 | private final ActorTestCtx testCtx; |
42 | + private final CountDownLatch initLatch; | |
37 | 43 | |
38 | - public SlowCreateActorCreator(TbActorId actorId, ActorTestCtx testCtx) { | |
44 | + public SlowCreateActorCreator(TbActorId actorId, ActorTestCtx testCtx, CountDownLatch initLatch) { | |
39 | 45 | this.actorId = actorId; |
40 | 46 | this.testCtx = testCtx; |
47 | + this.initLatch = initLatch; | |
41 | 48 | } |
42 | 49 | |
43 | 50 | @Override |
... | ... | @@ -47,7 +54,7 @@ public class SlowCreateActor extends TestRootActor { |
47 | 54 | |
48 | 55 | @Override |
49 | 56 | public TbActor createActor() { |
50 | - return new SlowCreateActor(actorId, testCtx); | |
57 | + return new SlowCreateActor(actorId, testCtx, initLatch); | |
51 | 58 | } |
52 | 59 | } |
53 | 60 | } | ... | ... |
... | ... | @@ -36,6 +36,8 @@ import java.util.concurrent.Executors; |
36 | 36 | import java.util.concurrent.ScheduledExecutorService; |
37 | 37 | import java.util.concurrent.TimeUnit; |
38 | 38 | |
39 | +import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME; | |
40 | + | |
39 | 41 | @Slf4j |
40 | 42 | @Component |
41 | 43 | @TbCoapServerComponent |
... | ... | @@ -91,7 +93,16 @@ public class DefaultCoapServerService implements CoapServerService { |
91 | 93 | InetAddress addr = InetAddress.getByName(coapServerContext.getHost()); |
92 | 94 | InetSocketAddress sockAddr = new InetSocketAddress(addr, coapServerContext.getPort()); |
93 | 95 | noSecCoapEndpointBuilder.setInetSocketAddress(sockAddr); |
94 | - noSecCoapEndpointBuilder.setNetworkConfig(NetworkConfig.getStandard()); | |
96 | + NetworkConfig networkConfig = new NetworkConfig(); | |
97 | + networkConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true); | |
98 | + networkConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); | |
99 | + networkConfig.setLong(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME); | |
100 | + networkConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024); | |
101 | + networkConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED"); | |
102 | + networkConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024); | |
103 | + networkConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); | |
104 | + networkConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4); | |
105 | + noSecCoapEndpointBuilder.setNetworkConfig(networkConfig); | |
95 | 106 | CoapEndpoint noSecCoapEndpoint = noSecCoapEndpointBuilder.build(); |
96 | 107 | server.addEndpoint(noSecCoapEndpoint); |
97 | 108 | ... | ... |
... | ... | @@ -19,8 +19,10 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; |
19 | 19 | import com.fasterxml.jackson.annotation.JsonAnySetter; |
20 | 20 | import com.fasterxml.jackson.annotation.JsonIgnore; |
21 | 21 | import lombok.Data; |
22 | -import org.thingsboard.server.common.data.DeviceProfileType; | |
23 | 22 | import org.thingsboard.server.common.data.DeviceTransportType; |
23 | +import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration; | |
24 | +import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; | |
25 | +import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration; | |
24 | 26 | |
25 | 27 | import java.util.HashMap; |
26 | 28 | import java.util.Map; | ... | ... |
... | ... | @@ -51,6 +51,13 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur |
51 | 51 | private String privacyPassphrase; |
52 | 52 | private String engineId; |
53 | 53 | |
54 | + public SnmpDeviceTransportConfiguration() { | |
55 | + this.host = "localhost"; | |
56 | + this.port = 161; | |
57 | + this.protocolVersion = SnmpProtocolVersion.V2C; | |
58 | + this.community = "public"; | |
59 | + } | |
60 | + | |
54 | 61 | @Override |
55 | 62 | public DeviceTransportType getType() { |
56 | 63 | return DeviceTransportType.SNMP; |
... | ... | @@ -76,7 +83,7 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur |
76 | 83 | isValid = StringUtils.isNotBlank(username) && StringUtils.isNotBlank(securityName) |
77 | 84 | && contextName != null && authenticationProtocol != null |
78 | 85 | && StringUtils.isNotBlank(authenticationPassphrase) |
79 | - && privacyProtocol != null && privacyPassphrase != null && engineId != null; | |
86 | + && privacyProtocol != null && StringUtils.isNotBlank(privacyPassphrase) && engineId != null; | |
80 | 87 | break; |
81 | 88 | } |
82 | 89 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.device.data.lwm2m; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +import java.util.Map; | |
21 | + | |
22 | +@Data | |
23 | +public class BootstrapConfiguration { | |
24 | + | |
25 | + //TODO: define the objects; | |
26 | + private Map<String, Object> servers; | |
27 | + private Map<String, Object> lwm2mServer; | |
28 | + private Map<String, Object> bootstrapServer; | |
29 | + | |
30 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.device.data.lwm2m; | |
17 | + | |
18 | +import com.fasterxml.jackson.annotation.JsonInclude; | |
19 | +import lombok.Data; | |
20 | + | |
21 | +@Data | |
22 | +@JsonInclude(JsonInclude.Include.NON_NULL) | |
23 | +public class ObjectAttributes { | |
24 | + | |
25 | + private Long dim; | |
26 | + private String ver; | |
27 | + private Long pmin; | |
28 | + private Long pmax; | |
29 | + private Double gt; | |
30 | + private Double lt; | |
31 | + private Double st; | |
32 | + | |
33 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.device.data.lwm2m; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +@Data | |
21 | +public class OtherConfiguration { | |
22 | + | |
23 | + private Integer fwUpdateStrategy; | |
24 | + private Integer swUpdateStrategy; | |
25 | + private Integer clientOnlyObserveAfterConnect; | |
26 | + private String fwUpdateRecourse; | |
27 | + private String swUpdateRecourse; | |
28 | + | |
29 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.device.data.lwm2m; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +import java.util.Map; | |
21 | +import java.util.Set; | |
22 | + | |
23 | +@Data | |
24 | +public class TelemetryMappingConfiguration { | |
25 | + | |
26 | + private Map<String, String> keyName; | |
27 | + private Set<String> observe; | |
28 | + private Set<String> attribute; | |
29 | + private Set<String> telemetry; | |
30 | + private Map<String, ObjectAttributes> attributeLwm2m; | |
31 | + | |
32 | +} | ... | ... |
... | ... | @@ -16,6 +16,7 @@ |
16 | 16 | package org.thingsboard.server.common.data.device.profile; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | +import org.thingsboard.server.common.data.id.DashboardId; | |
19 | 20 | import org.thingsboard.server.common.data.validation.NoXss; |
20 | 21 | |
21 | 22 | import javax.validation.Valid; |
... | ... | @@ -31,5 +32,6 @@ public class AlarmRule implements Serializable { |
31 | 32 | // Advanced |
32 | 33 | @NoXss |
33 | 34 | private String alarmDetails; |
35 | + private DashboardId dashboardId; | |
34 | 36 | |
35 | 37 | } | ... | ... |
... | ... | @@ -15,31 +15,20 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.common.data.device.profile; |
17 | 17 | |
18 | -import com.fasterxml.jackson.annotation.JsonAnyGetter; | |
19 | -import com.fasterxml.jackson.annotation.JsonAnySetter; | |
20 | -import com.fasterxml.jackson.annotation.JsonIgnore; | |
21 | 18 | import lombok.Data; |
22 | -import org.thingsboard.server.common.data.DeviceProfileType; | |
23 | 19 | import org.thingsboard.server.common.data.DeviceTransportType; |
24 | - | |
25 | -import java.util.HashMap; | |
26 | -import java.util.Map; | |
20 | +import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration; | |
21 | +import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; | |
22 | +import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration; | |
27 | 23 | |
28 | 24 | @Data |
29 | 25 | public class Lwm2mDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { |
30 | 26 | |
31 | - @JsonIgnore | |
32 | - private Map<String, Object> properties = new HashMap<>(); | |
33 | - | |
34 | - @JsonAnyGetter | |
35 | - public Map<String, Object> properties() { | |
36 | - return this.properties; | |
37 | - } | |
27 | + private static final long serialVersionUID = 6257277825459600068L; | |
38 | 28 | |
39 | - @JsonAnySetter | |
40 | - public void put(String name, Object value) { | |
41 | - this.properties.put(name, value); | |
42 | - } | |
29 | + private TelemetryMappingConfiguration observeAttr; | |
30 | + private BootstrapConfiguration bootstrap; | |
31 | + private OtherConfiguration clientLwM2mSettings; | |
43 | 32 | |
44 | 33 | @Override |
45 | 34 | public DeviceTransportType getType() { | ... | ... |
... | ... | @@ -20,7 +20,9 @@ import lombok.Data; |
20 | 20 | @Data |
21 | 21 | public class ServerSecurityConfig { |
22 | 22 | String host; |
23 | + String securityHost; | |
23 | 24 | Integer port; |
25 | + Integer securityPort; | |
24 | 26 | String serverPublicKey; |
25 | 27 | boolean bootstrapServerIs = true; |
26 | 28 | Integer clientHoldOffTime = 1; | ... | ... |
... | ... | @@ -31,12 +31,12 @@ public class OAuth2Mobile extends BaseData<OAuth2MobileId> { |
31 | 31 | |
32 | 32 | private OAuth2ParamsId oauth2ParamsId; |
33 | 33 | private String pkgName; |
34 | - private String callbackUrlScheme; | |
34 | + private String appSecret; | |
35 | 35 | |
36 | 36 | public OAuth2Mobile(OAuth2Mobile mobile) { |
37 | 37 | super(mobile); |
38 | 38 | this.oauth2ParamsId = mobile.oauth2ParamsId; |
39 | 39 | this.pkgName = mobile.pkgName; |
40 | - this.callbackUrlScheme = mobile.callbackUrlScheme; | |
40 | + this.appSecret = mobile.appSecret; | |
41 | 41 | } |
42 | 42 | } | ... | ... |
... | ... | @@ -24,6 +24,8 @@ public interface TbQueueRequestTemplate<Request extends TbQueueMsg, Response ext |
24 | 24 | |
25 | 25 | ListenableFuture<Response> send(Request request); |
26 | 26 | |
27 | + ListenableFuture<Response> send(Request request, long timeoutNs); | |
28 | + | |
27 | 29 | void stop(); |
28 | 30 | |
29 | 31 | void setMessagesStats(MessagesStats messagesStats); | ... | ... |
... | ... | @@ -19,7 +19,9 @@ import com.google.common.util.concurrent.Futures; |
19 | 19 | import com.google.common.util.concurrent.ListenableFuture; |
20 | 20 | import com.google.common.util.concurrent.SettableFuture; |
21 | 21 | import lombok.Builder; |
22 | +import lombok.Getter; | |
22 | 23 | import lombok.extern.slf4j.Slf4j; |
24 | +import org.thingsboard.common.util.TbStopWatch; | |
23 | 25 | import org.thingsboard.common.util.ThingsBoardThreadFactory; |
24 | 26 | import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
25 | 27 | import org.thingsboard.server.queue.TbQueueAdmin; |
... | ... | @@ -31,13 +33,17 @@ import org.thingsboard.server.queue.TbQueueProducer; |
31 | 33 | import org.thingsboard.server.queue.TbQueueRequestTemplate; |
32 | 34 | import org.thingsboard.server.common.stats.MessagesStats; |
33 | 35 | |
36 | +import javax.annotation.Nullable; | |
34 | 37 | import java.util.List; |
35 | 38 | import java.util.UUID; |
36 | 39 | import java.util.concurrent.ConcurrentHashMap; |
37 | -import java.util.concurrent.ConcurrentMap; | |
38 | 40 | import java.util.concurrent.ExecutorService; |
39 | 41 | import java.util.concurrent.Executors; |
42 | +import java.util.concurrent.TimeUnit; | |
40 | 43 | import java.util.concurrent.TimeoutException; |
44 | +import java.util.concurrent.locks.Lock; | |
45 | +import java.util.concurrent.locks.LockSupport; | |
46 | +import java.util.concurrent.locks.ReentrantLock; | |
41 | 47 | |
42 | 48 | @Slf4j |
43 | 49 | public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response extends TbQueueMsg> extends AbstractTbQueueTemplate |
... | ... | @@ -46,15 +52,15 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response |
46 | 52 | private final TbQueueAdmin queueAdmin; |
47 | 53 | private final TbQueueProducer<Request> requestTemplate; |
48 | 54 | private final TbQueueConsumer<Response> responseTemplate; |
49 | - private final ConcurrentMap<UUID, DefaultTbQueueRequestTemplate.ResponseMetaData<Response>> pendingRequests; | |
50 | - private final boolean internalExecutor; | |
51 | - private final ExecutorService executor; | |
52 | - private final long maxRequestTimeout; | |
53 | - private final long maxPendingRequests; | |
54 | - private final long pollInterval; | |
55 | - private volatile long tickTs = 0L; | |
56 | - private volatile long tickSize = 0L; | |
57 | - private volatile boolean stopped = false; | |
55 | + final ConcurrentHashMap<UUID, DefaultTbQueueRequestTemplate.ResponseMetaData<Response>> pendingRequests = new ConcurrentHashMap<>(); | |
56 | + final boolean internalExecutor; | |
57 | + final ExecutorService executor; | |
58 | + final long maxRequestTimeoutNs; | |
59 | + final long maxPendingRequests; | |
60 | + final long pollInterval; | |
61 | + volatile boolean stopped = false; | |
62 | + long nextCleanupNs = 0L; | |
63 | + private final Lock cleanerLock = new ReentrantLock(); | |
58 | 64 | |
59 | 65 | private MessagesStats messagesStats; |
60 | 66 | |
... | ... | @@ -65,79 +71,113 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response |
65 | 71 | long maxRequestTimeout, |
66 | 72 | long maxPendingRequests, |
67 | 73 | long pollInterval, |
68 | - ExecutorService executor) { | |
74 | + @Nullable ExecutorService executor) { | |
69 | 75 | this.queueAdmin = queueAdmin; |
70 | 76 | this.requestTemplate = requestTemplate; |
71 | 77 | this.responseTemplate = responseTemplate; |
72 | - this.pendingRequests = new ConcurrentHashMap<>(); | |
73 | - this.maxRequestTimeout = maxRequestTimeout; | |
78 | + this.maxRequestTimeoutNs = TimeUnit.MILLISECONDS.toNanos(maxRequestTimeout); | |
74 | 79 | this.maxPendingRequests = maxPendingRequests; |
75 | 80 | this.pollInterval = pollInterval; |
76 | - if (executor != null) { | |
77 | - internalExecutor = false; | |
78 | - this.executor = executor; | |
79 | - } else { | |
80 | - internalExecutor = true; | |
81 | - this.executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-queue-request-template-" + responseTemplate.getTopic())); | |
82 | - } | |
81 | + this.internalExecutor = (executor == null); | |
82 | + this.executor = internalExecutor ? createExecutor() : executor; | |
83 | + } | |
84 | + | |
85 | + ExecutorService createExecutor() { | |
86 | + return Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-queue-request-template-" + responseTemplate.getTopic())); | |
83 | 87 | } |
84 | 88 | |
85 | 89 | @Override |
86 | 90 | public void init() { |
87 | 91 | queueAdmin.createTopicIfNotExists(responseTemplate.getTopic()); |
88 | - this.requestTemplate.init(); | |
89 | - tickTs = System.currentTimeMillis(); | |
92 | + requestTemplate.init(); | |
90 | 93 | responseTemplate.subscribe(); |
91 | - executor.submit(() -> { | |
92 | - long nextCleanupMs = 0L; | |
93 | - while (!stopped) { | |
94 | - try { | |
95 | - List<Response> responses = responseTemplate.poll(pollInterval); | |
96 | - if (responses.size() > 0) { | |
97 | - log.trace("Polling responses completed, consumer records count [{}]", responses.size()); | |
98 | - } | |
99 | - responses.forEach(response -> { | |
100 | - byte[] requestIdHeader = response.getHeaders().get(REQUEST_ID_HEADER); | |
101 | - UUID requestId; | |
102 | - if (requestIdHeader == null) { | |
103 | - log.error("[{}] Missing requestId in header and body", response); | |
104 | - } else { | |
105 | - requestId = bytesToUuid(requestIdHeader); | |
106 | - log.trace("[{}] Response received: {}", requestId, response); | |
107 | - ResponseMetaData<Response> expectedResponse = pendingRequests.remove(requestId); | |
108 | - if (expectedResponse == null) { | |
109 | - log.trace("[{}] Invalid or stale request", requestId); | |
110 | - } else { | |
111 | - expectedResponse.future.set(response); | |
112 | - } | |
94 | + executor.submit(this::mainLoop); | |
95 | + } | |
96 | + | |
97 | + void mainLoop() { | |
98 | + while (!stopped) { | |
99 | + TbStopWatch sw = TbStopWatch.startNew(); | |
100 | + try { | |
101 | + fetchAndProcessResponses(); | |
102 | + } catch (Throwable e) { | |
103 | + long sleepNanos = TimeUnit.MILLISECONDS.toNanos(this.pollInterval) - sw.stopAndGetTotalTimeNanos(); | |
104 | + log.warn("Failed to obtain and process responses from queue. Going to sleep " + sleepNanos + "ns", e); | |
105 | + sleep(sleepNanos); | |
106 | + } | |
107 | + } | |
108 | + } | |
109 | + | |
110 | + void fetchAndProcessResponses() { | |
111 | + final long pendingRequestsCount = pendingRequests.mappingCount(); | |
112 | + log.trace("Starting template pool topic {}, for pendingRequests {}", responseTemplate.getTopic(), pendingRequestsCount); | |
113 | + List<Response> responses = doPoll(); //poll js responses | |
114 | + log.trace("Completed template poll topic {}, for pendingRequests [{}], received [{}] responses", responseTemplate.getTopic(), pendingRequestsCount, responses.size()); | |
115 | + responses.forEach(this::processResponse); //this can take a long time | |
116 | + responseTemplate.commit(); | |
117 | + tryCleanStaleRequests(); | |
118 | + } | |
119 | + | |
120 | + private boolean tryCleanStaleRequests() { | |
121 | + if (!cleanerLock.tryLock()) { | |
122 | + return false; | |
123 | + } | |
124 | + try { | |
125 | + log.trace("tryCleanStaleRequest..."); | |
126 | + final long currentNs = getCurrentClockNs(); | |
127 | + if (nextCleanupNs < currentNs) { | |
128 | + pendingRequests.forEach((key, value) -> { | |
129 | + if (value.expTime < currentNs) { | |
130 | + ResponseMetaData<Response> staleRequest = pendingRequests.remove(key); | |
131 | + if (staleRequest != null) { | |
132 | + setTimeoutException(key, staleRequest, currentNs); | |
113 | 133 | } |
114 | - }); | |
115 | - responseTemplate.commit(); | |
116 | - tickTs = System.currentTimeMillis(); | |
117 | - tickSize = pendingRequests.size(); | |
118 | - if (nextCleanupMs < tickTs) { | |
119 | - //cleanup; | |
120 | - pendingRequests.forEach((key, value) -> { | |
121 | - if (value.expTime < tickTs) { | |
122 | - ResponseMetaData<Response> staleRequest = pendingRequests.remove(key); | |
123 | - if (staleRequest != null) { | |
124 | - log.trace("[{}] Request timeout detected, expTime [{}], tickTs [{}]", key, staleRequest.expTime, tickTs); | |
125 | - staleRequest.future.setException(new TimeoutException()); | |
126 | - } | |
127 | - } | |
128 | - }); | |
129 | - nextCleanupMs = tickTs + maxRequestTimeout; | |
130 | - } | |
131 | - } catch (Throwable e) { | |
132 | - log.warn("Failed to obtain responses from queue.", e); | |
133 | - try { | |
134 | - Thread.sleep(pollInterval); | |
135 | - } catch (InterruptedException e2) { | |
136 | - log.trace("Failed to wait until the server has capacity to handle new responses", e2); | |
137 | 134 | } |
138 | - } | |
135 | + }); | |
136 | + setupNextCleanup(); | |
139 | 137 | } |
140 | - }); | |
138 | + } finally { | |
139 | + cleanerLock.unlock(); | |
140 | + } | |
141 | + return true; | |
142 | + } | |
143 | + | |
144 | + void setupNextCleanup() { | |
145 | + nextCleanupNs = getCurrentClockNs() + maxRequestTimeoutNs; | |
146 | + log.trace("setupNextCleanup {}", nextCleanupNs); | |
147 | + } | |
148 | + | |
149 | + List<Response> doPoll() { | |
150 | + return responseTemplate.poll(pollInterval); | |
151 | + } | |
152 | + | |
153 | + void sleep(long nanos) { | |
154 | + LockSupport.parkNanos(nanos); | |
155 | + } | |
156 | + | |
157 | + void setTimeoutException(UUID key, ResponseMetaData<Response> staleRequest, long currentNs) { | |
158 | + if (currentNs >= staleRequest.getSubmitTime() + staleRequest.getTimeout()) { | |
159 | + log.warn("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key); | |
160 | + } else { | |
161 | + log.error("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key); | |
162 | + } | |
163 | + staleRequest.future.setException(new TimeoutException()); | |
164 | + } | |
165 | + | |
166 | + void processResponse(Response response) { | |
167 | + byte[] requestIdHeader = response.getHeaders().get(REQUEST_ID_HEADER); | |
168 | + UUID requestId; | |
169 | + if (requestIdHeader == null) { | |
170 | + log.error("[{}] Missing requestId in header and body", response); | |
171 | + } else { | |
172 | + requestId = bytesToUuid(requestIdHeader); | |
173 | + log.trace("[{}] Response received: {}", requestId, String.valueOf(response).replace("\n", " ")); //TODO remove overhead | |
174 | + ResponseMetaData<Response> expectedResponse = pendingRequests.remove(requestId); | |
175 | + if (expectedResponse == null) { | |
176 | + log.warn("[{}] Invalid or stale request, response: {}", requestId, String.valueOf(response).replace("\n", " ")); | |
177 | + } else { | |
178 | + expectedResponse.future.set(response); | |
179 | + } | |
180 | + } | |
141 | 181 | } |
142 | 182 | |
143 | 183 | @Override |
... | ... | @@ -164,17 +204,48 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response |
164 | 204 | |
165 | 205 | @Override |
166 | 206 | public ListenableFuture<Response> send(Request request) { |
167 | - if (tickSize > maxPendingRequests) { | |
207 | + return send(request, this.maxRequestTimeoutNs); | |
208 | + } | |
209 | + | |
210 | + @Override | |
211 | + public ListenableFuture<Response> send(Request request, long requestTimeoutNs) { | |
212 | + if (pendingRequests.mappingCount() >= maxPendingRequests) { | |
213 | + log.warn("Pending request map is full [{}]! Consider to increase maxPendingRequests or increase processing performance", maxPendingRequests); | |
168 | 214 | return Futures.immediateFailedFuture(new RuntimeException("Pending request map is full!")); |
169 | 215 | } |
170 | 216 | UUID requestId = UUID.randomUUID(); |
171 | 217 | request.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId)); |
172 | 218 | request.getHeaders().put(RESPONSE_TOPIC_HEADER, stringToBytes(responseTemplate.getTopic())); |
173 | - request.getHeaders().put(REQUEST_TIME, longToBytes(System.currentTimeMillis())); | |
219 | + request.getHeaders().put(REQUEST_TIME, longToBytes(getCurrentTimeMs())); | |
220 | + long currentClockNs = getCurrentClockNs(); | |
174 | 221 | SettableFuture<Response> future = SettableFuture.create(); |
175 | - ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future); | |
176 | - pendingRequests.putIfAbsent(requestId, responseMetaData); | |
177 | - log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, request.getKey(), responseMetaData.expTime); | |
222 | + ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(currentClockNs + requestTimeoutNs, future, currentClockNs, requestTimeoutNs); | |
223 | + log.trace("pending {}", responseMetaData); | |
224 | + if (pendingRequests.putIfAbsent(requestId, responseMetaData) != null) { | |
225 | + log.warn("Pending request already exists [{}]!", maxPendingRequests); | |
226 | + return Futures.immediateFailedFuture(new RuntimeException("Pending request already exists !" + requestId)); | |
227 | + } | |
228 | + sendToRequestTemplate(request, requestId, future, responseMetaData); | |
229 | + return future; | |
230 | + } | |
231 | + | |
232 | + /** | |
233 | + * MONOTONIC clock instead jumping wall clock. | |
234 | + * Wrapped into the method for the test purposes to travel through the time | |
235 | + * */ | |
236 | + long getCurrentClockNs() { | |
237 | + return System.nanoTime(); | |
238 | + } | |
239 | + | |
240 | + /** | |
241 | + * Wall clock to send timestamp to an external service | |
242 | + * */ | |
243 | + long getCurrentTimeMs() { | |
244 | + return System.currentTimeMillis(); | |
245 | + } | |
246 | + | |
247 | + void sendToRequestTemplate(Request request, UUID requestId, SettableFuture<Response> future, ResponseMetaData<Response> responseMetaData) { | |
248 | + log.trace("[{}] Sending request, key [{}], expTime [{}], request {}", requestId, request.getKey(), responseMetaData.expTime, request); | |
178 | 249 | if (messagesStats != null) { |
179 | 250 | messagesStats.incrementTotal(); |
180 | 251 | } |
... | ... | @@ -184,7 +255,7 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response |
184 | 255 | if (messagesStats != null) { |
185 | 256 | messagesStats.incrementSuccessful(); |
186 | 257 | } |
187 | - log.trace("[{}] Request sent: {}", requestId, metadata); | |
258 | + log.trace("[{}] Request sent: {}, request {}", requestId, metadata, request); | |
188 | 259 | } |
189 | 260 | |
190 | 261 | @Override |
... | ... | @@ -196,17 +267,32 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response |
196 | 267 | future.setException(t); |
197 | 268 | } |
198 | 269 | }); |
199 | - return future; | |
200 | 270 | } |
201 | 271 | |
202 | - private static class ResponseMetaData<T> { | |
272 | + @Getter | |
273 | + static class ResponseMetaData<T> { | |
274 | + private final long submitTime; | |
275 | + private final long timeout; | |
203 | 276 | private final long expTime; |
204 | 277 | private final SettableFuture<T> future; |
205 | 278 | |
206 | - ResponseMetaData(long ts, SettableFuture<T> future) { | |
279 | + ResponseMetaData(long ts, SettableFuture<T> future, long submitTime, long timeout) { | |
280 | + this.submitTime = submitTime; | |
281 | + this.timeout = timeout; | |
207 | 282 | this.expTime = ts; |
208 | 283 | this.future = future; |
209 | 284 | } |
285 | + | |
286 | + @Override | |
287 | + public String toString() { | |
288 | + return "ResponseMetaData{" + | |
289 | + "submitTime=" + submitTime + | |
290 | + ", calculatedExpTime=" + (submitTime + timeout) + | |
291 | + ", deltaMs=" + (expTime - submitTime) + | |
292 | + ", expTime=" + expTime + | |
293 | + ", future=" + future + | |
294 | + '}'; | |
295 | + } | |
210 | 296 | } |
211 | 297 | |
212 | 298 | } | ... | ... |
... | ... | @@ -21,6 +21,7 @@ import org.apache.kafka.clients.consumer.ConsumerConfig; |
21 | 21 | import org.apache.kafka.clients.consumer.ConsumerRecord; |
22 | 22 | import org.apache.kafka.clients.consumer.ConsumerRecords; |
23 | 23 | import org.apache.kafka.clients.consumer.KafkaConsumer; |
24 | +import org.springframework.util.StopWatch; | |
24 | 25 | import org.thingsboard.server.queue.TbQueueAdmin; |
25 | 26 | import org.thingsboard.server.queue.TbQueueMsg; |
26 | 27 | import org.thingsboard.server.queue.common.AbstractTbQueueConsumerTemplate; |
... | ... | @@ -82,7 +83,16 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue |
82 | 83 | |
83 | 84 | @Override |
84 | 85 | protected List<ConsumerRecord<String, byte[]>> doPoll(long durationInMillis) { |
86 | + StopWatch stopWatch = new StopWatch(); | |
87 | + stopWatch.start(); | |
88 | + | |
89 | + log.trace("poll topic {} maxDuration {}", getTopic(), durationInMillis); | |
90 | + | |
85 | 91 | ConsumerRecords<String, byte[]> records = consumer.poll(Duration.ofMillis(durationInMillis)); |
92 | + | |
93 | + stopWatch.stop(); | |
94 | + log.trace("poll topic {} took {}ms", getTopic(), stopWatch.getTotalTimeMillis()); | |
95 | + | |
86 | 96 | if (records.isEmpty()) { |
87 | 97 | return Collections.emptyList(); |
88 | 98 | } else { |
... | ... | @@ -99,7 +109,7 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue |
99 | 109 | |
100 | 110 | @Override |
101 | 111 | protected void doCommit() { |
102 | - consumer.commitAsync(); | |
112 | + consumer.commitSync(); | |
103 | 113 | } |
104 | 114 | |
105 | 115 | @Override | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.queue.common; | |
17 | + | |
18 | +import lombok.extern.slf4j.Slf4j; | |
19 | +import org.junit.After; | |
20 | +import org.junit.Before; | |
21 | +import org.junit.Test; | |
22 | +import org.junit.runner.RunWith; | |
23 | +import org.mockito.ArgumentCaptor; | |
24 | +import org.mockito.Mock; | |
25 | +import org.mockito.junit.MockitoJUnitRunner; | |
26 | +import org.thingsboard.server.queue.TbQueueAdmin; | |
27 | +import org.thingsboard.server.queue.TbQueueConsumer; | |
28 | +import org.thingsboard.server.queue.TbQueueMsg; | |
29 | +import org.thingsboard.server.queue.TbQueueProducer; | |
30 | + | |
31 | +import java.util.Collections; | |
32 | +import java.util.List; | |
33 | +import java.util.UUID; | |
34 | +import java.util.concurrent.CountDownLatch; | |
35 | +import java.util.concurrent.ExecutorService; | |
36 | +import java.util.concurrent.TimeUnit; | |
37 | +import java.util.concurrent.atomic.AtomicLong; | |
38 | + | |
39 | +import static org.hamcrest.Matchers.equalTo; | |
40 | +import static org.hamcrest.Matchers.greaterThanOrEqualTo; | |
41 | +import static org.hamcrest.Matchers.is; | |
42 | +import static org.hamcrest.Matchers.lessThan; | |
43 | +import static org.mockito.ArgumentMatchers.any; | |
44 | +import static org.mockito.ArgumentMatchers.anyLong; | |
45 | +import static org.mockito.BDDMockito.willAnswer; | |
46 | +import static org.mockito.BDDMockito.willDoNothing; | |
47 | +import static org.mockito.BDDMockito.willReturn; | |
48 | +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; | |
49 | +import static org.mockito.Mockito.atLeastOnce; | |
50 | +import static org.mockito.Mockito.mock; | |
51 | +import static org.mockito.Mockito.never; | |
52 | +import static org.mockito.Mockito.spy; | |
53 | +import static org.mockito.Mockito.times; | |
54 | +import static org.mockito.Mockito.verify; | |
55 | + | |
56 | +import static org.hamcrest.MatcherAssert.assertThat; | |
57 | +import static org.mockito.hamcrest.MockitoHamcrest.longThat; | |
58 | + | |
59 | +@Slf4j | |
60 | +@RunWith(MockitoJUnitRunner.class) | |
61 | +public class DefaultTbQueueRequestTemplateTest { | |
62 | + | |
63 | + @Mock | |
64 | + TbQueueAdmin queueAdmin; | |
65 | + @Mock | |
66 | + TbQueueProducer<TbQueueMsg> requestTemplate; | |
67 | + @Mock | |
68 | + TbQueueConsumer<TbQueueMsg> responseTemplate; | |
69 | + @Mock | |
70 | + ExecutorService executorMock; | |
71 | + | |
72 | + ExecutorService executor; | |
73 | + String topic = "js-responses-tb-node-0"; | |
74 | + long maxRequestTimeout = 10; | |
75 | + long maxPendingRequests = 32; | |
76 | + long pollInterval = 5; | |
77 | + | |
78 | + DefaultTbQueueRequestTemplate inst; | |
79 | + | |
80 | + @Before | |
81 | + public void setUp() throws Exception { | |
82 | + willReturn(topic).given(responseTemplate).getTopic(); | |
83 | + inst = spy(new DefaultTbQueueRequestTemplate( | |
84 | + queueAdmin, requestTemplate, responseTemplate, | |
85 | + maxRequestTimeout, maxPendingRequests, pollInterval, executorMock)); | |
86 | + | |
87 | + } | |
88 | + | |
89 | + @After | |
90 | + public void tearDown() throws Exception { | |
91 | + if (executor != null) { | |
92 | + executor.shutdownNow(); | |
93 | + } | |
94 | + } | |
95 | + | |
96 | + @Test | |
97 | + public void givenInstance_whenVerifyInitialParameters_thenOK() { | |
98 | + assertThat(inst.maxPendingRequests, equalTo(maxPendingRequests)); | |
99 | + assertThat(inst.maxRequestTimeoutNs, equalTo(TimeUnit.MILLISECONDS.toNanos(maxRequestTimeout))); | |
100 | + assertThat(inst.pollInterval, equalTo(pollInterval)); | |
101 | + assertThat(inst.executor, is(executorMock)); | |
102 | + assertThat(inst.stopped, is(false)); | |
103 | + assertThat(inst.internalExecutor, is(false)); | |
104 | + } | |
105 | + | |
106 | + @Test | |
107 | + public void givenExternalExecutor_whenInitStop_thenOK() { | |
108 | + inst.init(); | |
109 | + assertThat(inst.nextCleanupNs, equalTo(0L)); | |
110 | + verify(queueAdmin, times(1)).createTopicIfNotExists(topic); | |
111 | + verify(requestTemplate, times(1)).init(); | |
112 | + verify(responseTemplate, times(1)).subscribe(); | |
113 | + verify(executorMock, times(1)).submit(any(Runnable.class)); | |
114 | + | |
115 | + inst.stop(); | |
116 | + assertThat(inst.stopped, is(true)); | |
117 | + verify(responseTemplate, times(1)).unsubscribe(); | |
118 | + verify(requestTemplate, times(1)).stop(); | |
119 | + verify(executorMock, never()).shutdownNow(); | |
120 | + } | |
121 | + | |
122 | + @Test | |
123 | + public void givenMainLoop_whenLoopFewTimes_thenVerifyInvocationCount() throws InterruptedException { | |
124 | + executor = inst.createExecutor(); | |
125 | + CountDownLatch latch = new CountDownLatch(5); | |
126 | + willDoNothing().given(inst).sleep(anyLong()); | |
127 | + willAnswer(invocation -> { | |
128 | + if (latch.getCount() == 1) { | |
129 | + inst.stop(); //stop the loop in natural way | |
130 | + } | |
131 | + if (latch.getCount() == 3 || latch.getCount() == 4) { | |
132 | + latch.countDown(); | |
133 | + throw new RuntimeException("test catch block"); | |
134 | + } | |
135 | + latch.countDown(); | |
136 | + return null; | |
137 | + }).given(inst).fetchAndProcessResponses(); | |
138 | + | |
139 | + executor.submit(inst::mainLoop); | |
140 | + latch.await(10, TimeUnit.SECONDS); | |
141 | + | |
142 | + verify(inst, times(5)).fetchAndProcessResponses(); | |
143 | + verify(inst, times(2)).sleep(longThat(lessThan(TimeUnit.MILLISECONDS.toNanos(inst.pollInterval)))); | |
144 | + } | |
145 | + | |
146 | + @Test | |
147 | + public void givenMessages_whenSend_thenOK() { | |
148 | + willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any()); | |
149 | + inst.init(); | |
150 | + final int msgCount = 10; | |
151 | + for (int i = 0; i < msgCount; i++) { | |
152 | + inst.send(getRequestMsgMock()); | |
153 | + } | |
154 | + assertThat(inst.pendingRequests.mappingCount(), equalTo((long) msgCount)); | |
155 | + verify(inst, times(msgCount)).sendToRequestTemplate(any(), any(), any(), any()); | |
156 | + } | |
157 | + | |
158 | + @Test | |
159 | + public void givenMessagesOverMaxPendingRequests_whenSend_thenImmediateFailedFutureForTheOfRequests() { | |
160 | + willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any()); | |
161 | + inst.init(); | |
162 | + int msgOverflowCount = 10; | |
163 | + for (int i = 0; i < inst.maxPendingRequests; i++) { | |
164 | + assertThat(inst.send(getRequestMsgMock()).isDone(), is(false)); //SettableFuture future - pending only | |
165 | + } | |
166 | + for (int i = 0; i < msgOverflowCount; i++) { | |
167 | + assertThat("max pending requests overflow", inst.send(getRequestMsgMock()).isDone(), is(true)); //overflow, immediate failed future | |
168 | + } | |
169 | + assertThat(inst.pendingRequests.mappingCount(), equalTo(inst.maxPendingRequests)); | |
170 | + verify(inst, times((int) inst.maxPendingRequests)).sendToRequestTemplate(any(), any(), any(), any()); | |
171 | + } | |
172 | + | |
173 | + @Test | |
174 | + public void givenNothing_whenSendAndFetchAndProcessResponsesWithTimeout_thenFail() { | |
175 | + //given | |
176 | + AtomicLong currentTime = new AtomicLong(); | |
177 | + willAnswer(x -> { | |
178 | + log.info("currentTime={}", currentTime.get()); | |
179 | + return currentTime.get(); | |
180 | + }).given(inst).getCurrentClockNs(); | |
181 | + inst.init(); | |
182 | + inst.setupNextCleanup(); | |
183 | + willReturn(Collections.emptyList()).given(inst).doPoll(); | |
184 | + | |
185 | + //when | |
186 | + long stepNs = TimeUnit.MILLISECONDS.toNanos(1); | |
187 | + for (long i = 0; i <= inst.maxRequestTimeoutNs * 2; i = i + stepNs) { | |
188 | + currentTime.addAndGet(stepNs); | |
189 | + assertThat(inst.send(getRequestMsgMock()).isDone(), is(false)); //SettableFuture future - pending only | |
190 | + if (i % (inst.maxRequestTimeoutNs * 3 / 2) == 0) { | |
191 | + inst.fetchAndProcessResponses(); | |
192 | + } | |
193 | + } | |
194 | + | |
195 | + //then | |
196 | + ArgumentCaptor<DefaultTbQueueRequestTemplate.ResponseMetaData> argumentCaptorResp = ArgumentCaptor.forClass(DefaultTbQueueRequestTemplate.ResponseMetaData.class); | |
197 | + ArgumentCaptor<UUID> argumentCaptorUUID = ArgumentCaptor.forClass(UUID.class); | |
198 | + ArgumentCaptor<Long> argumentCaptorLong = ArgumentCaptor.forClass(Long.class); | |
199 | + verify(inst, atLeastOnce()).setTimeoutException(argumentCaptorUUID.capture(), argumentCaptorResp.capture(), argumentCaptorLong.capture()); | |
200 | + | |
201 | + List<DefaultTbQueueRequestTemplate.ResponseMetaData> responseMetaDataList = argumentCaptorResp.getAllValues(); | |
202 | + List<Long> tickTsList = argumentCaptorLong.getAllValues(); | |
203 | + for (int i = 0; i < responseMetaDataList.size(); i++) { | |
204 | + assertThat("tickTs >= calculatedExpTime", tickTsList.get(i), greaterThanOrEqualTo(responseMetaDataList.get(i).getSubmitTime() + responseMetaDataList.get(i).getTimeout())); | |
205 | + } | |
206 | + } | |
207 | + | |
208 | + TbQueueMsg getRequestMsgMock() { | |
209 | + return mock(TbQueueMsg.class, RETURNS_DEEP_STUBS); | |
210 | + } | |
211 | +} | |
\ No newline at end of file | ... | ... |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
... | ... | @@ -44,7 +44,6 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC |
44 | 44 | import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; |
45 | 45 | import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; |
46 | 46 | import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; |
47 | -import org.thingsboard.server.common.data.ota.OtaPackageType; | |
48 | 47 | import org.thingsboard.server.common.data.rpc.RpcStatus; |
49 | 48 | import org.thingsboard.server.common.data.security.DeviceTokenCredentials; |
50 | 49 | import org.thingsboard.server.common.msg.session.FeatureType; |
... | ... | @@ -140,10 +139,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
140 | 139 | processExchangeGetRequest(exchange, featureType.get()); |
141 | 140 | } else if (featureType.get() == FeatureType.ATTRIBUTES) { |
142 | 141 | processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); |
143 | - } else if (featureType.get() == FeatureType.FIRMWARE) { | |
144 | - processRequest(exchange, SessionMsgType.GET_FIRMWARE_REQUEST); | |
145 | - } else if (featureType.get() == FeatureType.SOFTWARE) { | |
146 | - processRequest(exchange, SessionMsgType.GET_SOFTWARE_REQUEST); | |
147 | 142 | } else { |
148 | 143 | log.trace("Invalid feature type parameter"); |
149 | 144 | exchange.respond(CoAP.ResponseCode.BAD_REQUEST); |
... | ... | @@ -350,12 +345,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
350 | 345 | coapTransportAdaptor.convertToGetAttributes(sessionId, request), |
351 | 346 | new CoapNoOpCallback(exchange)); |
352 | 347 | break; |
353 | - case GET_FIRMWARE_REQUEST: | |
354 | - getOtaPackageCallback(sessionInfo, exchange, OtaPackageType.FIRMWARE); | |
355 | - break; | |
356 | - case GET_SOFTWARE_REQUEST: | |
357 | - getOtaPackageCallback(sessionInfo, exchange, OtaPackageType.SOFTWARE); | |
358 | - break; | |
359 | 348 | } |
360 | 349 | } catch (AdaptorException e) { |
361 | 350 | log.trace("[{}] Failed to decode message: ", sessionId, e); |
... | ... | @@ -367,16 +356,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
367 | 356 | return new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB()); |
368 | 357 | } |
369 | 358 | |
370 | - private void getOtaPackageCallback(TransportProtos.SessionInfoProto sessionInfo, CoapExchange exchange, OtaPackageType firmwareType) { | |
371 | - TransportProtos.GetOtaPackageRequestMsg requestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder() | |
372 | - .setTenantIdMSB(sessionInfo.getTenantIdMSB()) | |
373 | - .setTenantIdLSB(sessionInfo.getTenantIdLSB()) | |
374 | - .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) | |
375 | - .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) | |
376 | - .setType(firmwareType.name()).build(); | |
377 | - transportContext.getTransportService().process(sessionInfo, requestMsg, new OtaPackageCallback(exchange)); | |
378 | - } | |
379 | - | |
380 | 359 | private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { |
381 | 360 | tokenToObserveNotificationSeqMap.remove(token); |
382 | 361 | return tokenToSessionInfoMap.remove(token); |
... | ... | @@ -471,57 +450,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
471 | 450 | } |
472 | 451 | } |
473 | 452 | |
474 | - private class OtaPackageCallback implements TransportServiceCallback<TransportProtos.GetOtaPackageResponseMsg> { | |
475 | - private final CoapExchange exchange; | |
476 | - | |
477 | - OtaPackageCallback(CoapExchange exchange) { | |
478 | - this.exchange = exchange; | |
479 | - } | |
480 | - | |
481 | - @Override | |
482 | - public void onSuccess(TransportProtos.GetOtaPackageResponseMsg msg) { | |
483 | - String title = exchange.getQueryParameter("title"); | |
484 | - String version = exchange.getQueryParameter("version"); | |
485 | - if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) { | |
486 | - String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString(); | |
487 | - if (msg.getTitle().equals(title) && msg.getVersion().equals(version)) { | |
488 | - String strChunkSize = exchange.getQueryParameter("size"); | |
489 | - String strChunk = exchange.getQueryParameter("chunk"); | |
490 | - int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize); | |
491 | - int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk); | |
492 | - exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk)); | |
493 | - } | |
494 | - else if (firmwareId != null) { | |
495 | - sendOtaData(exchange, firmwareId); | |
496 | - } else { | |
497 | - exchange.respond(CoAP.ResponseCode.BAD_REQUEST); | |
498 | - } | |
499 | - } else { | |
500 | - exchange.respond(CoAP.ResponseCode.NOT_FOUND); | |
501 | - } | |
502 | - } | |
503 | - | |
504 | - @Override | |
505 | - public void onError(Throwable e) { | |
506 | - log.warn("Failed to process request", e); | |
507 | - exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); | |
508 | - } | |
509 | - } | |
510 | - | |
511 | - private void sendOtaData(CoapExchange exchange, String firmwareId) { | |
512 | - Response response = new Response(CoAP.ResponseCode.CONTENT); | |
513 | - byte[] fwData = transportContext.getOtaPackageDataCache().get(firmwareId); | |
514 | - if (fwData != null && fwData.length > 0) { | |
515 | - response.setPayload(fwData); | |
516 | - if (exchange.getRequestOptions().getBlock2() != null) { | |
517 | - int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); | |
518 | - boolean moreFlag = fwData.length > chunkSize; | |
519 | - response.getOptions().setBlock2(chunkSize, moreFlag, 0); | |
520 | - } | |
521 | - exchange.respond(response); | |
522 | - } | |
523 | - } | |
524 | - | |
525 | 453 | private static class CoapSessionListener implements SessionMsgListener { |
526 | 454 | |
527 | 455 | private final CoapTransportResource coapTransportResource; | ... | ... |
... | ... | @@ -23,6 +23,7 @@ import org.springframework.stereotype.Service; |
23 | 23 | import org.thingsboard.server.common.data.TbTransportService; |
24 | 24 | import org.thingsboard.server.coapserver.CoapServerService; |
25 | 25 | import org.thingsboard.server.coapserver.TbCoapServerComponent; |
26 | +import org.thingsboard.server.common.data.ota.OtaPackageType; | |
26 | 27 | import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; |
27 | 28 | |
28 | 29 | import javax.annotation.PostConstruct; |
... | ... | @@ -59,6 +60,8 @@ public class CoapTransportService implements TbTransportService { |
59 | 60 | efento.add(efentoMeasurementsTransportResource); |
60 | 61 | coapServer.add(api); |
61 | 62 | coapServer.add(efento); |
63 | + coapServer.add(new OtaPackageTransportResource(coapTransportContext, OtaPackageType.FIRMWARE)); | |
64 | + coapServer.add(new OtaPackageTransportResource(coapTransportContext, OtaPackageType.SOFTWARE)); | |
62 | 65 | log.info("CoAP transport started!"); |
63 | 66 | } |
64 | 67 | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.coap; | |
17 | + | |
18 | +import lombok.extern.slf4j.Slf4j; | |
19 | +import org.eclipse.californium.core.coap.CoAP; | |
20 | +import org.eclipse.californium.core.coap.Request; | |
21 | +import org.eclipse.californium.core.coap.Response; | |
22 | +import org.eclipse.californium.core.network.Exchange; | |
23 | +import org.eclipse.californium.core.observe.ObserveRelation; | |
24 | +import org.eclipse.californium.core.server.resources.CoapExchange; | |
25 | +import org.eclipse.californium.core.server.resources.Resource; | |
26 | +import org.eclipse.californium.core.server.resources.ResourceObserver; | |
27 | +import org.thingsboard.server.common.data.DeviceTransportType; | |
28 | +import org.thingsboard.server.common.data.StringUtils; | |
29 | +import org.thingsboard.server.common.data.ota.OtaPackageType; | |
30 | +import org.thingsboard.server.common.data.security.DeviceTokenCredentials; | |
31 | +import org.thingsboard.server.common.transport.TransportServiceCallback; | |
32 | +import org.thingsboard.server.gen.transport.TransportProtos; | |
33 | + | |
34 | +import java.util.List; | |
35 | +import java.util.Optional; | |
36 | +import java.util.UUID; | |
37 | + | |
38 | +@Slf4j | |
39 | +public class OtaPackageTransportResource extends AbstractCoapTransportResource { | |
40 | + private static final int ACCESS_TOKEN_POSITION = 2; | |
41 | + | |
42 | + private final OtaPackageType otaPackageType; | |
43 | + | |
44 | + public OtaPackageTransportResource(CoapTransportContext ctx, OtaPackageType otaPackageType) { | |
45 | + super(ctx, otaPackageType.getKeyPrefix()); | |
46 | + this.setObservable(true); | |
47 | + this.addObserver(new OtaPackageTransportResource.CoapResourceObserver()); | |
48 | + this.otaPackageType = otaPackageType; | |
49 | + } | |
50 | + | |
51 | + @Override | |
52 | + protected void processHandleGet(CoapExchange exchange) { | |
53 | + log.trace("Processing {}", exchange.advanced().getRequest()); | |
54 | + exchange.accept(); | |
55 | + Exchange advanced = exchange.advanced(); | |
56 | + Request request = advanced.getRequest(); | |
57 | + processAccessTokenRequest(exchange, request); | |
58 | + } | |
59 | + | |
60 | + @Override | |
61 | + protected void processHandlePost(CoapExchange exchange) { | |
62 | + exchange.respond(CoAP.ResponseCode.METHOD_NOT_ALLOWED); | |
63 | + } | |
64 | + | |
65 | + private void processAccessTokenRequest(CoapExchange exchange, Request request) { | |
66 | + Optional<DeviceTokenCredentials> credentials = decodeCredentials(request); | |
67 | + if (credentials.isEmpty()) { | |
68 | + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED); | |
69 | + return; | |
70 | + } | |
71 | + transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), | |
72 | + new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> { | |
73 | + getOtaPackageCallback(sessionInfo, exchange, otaPackageType); | |
74 | + })); | |
75 | + } | |
76 | + | |
77 | + private void getOtaPackageCallback(TransportProtos.SessionInfoProto sessionInfo, CoapExchange exchange, OtaPackageType firmwareType) { | |
78 | + TransportProtos.GetOtaPackageRequestMsg requestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder() | |
79 | + .setTenantIdMSB(sessionInfo.getTenantIdMSB()) | |
80 | + .setTenantIdLSB(sessionInfo.getTenantIdLSB()) | |
81 | + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) | |
82 | + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) | |
83 | + .setType(firmwareType.name()).build(); | |
84 | + transportContext.getTransportService().process(sessionInfo, requestMsg, new OtaPackageCallback(exchange)); | |
85 | + } | |
86 | + | |
87 | + private Optional<DeviceTokenCredentials> decodeCredentials(Request request) { | |
88 | + List<String> uriPath = request.getOptions().getUriPath(); | |
89 | + if (uriPath.size() == ACCESS_TOKEN_POSITION) { | |
90 | + return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); | |
91 | + } else { | |
92 | + return Optional.empty(); | |
93 | + } | |
94 | + } | |
95 | + | |
96 | + @Override | |
97 | + public Resource getChild(String name) { | |
98 | + return this; | |
99 | + } | |
100 | + | |
101 | + private class OtaPackageCallback implements TransportServiceCallback<TransportProtos.GetOtaPackageResponseMsg> { | |
102 | + private final CoapExchange exchange; | |
103 | + | |
104 | + OtaPackageCallback(CoapExchange exchange) { | |
105 | + this.exchange = exchange; | |
106 | + } | |
107 | + | |
108 | + @Override | |
109 | + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg msg) { | |
110 | + String title = exchange.getQueryParameter("title"); | |
111 | + String version = exchange.getQueryParameter("version"); | |
112 | + if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) { | |
113 | + String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString(); | |
114 | + if ((title == null || msg.getTitle().equals(title)) && (version == null || msg.getVersion().equals(version))) { | |
115 | + String strChunkSize = exchange.getQueryParameter("size"); | |
116 | + String strChunk = exchange.getQueryParameter("chunk"); | |
117 | + int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize); | |
118 | + int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk); | |
119 | + respondOtaPackage(exchange, transportContext.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk)); | |
120 | + } else { | |
121 | + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); | |
122 | + } | |
123 | + } else { | |
124 | + exchange.respond(CoAP.ResponseCode.NOT_FOUND); | |
125 | + } | |
126 | + } | |
127 | + | |
128 | + @Override | |
129 | + public void onError(Throwable e) { | |
130 | + log.warn("Failed to process request", e); | |
131 | + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); | |
132 | + } | |
133 | + } | |
134 | + | |
135 | + private void respondOtaPackage(CoapExchange exchange, byte[] data) { | |
136 | + Response response = new Response(CoAP.ResponseCode.CONTENT); | |
137 | + if (data != null && data.length > 0) { | |
138 | + response.setPayload(data); | |
139 | + if (exchange.getRequestOptions().getBlock2() != null) { | |
140 | + int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); | |
141 | + boolean lastFlag = data.length > chunkSize; | |
142 | + response.getOptions().setUriPath(exchange.getRequestOptions().getUriPathString()); | |
143 | + response.getOptions().setBlock2(chunkSize, lastFlag, 0); | |
144 | + } | |
145 | + exchange.respond(response); | |
146 | + } | |
147 | + } | |
148 | + | |
149 | + public class CoapResourceObserver implements ResourceObserver { | |
150 | + @Override | |
151 | + public void changedName(String old) { | |
152 | + | |
153 | + } | |
154 | + | |
155 | + @Override | |
156 | + public void changedPath(String old) { | |
157 | + | |
158 | + } | |
159 | + | |
160 | + @Override | |
161 | + public void addedChild(Resource child) { | |
162 | + | |
163 | + } | |
164 | + | |
165 | + @Override | |
166 | + public void removedChild(Resource child) { | |
167 | + | |
168 | + } | |
169 | + | |
170 | + @Override | |
171 | + public void addedObserveRelation(ObserveRelation relation) { | |
172 | + | |
173 | + } | |
174 | + | |
175 | + @Override | |
176 | + public void removedObserveRelation(ObserveRelation relation) { | |
177 | + | |
178 | + } | |
179 | + } | |
180 | + | |
181 | +} | ... | ... |
... | ... | @@ -15,9 +15,6 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.transport.lwm2m.bootstrap.secure; |
17 | 17 | |
18 | -import com.fasterxml.jackson.core.JsonProcessingException; | |
19 | -import com.fasterxml.jackson.databind.ObjectMapper; | |
20 | -import com.google.gson.JsonObject; | |
21 | 18 | import lombok.extern.slf4j.Slf4j; |
22 | 19 | import org.eclipse.leshan.core.SecurityMode; |
23 | 20 | import org.eclipse.leshan.core.util.Hex; |
... | ... | @@ -29,6 +26,8 @@ import org.eclipse.leshan.server.security.BootstrapSecurityStore; |
29 | 26 | import org.eclipse.leshan.server.security.SecurityInfo; |
30 | 27 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
31 | 28 | import org.springframework.stereotype.Service; |
29 | +import org.thingsboard.common.util.JacksonUtil; | |
30 | +import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration; | |
32 | 31 | import org.thingsboard.server.gen.transport.TransportProtos; |
33 | 32 | import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; |
34 | 33 | import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; |
... | ... | @@ -43,12 +42,9 @@ import java.util.Collections; |
43 | 42 | import java.util.Iterator; |
44 | 43 | import java.util.UUID; |
45 | 44 | |
46 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.BOOTSTRAP_SERVER; | |
47 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; | |
48 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; | |
49 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_TELEMETRY; | |
50 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_SERVER; | |
51 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SERVERS; | |
45 | +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; | |
46 | +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; | |
47 | +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; | |
52 | 48 | import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getBootstrapParametersFromThingsboard; |
53 | 49 | |
54 | 50 | @Slf4j |
... | ... | @@ -151,35 +147,30 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { |
151 | 147 | } |
152 | 148 | |
153 | 149 | private LwM2MBootstrapConfig getParametersBootstrap(TbLwM2MSecurityInfo store) { |
154 | - try { | |
155 | - LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig(); | |
156 | - if (lwM2MBootstrapConfig != null) { | |
157 | - ObjectMapper mapper = new ObjectMapper(); | |
158 | - JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); | |
159 | - lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class); | |
160 | - LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); | |
161 | - LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class); | |
162 | - UUID sessionUUiD = UUID.randomUUID(); | |
163 | - TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); | |
164 | - context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, sessionInfo, context.getTransportService())); | |
165 | - if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { | |
166 | - lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); | |
167 | - lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); | |
168 | - String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndpoint()); | |
169 | - helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), sessionInfo); | |
170 | - return lwM2MBootstrapConfig; | |
171 | - } else { | |
172 | - log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint()); | |
173 | - log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint()); | |
174 | - String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint()); | |
175 | - helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), sessionInfo); | |
176 | - return null; | |
177 | - } | |
150 | + LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig(); | |
151 | + if (lwM2MBootstrapConfig != null) { | |
152 | + BootstrapConfiguration bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); | |
153 | + lwM2MBootstrapConfig.servers = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getServers()), LwM2MBootstrapServers.class); | |
154 | + LwM2MServerBootstrap profileServerBootstrap = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getBootstrapServer()), LwM2MServerBootstrap.class); | |
155 | + LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class); | |
156 | + UUID sessionUUiD = UUID.randomUUID(); | |
157 | + TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); | |
158 | + context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, null, null, sessionInfo, context.getTransportService())); | |
159 | + if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { | |
160 | + lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); | |
161 | + lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); | |
162 | + String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LWM2M_INFO, store.getEndpoint()); | |
163 | + helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), sessionInfo); | |
164 | + return lwM2MBootstrapConfig; | |
165 | + } else { | |
166 | + log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint()); | |
167 | + log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR, store.getEndpoint()); | |
168 | + String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR, store.getEndpoint()); | |
169 | + helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), sessionInfo); | |
170 | + return null; | |
178 | 171 | } |
179 | - } catch (JsonProcessingException e) { | |
180 | - log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndpoint(), e.getMessage()); | |
181 | - return null; | |
182 | 172 | } |
173 | + | |
183 | 174 | log.error("Unable to decode Json or Certificate for [{}]", store.getEndpoint()); |
184 | 175 | return null; |
185 | 176 | } | ... | ... |
... | ... | @@ -57,30 +57,22 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { |
57 | 57 | private boolean recommendedSupportedGroups; |
58 | 58 | |
59 | 59 | @Getter |
60 | - @Value("${transport.lwm2m.response_pool_size:}") | |
61 | - private int responsePoolSize; | |
60 | + @Value("${transport.lwm2m.downlink_pool_size:}") | |
61 | + private int downlinkPoolSize; | |
62 | 62 | |
63 | 63 | @Getter |
64 | - @Value("${transport.lwm2m.registered_pool_size:}") | |
65 | - private int registeredPoolSize; | |
64 | + @Value("${transport.lwm2m.uplink_pool_size:}") | |
65 | + private int uplinkPoolSize; | |
66 | 66 | |
67 | 67 | @Getter |
68 | - @Value("${transport.lwm2m.registration_store_pool_size:}") | |
69 | - private int registrationStorePoolSize; | |
68 | + @Value("${transport.lwm2m.ota_pool_size:}") | |
69 | + private int otaPoolSize; | |
70 | 70 | |
71 | 71 | @Getter |
72 | 72 | @Value("${transport.lwm2m.clean_period_in_sec:}") |
73 | 73 | private int cleanPeriodInSec; |
74 | 74 | |
75 | 75 | @Getter |
76 | - @Value("${transport.lwm2m.update_registered_pool_size:}") | |
77 | - private int updateRegisteredPoolSize; | |
78 | - | |
79 | - @Getter | |
80 | - @Value("${transport.lwm2m.un_registered_pool_size:}") | |
81 | - private int unRegisteredPoolSize; | |
82 | - | |
83 | - @Getter | |
84 | 76 | @Value("${transport.lwm2m.security.key_store_type:}") |
85 | 77 | private String keyStoreType; |
86 | 78 | ... | ... |
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java
deleted
100644 → 0
1 | -/** | |
2 | - * Copyright © 2016-2021 The Thingsboard Authors | |
3 | - * | |
4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | - * you may not use this file except in compliance with the License. | |
6 | - * You may obtain a copy of the License at | |
7 | - * | |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | - * | |
10 | - * Unless required by applicable law or agreed to in writing, software | |
11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | - * See the License for the specific language governing permissions and | |
14 | - * limitations under the License. | |
15 | - */ | |
16 | -package org.thingsboard.server.transport.lwm2m.server; | |
17 | - | |
18 | -import com.fasterxml.jackson.core.type.TypeReference; | |
19 | -import com.google.gson.Gson; | |
20 | -import com.google.gson.GsonBuilder; | |
21 | -import com.google.gson.JsonArray; | |
22 | -import com.google.gson.JsonElement; | |
23 | -import com.google.gson.JsonObject; | |
24 | -import com.google.gson.reflect.TypeToken; | |
25 | -import lombok.extern.slf4j.Slf4j; | |
26 | -import org.apache.commons.lang3.StringUtils; | |
27 | -import org.eclipse.leshan.core.model.ObjectModel; | |
28 | -import org.eclipse.leshan.core.model.ResourceModel; | |
29 | -import org.eclipse.leshan.core.node.LwM2mObject; | |
30 | -import org.eclipse.leshan.core.node.LwM2mObjectInstance; | |
31 | -import org.eclipse.leshan.core.node.LwM2mPath; | |
32 | -import org.eclipse.leshan.core.node.LwM2mResource; | |
33 | -import org.eclipse.leshan.core.observation.Observation; | |
34 | -import org.eclipse.leshan.core.request.WriteRequest; | |
35 | -import org.eclipse.leshan.core.response.ReadResponse; | |
36 | -import org.eclipse.leshan.server.registration.Registration; | |
37 | -import org.springframework.context.annotation.Lazy; | |
38 | -import org.springframework.stereotype.Service; | |
39 | -import org.thingsboard.common.util.JacksonUtil; | |
40 | -import org.thingsboard.common.util.ThingsBoardExecutors; | |
41 | -import org.thingsboard.server.cache.ota.OtaPackageDataCache; | |
42 | -import org.thingsboard.server.common.data.Device; | |
43 | -import org.thingsboard.server.common.data.DeviceProfile; | |
44 | -import org.thingsboard.server.common.data.id.OtaPackageId; | |
45 | -import org.thingsboard.server.common.data.ota.OtaPackageKey; | |
46 | -import org.thingsboard.server.common.data.ota.OtaPackageType; | |
47 | -import org.thingsboard.server.common.data.ota.OtaPackageUtil; | |
48 | -import org.thingsboard.server.common.transport.TransportService; | |
49 | -import org.thingsboard.server.common.transport.TransportServiceCallback; | |
50 | -import org.thingsboard.server.common.transport.adaptor.AdaptorException; | |
51 | -import org.thingsboard.server.common.transport.service.DefaultTransportService; | |
52 | -import org.thingsboard.server.gen.transport.TransportProtos; | |
53 | -import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; | |
54 | -import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; | |
55 | -import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; | |
56 | -import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; | |
57 | -import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; | |
58 | -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; | |
59 | -import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor; | |
60 | -import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState; | |
61 | -import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientStateException; | |
62 | -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; | |
63 | -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; | |
64 | -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; | |
65 | -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientRpcRequest; | |
66 | -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mFwSwUpdate; | |
67 | -import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; | |
68 | -import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto; | |
69 | -import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters; | |
70 | -import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; | |
71 | -import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; | |
72 | - | |
73 | -import javax.annotation.PostConstruct; | |
74 | -import java.util.ArrayList; | |
75 | -import java.util.Collection; | |
76 | -import java.util.Collections; | |
77 | -import java.util.HashSet; | |
78 | -import java.util.List; | |
79 | -import java.util.Map; | |
80 | -import java.util.Optional; | |
81 | -import java.util.Random; | |
82 | -import java.util.Set; | |
83 | -import java.util.UUID; | |
84 | -import java.util.concurrent.ConcurrentHashMap; | |
85 | -import java.util.concurrent.ConcurrentMap; | |
86 | -import java.util.concurrent.ExecutorService; | |
87 | -import java.util.concurrent.TimeUnit; | |
88 | -import java.util.stream.Collectors; | |
89 | - | |
90 | -import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST; | |
91 | -import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; | |
92 | -import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; | |
93 | -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; | |
94 | -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED; | |
95 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto; | |
96 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEVICE_ATTRIBUTES_REQUEST; | |
97 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_ID; | |
98 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID; | |
99 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID; | |
100 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; | |
101 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; | |
102 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_TELEMETRY; | |
103 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE; | |
104 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_WARN; | |
105 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER; | |
106 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE; | |
107 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; | |
108 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL_ALL; | |
109 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.READ; | |
110 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; | |
111 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; | |
112 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_ID; | |
113 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertJsonArrayToSet; | |
114 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; | |
115 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; | |
116 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; | |
117 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getAckCallback; | |
118 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.isFwSwWords; | |
119 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.setValidTypeOper; | |
120 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey; | |
121 | - | |
122 | - | |
123 | -@Slf4j | |
124 | -@Service | |
125 | -@TbLwM2mTransportComponent | |
126 | -public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler { | |
127 | - | |
128 | - private ExecutorService registrationExecutor; | |
129 | - private ExecutorService updateRegistrationExecutor; | |
130 | - private ExecutorService unRegistrationExecutor; | |
131 | - public LwM2mValueConverterImpl converter; | |
132 | - | |
133 | - private final TransportService transportService; | |
134 | - private final LwM2mTransportContext context; | |
135 | - public final LwM2MTransportServerConfig config; | |
136 | - public final OtaPackageDataCache otaPackageDataCache; | |
137 | - public final LwM2mTransportServerHelper helper; | |
138 | - private final LwM2MJsonAdaptor adaptor; | |
139 | - private final TbLwM2MDtlsSessionStore sessionStore; | |
140 | - public final LwM2mClientContext clientContext; | |
141 | - public final LwM2mTransportRequest lwM2mTransportRequest; | |
142 | - private final Map<UUID, Long> rpcSubscriptions; | |
143 | - public final Map<String, Integer> firmwareUpdateState; | |
144 | - | |
145 | - public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, | |
146 | - LwM2mClientContext clientContext, | |
147 | - @Lazy LwM2mTransportRequest lwM2mTransportRequest, | |
148 | - OtaPackageDataCache otaPackageDataCache, | |
149 | - LwM2mTransportContext context, LwM2MJsonAdaptor adaptor, TbLwM2MDtlsSessionStore sessionStore) { | |
150 | - this.transportService = transportService; | |
151 | - this.config = config; | |
152 | - this.helper = helper; | |
153 | - this.clientContext = clientContext; | |
154 | - this.lwM2mTransportRequest = lwM2mTransportRequest; | |
155 | - this.otaPackageDataCache = otaPackageDataCache; | |
156 | - this.context = context; | |
157 | - this.adaptor = adaptor; | |
158 | - this.rpcSubscriptions = new ConcurrentHashMap<>(); | |
159 | - this.firmwareUpdateState = new ConcurrentHashMap<>(); | |
160 | - this.sessionStore = sessionStore; | |
161 | - } | |
162 | - | |
163 | - @PostConstruct | |
164 | - public void init() { | |
165 | - this.context.getScheduler().scheduleAtFixedRate(this::reportActivity, new Random().nextInt((int) config.getSessionReportTimeout()), config.getSessionReportTimeout(), TimeUnit.MILLISECONDS); | |
166 | - this.registrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getRegisteredPoolSize(), "LwM2M registration"); | |
167 | - this.updateRegistrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getUpdateRegisteredPoolSize(), "LwM2M update registration"); | |
168 | - this.unRegistrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getUnRegisteredPoolSize(), "LwM2M unRegistration"); | |
169 | - this.converter = LwM2mValueConverterImpl.getInstance(); | |
170 | - } | |
171 | - | |
172 | - /** | |
173 | - * Start registration device | |
174 | - * Create session: Map<String <registrationId >, LwM2MClient> | |
175 | - * 1. replaceNewRegistration -> (solving the problem of incorrect termination of the previous session with this endpoint) | |
176 | - * 1.1 When we initialize the registration, we register the session by endpoint. | |
177 | - * 1.2 If the server has incomplete requests (canceling the registration of the previous session), | |
178 | - * delete the previous session only by the previous registration.getId | |
179 | - * 1.2 Add Model (Entity) for client (from registration & observe) by registration.getId | |
180 | - * 1.2 Remove from sessions Model by enpPoint | |
181 | - * Next -> Create new LwM2MClient for current session -> setModelClient... | |
182 | - * | |
183 | - * @param registration - Registration LwM2M Client | |
184 | - * @param previousObservations - may be null | |
185 | - */ | |
186 | - public void onRegistered(Registration registration, Collection<Observation> previousObservations) { | |
187 | - registrationExecutor.submit(() -> { | |
188 | - LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint()); | |
189 | - try { | |
190 | - log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); | |
191 | - if (lwM2MClient != null) { | |
192 | - this.clientContext.register(lwM2MClient, registration); | |
193 | - this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_INFO + ": Client registered with registration id: " + registration.getId()); | |
194 | - SessionInfoProto sessionInfo = lwM2MClient.getSession(); | |
195 | - transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo, transportService)); | |
196 | - log.warn("40) sessionId [{}] Registering rpc subscription after Registration client", new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); | |
197 | - TransportProtos.TransportToDeviceActorMsg msg = TransportProtos.TransportToDeviceActorMsg.newBuilder() | |
198 | - .setSessionInfo(sessionInfo) | |
199 | - .setSessionEvent(DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN)) | |
200 | - .setSubscribeToAttributes(TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setSessionType(TransportProtos.SessionType.ASYNC).build()) | |
201 | - .setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder().setSessionType(TransportProtos.SessionType.ASYNC).build()) | |
202 | - .build(); | |
203 | - transportService.process(msg, null); | |
204 | - this.getInfoFirmwareUpdate(lwM2MClient, null); | |
205 | - this.getInfoSoftwareUpdate(lwM2MClient, null); | |
206 | - this.initClientTelemetry(lwM2MClient); | |
207 | - } else { | |
208 | - log.error("Client: [{}] onRegistered [{}] name [{}] lwM2MClient ", registration.getId(), registration.getEndpoint(), null); | |
209 | - } | |
210 | - } catch (LwM2MClientStateException stateException) { | |
211 | - if (LwM2MClientState.UNREGISTERED.equals(stateException.getState())) { | |
212 | - log.info("[{}] retry registration due to race condition: [{}].", registration.getEndpoint(), stateException.getState()); | |
213 | - // Race condition detected and the client was in progress of unregistration while new registration arrived. Let's try again. | |
214 | - onRegistered(registration, previousObservations); | |
215 | - } else { | |
216 | - this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState()); | |
217 | - } | |
218 | - } catch (Throwable t) { | |
219 | - log.error("[{}] endpoint [{}] error Unable registration.", registration.getEndpoint(), t); | |
220 | - this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_WARN + ": Client registration failed due to: " + t.getMessage()); | |
221 | - } | |
222 | - }); | |
223 | - } | |
224 | - | |
225 | - /** | |
226 | - * if sessionInfo removed from sessions, then new registerAsyncSession | |
227 | - * | |
228 | - * @param registration - Registration LwM2M Client | |
229 | - */ | |
230 | - public void updatedReg(Registration registration) { | |
231 | - updateRegistrationExecutor.submit(() -> { | |
232 | - LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); | |
233 | - try { | |
234 | - clientContext.updateRegistration(lwM2MClient, registration); | |
235 | - TransportProtos.SessionInfoProto sessionInfo = lwM2MClient.getSession(); | |
236 | - this.reportActivityAndRegister(sessionInfo); | |
237 | - if (registration.usesQueueMode()) { | |
238 | - LwM2mQueuedRequest request; | |
239 | - while ((request = lwM2MClient.getQueuedRequests().poll()) != null) { | |
240 | - request.send(); | |
241 | - } | |
242 | - } | |
243 | - } catch (LwM2MClientStateException stateException) { | |
244 | - if (LwM2MClientState.REGISTERED.equals(stateException.getState())) { | |
245 | - log.info("[{}] update registration failed because client has different registration id: [{}] {}.", registration.getEndpoint(), stateException.getState(), stateException.getMessage()); | |
246 | - } else { | |
247 | - onRegistered(registration, Collections.emptyList()); | |
248 | - } | |
249 | - } catch (Throwable t) { | |
250 | - log.error("[{}] endpoint [{}] error Unable update registration.", registration.getEndpoint(), t); | |
251 | - this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_ERROR + String.format(": Client update Registration, %s", t.getMessage())); | |
252 | - } | |
253 | - }); | |
254 | - } | |
255 | - | |
256 | - /** | |
257 | - * @param registration - Registration LwM2M Client | |
258 | - * @param observations - !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect | |
259 | - */ | |
260 | - public void unReg(Registration registration, Collection<Observation> observations) { | |
261 | - unRegistrationExecutor.submit(() -> { | |
262 | - LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint()); | |
263 | - try { | |
264 | - this.sendLogsToThingsboard(client, LOG_LW2M_INFO + ": Client unRegistration"); | |
265 | - clientContext.unregister(client, registration); | |
266 | - SessionInfoProto sessionInfo = client.getSession(); | |
267 | - if (sessionInfo != null) { | |
268 | - this.doCloseSession(sessionInfo); | |
269 | - transportService.deregisterSession(sessionInfo); | |
270 | - sessionStore.remove(registration.getEndpoint()); | |
271 | - log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); | |
272 | - } else { | |
273 | - log.error("Client close session: [{}] unReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); | |
274 | - } | |
275 | - } catch (LwM2MClientStateException stateException) { | |
276 | - log.info("[{}] delete registration: [{}] {}.", registration.getEndpoint(), stateException.getState(), stateException.getMessage()); | |
277 | - } catch (Throwable t) { | |
278 | - log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t); | |
279 | - this.sendLogsToThingsboard(client, LOG_LW2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage())); | |
280 | - } | |
281 | - }); | |
282 | - } | |
283 | - | |
284 | - @Override | |
285 | - public void onSleepingDev(Registration registration) { | |
286 | - log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); | |
287 | - this.sendLogsToThingsboard(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LW2M_INFO + ": Client is sleeping!"); | |
288 | - //TODO: associate endpointId with device information. | |
289 | - } | |
290 | - | |
291 | - /** | |
292 | - * Cancel observation for All objects for this registration | |
293 | - */ | |
294 | - @Override | |
295 | - public void setCancelObservationsAll(Registration registration) { | |
296 | - if (registration != null) { | |
297 | - LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint()); | |
298 | - if (client != null && client.getRegistration() != null && client.getRegistration().getId().equals(registration.getId())) { | |
299 | - this.lwM2mTransportRequest.sendAllRequest(client, null, OBSERVE_CANCEL_ALL, | |
300 | - null, null, this.config.getTimeout(), null); | |
301 | - } | |
302 | - } | |
303 | - } | |
304 | - | |
305 | - /** | |
306 | - * Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource | |
307 | - * | |
308 | - * @param registration - Registration LwM2M Client | |
309 | - * @param path - observe | |
310 | - * @param response - observe | |
311 | - */ | |
312 | - @Override | |
313 | - public void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, LwM2mClientRpcRequest rpcRequest) { | |
314 | - if (response.getContent() != null) { | |
315 | - LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); | |
316 | - ObjectModel objectModelVersion = lwM2MClient.getObjectModel(path, this.config.getModelProvider()); | |
317 | - if (objectModelVersion != null) { | |
318 | - if (response.getContent() instanceof LwM2mObject) { | |
319 | - LwM2mObject lwM2mObject = (LwM2mObject) response.getContent(); | |
320 | - this.updateObjectResourceValue(registration, lwM2mObject, path); | |
321 | - } else if (response.getContent() instanceof LwM2mObjectInstance) { | |
322 | - LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) response.getContent(); | |
323 | - this.updateObjectInstanceResourceValue(registration, lwM2mObjectInstance, path); | |
324 | - } else if (response.getContent() instanceof LwM2mResource) { | |
325 | - LwM2mResource lwM2mResource = (LwM2mResource) response.getContent(); | |
326 | - this.updateResourcesValue(registration, lwM2mResource, path); | |
327 | - } | |
328 | - } | |
329 | - if (rpcRequest != null) { | |
330 | - this.sendRpcRequestAfterReadResponse(registration, lwM2MClient, path, response, rpcRequest); | |
331 | - } | |
332 | - } | |
333 | - } | |
334 | - | |
335 | - private void sendRpcRequestAfterReadResponse(Registration registration, LwM2mClient lwM2MClient, String pathIdVer, ReadResponse response, | |
336 | - LwM2mClientRpcRequest rpcRequest) { | |
337 | - Object value = null; | |
338 | - if (response.getContent() instanceof LwM2mObject) { | |
339 | - value = lwM2MClient.objectToString((LwM2mObject) response.getContent(), this.converter, pathIdVer); | |
340 | - } else if (response.getContent() instanceof LwM2mObjectInstance) { | |
341 | - value = lwM2MClient.instanceToString((LwM2mObjectInstance) response.getContent(), this.converter, pathIdVer); | |
342 | - } else if (response.getContent() instanceof LwM2mResource) { | |
343 | - value = lwM2MClient.resourceToString((LwM2mResource) response.getContent(), this.converter, pathIdVer); | |
344 | - } | |
345 | - String msg = String.format("%s: type operation %s path - %s value - %s", LOG_LW2M_INFO, | |
346 | - READ, pathIdVer, value); | |
347 | - this.sendLogsToThingsboard(lwM2MClient, msg); | |
348 | - rpcRequest.setValueMsg(String.format("%s", value)); | |
349 | - this.sentRpcResponse(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE); | |
350 | - } | |
351 | - | |
352 | - /** | |
353 | - * Update - send request in change value resources in Client | |
354 | - * 1. FirmwareUpdate: | |
355 | - * - If msg.getSharedUpdatedList().forEach(tsKvProto -> {tsKvProto.getKv().getKey().indexOf(FIRMWARE_UPDATE_PREFIX, 0) == 0 | |
356 | - * 2. Shared Other AttributeUpdate | |
357 | - * -- Path to resources from profile equal keyName or from ModelObject equal name | |
358 | - * -- Only for resources: isWritable && isPresent as attribute in profile -> LwM2MClientProfile (format: CamelCase) | |
359 | - * 3. Delete - nothing | |
360 | - * | |
361 | - * @param msg - | |
362 | - */ | |
363 | - @Override | |
364 | - public void onAttributeUpdate(AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { | |
365 | - LwM2mClient lwM2MClient = clientContext.getClientBySessionInfo(sessionInfo); | |
366 | - if (msg.getSharedUpdatedCount() > 0 && lwM2MClient != null) { | |
367 | - log.warn("2) OnAttributeUpdate, SharedUpdatedList() [{}]", msg.getSharedUpdatedList()); | |
368 | - msg.getSharedUpdatedList().forEach(tsKvProto -> { | |
369 | - String pathName = tsKvProto.getKv().getKey(); | |
370 | - String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName); | |
371 | - Object valueNew = getValueFromKvProto(tsKvProto.getKv()); | |
372 | - if ((OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) | |
373 | - && (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion()))) | |
374 | - || (OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE).equals(pathName) | |
375 | - && (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentTitle())))) { | |
376 | - this.getInfoFirmwareUpdate(lwM2MClient, null); | |
377 | - } else if ((OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION).equals(pathName) | |
378 | - && (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentVersion()))) | |
379 | - || (OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE).equals(pathName) | |
380 | - && (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentTitle())))) { | |
381 | - this.getInfoSoftwareUpdate(lwM2MClient, null); | |
382 | - } | |
383 | - if (pathIdVer != null) { | |
384 | - ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config | |
385 | - .getModelProvider()); | |
386 | - if (resourceModel != null && resourceModel.operations.isWritable()) { | |
387 | - this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer); | |
388 | - } else { | |
389 | - log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", pathIdVer, valueNew); | |
390 | - String logMsg = String.format("%s: attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", | |
391 | - LOG_LW2M_ERROR, pathIdVer, valueNew); | |
392 | - this.sendLogsToThingsboard(lwM2MClient, logMsg); | |
393 | - } | |
394 | - } else if (!isFwSwWords(pathName)) { | |
395 | - log.error("Resource name name - [{}] value - [{}] is not present as attribute/telemetry in profile and cannot be updated", pathName, valueNew); | |
396 | - String logMsg = String.format("%s: attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated", | |
397 | - LOG_LW2M_ERROR, pathName, valueNew); | |
398 | - this.sendLogsToThingsboard(lwM2MClient, logMsg); | |
399 | - } | |
400 | - | |
401 | - }); | |
402 | - } else if (msg.getSharedDeletedCount() > 0 && lwM2MClient != null) { | |
403 | - msg.getSharedUpdatedList().forEach(tsKvProto -> { | |
404 | - String pathName = tsKvProto.getKv().getKey(); | |
405 | - Object valueNew = getValueFromKvProto(tsKvProto.getKv()); | |
406 | - if (OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())) { | |
407 | - lwM2MClient.getFwUpdate().setCurrentVersion((String) valueNew); | |
408 | - } | |
409 | - }); | |
410 | - log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); | |
411 | - } else if (lwM2MClient == null) { | |
412 | - log.error("OnAttributeUpdate, lwM2MClient is null"); | |
413 | - } | |
414 | - } | |
415 | - | |
416 | - /** | |
417 | - * @param sessionInfo - | |
418 | - * @param deviceProfile - | |
419 | - */ | |
420 | - @Override | |
421 | - public void onDeviceProfileUpdate(SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { | |
422 | - List<LwM2mClient> clients = clientContext.getLwM2mClients() | |
423 | - .stream().filter(e -> e.getProfileId().equals(deviceProfile.getUuidId())).collect(Collectors.toList()); | |
424 | - clients.forEach(client -> client.onDeviceProfileUpdate(deviceProfile)); | |
425 | - if (clients.size() > 0) { | |
426 | - this.onDeviceProfileUpdate(clients, deviceProfile); | |
427 | - } | |
428 | - } | |
429 | - | |
430 | - @Override | |
431 | - public void onDeviceUpdate(SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) { | |
432 | - //TODO: check, maybe device has multiple sessions/registrations? Is this possible according to the standard. | |
433 | - LwM2mClient client = clientContext.getClientByDeviceId(device.getUuidId()); | |
434 | - if (client != null) { | |
435 | - this.onDeviceUpdate(client, device, deviceProfileOpt); | |
436 | - } | |
437 | - } | |
438 | - | |
439 | - @Override | |
440 | - public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) { | |
441 | - String idVer = resourceUpdateMsgOpt.get().getResourceKey(); | |
442 | - clientContext.getLwM2mClients().forEach(e -> e.updateResourceModel(idVer, this.config.getModelProvider())); | |
443 | - } | |
444 | - | |
445 | - @Override | |
446 | - public void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) { | |
447 | - String pathIdVer = resourceDeleteMsgOpt.get().getResourceKey(); | |
448 | - clientContext.getLwM2mClients().forEach(e -> e.deleteResources(pathIdVer, this.config.getModelProvider())); | |
449 | - } | |
450 | - | |
451 | - /** | |
452 | - * #1 del from rpcSubscriptions by timeout | |
453 | - * #2 if not present in rpcSubscriptions by requestId: create new LwM2mClientRpcRequest, after success - add requestId, timeout | |
454 | - */ | |
455 | - @Override | |
456 | - public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg, SessionInfoProto sessionInfo) { | |
457 | - // #1 | |
458 | - this.checkRpcRequestTimeout(); | |
459 | - log.warn("4) toDeviceRpcRequestMsg: [{}], sessionUUID: [{}]", toDeviceRpcRequestMsg, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); | |
460 | - String bodyParams = StringUtils.trimToNull(toDeviceRpcRequestMsg.getParams()) != null ? toDeviceRpcRequestMsg.getParams() : "null"; | |
461 | - LwM2mTypeOper lwM2mTypeOper = setValidTypeOper(toDeviceRpcRequestMsg.getMethodName()); | |
462 | - UUID requestUUID = new UUID(toDeviceRpcRequestMsg.getRequestIdMSB(), toDeviceRpcRequestMsg.getRequestIdLSB()); | |
463 | - if (!this.rpcSubscriptions.containsKey(requestUUID)) { | |
464 | - this.rpcSubscriptions.put(requestUUID, toDeviceRpcRequestMsg.getExpirationTime()); | |
465 | - LwM2mClientRpcRequest lwm2mClientRpcRequest = null; | |
466 | - try { | |
467 | - LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); | |
468 | - Registration registration = client.getRegistration(); | |
469 | - if(registration != null) { | |
470 | - lwm2mClientRpcRequest = new LwM2mClientRpcRequest(lwM2mTypeOper, bodyParams, toDeviceRpcRequestMsg.getRequestId(), sessionInfo, registration, this); | |
471 | - if (lwm2mClientRpcRequest.getErrorMsg() != null) { | |
472 | - lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); | |
473 | - this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); | |
474 | - } else { | |
475 | - lwM2mTransportRequest.sendAllRequest(client, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), | |
476 | - null, | |
477 | - lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(), | |
478 | - this.config.getTimeout(), lwm2mClientRpcRequest); | |
479 | - } | |
480 | - } else { | |
481 | - this.sendErrorRpcResponse(lwm2mClientRpcRequest, "registration == null", sessionInfo); | |
482 | - } | |
483 | - } catch (Exception e) { | |
484 | - this.sendErrorRpcResponse(lwm2mClientRpcRequest, e.getMessage(), sessionInfo); | |
485 | - } | |
486 | - } | |
487 | - } | |
488 | - | |
489 | - private void sendErrorRpcResponse(LwM2mClientRpcRequest lwm2mClientRpcRequest, String msgError, SessionInfoProto sessionInfo) { | |
490 | - if (lwm2mClientRpcRequest == null) { | |
491 | - lwm2mClientRpcRequest = new LwM2mClientRpcRequest(); | |
492 | - } | |
493 | - lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); | |
494 | - if (lwm2mClientRpcRequest.getErrorMsg() == null) { | |
495 | - lwm2mClientRpcRequest.setErrorMsg(msgError); | |
496 | - } | |
497 | - this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); | |
498 | - } | |
499 | - | |
500 | - private void checkRpcRequestTimeout() { | |
501 | - log.warn("4.1) before rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); | |
502 | - if (rpcSubscriptions.size() > 0) { | |
503 | - Set<UUID> rpcSubscriptionsToRemove = rpcSubscriptions.entrySet().stream().filter(kv -> System.currentTimeMillis() > kv.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet()); | |
504 | - log.warn("4.2) System.currentTimeMillis(): [{}]", System.currentTimeMillis()); | |
505 | - log.warn("4.3) rpcSubscriptionsToRemove: [{}]", rpcSubscriptionsToRemove); | |
506 | - rpcSubscriptionsToRemove.forEach(rpcSubscriptions::remove); | |
507 | - } | |
508 | - log.warn("4.4) after rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); | |
509 | - } | |
510 | - | |
511 | - public void sentRpcResponse(LwM2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) { | |
512 | - rpcRequest.setResponseCode(requestCode); | |
513 | - if (LOG_LW2M_ERROR.equals(typeMsg)) { | |
514 | - rpcRequest.setInfoMsg(null); | |
515 | - rpcRequest.setValueMsg(null); | |
516 | - if (rpcRequest.getErrorMsg() == null) { | |
517 | - msg = msg.isEmpty() ? null : msg; | |
518 | - rpcRequest.setErrorMsg(msg); | |
519 | - } | |
520 | - } else if (LOG_LW2M_INFO.equals(typeMsg)) { | |
521 | - if (rpcRequest.getInfoMsg() == null) { | |
522 | - rpcRequest.setInfoMsg(msg); | |
523 | - } | |
524 | - } else if (LOG_LW2M_VALUE.equals(typeMsg)) { | |
525 | - if (rpcRequest.getValueMsg() == null) { | |
526 | - rpcRequest.setValueMsg(msg); | |
527 | - } | |
528 | - } | |
529 | - this.onToDeviceRpcResponse(rpcRequest.getDeviceRpcResponseResultMsg(), rpcRequest.getSessionInfo()); | |
530 | - } | |
531 | - | |
532 | - @Override | |
533 | - public void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceResponse, SessionInfoProto sessionInfo) { | |
534 | - log.warn("5) onToDeviceRpcResponse: [{}], sessionUUID: [{}]", toDeviceResponse, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); | |
535 | - transportService.process(sessionInfo, toDeviceResponse, null); | |
536 | - } | |
537 | - | |
538 | - public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { | |
539 | - log.info("[{}] toServerRpcResponse", toServerResponse); | |
540 | - } | |
541 | - | |
542 | - /** | |
543 | - * Deregister session in transport | |
544 | - * | |
545 | - * @param sessionInfo - lwm2m client | |
546 | - */ | |
547 | - @Override | |
548 | - public void doDisconnect(SessionInfoProto sessionInfo) { | |
549 | - transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); | |
550 | - transportService.deregisterSession(sessionInfo); | |
551 | - } | |
552 | - | |
553 | - /** | |
554 | - * Session device in thingsboard is closed | |
555 | - * | |
556 | - * @param sessionInfo - lwm2m client | |
557 | - */ | |
558 | - private void doCloseSession(SessionInfoProto sessionInfo) { | |
559 | - TransportProtos.SessionEvent event = SessionEvent.CLOSED; | |
560 | - TransportProtos.SessionEventMsg msg = TransportProtos.SessionEventMsg.newBuilder() | |
561 | - .setSessionType(TransportProtos.SessionType.ASYNC) | |
562 | - .setEvent(event).build(); | |
563 | - transportService.process(sessionInfo, msg, null); | |
564 | - } | |
565 | - | |
566 | - /** | |
567 | - * Those methods are called by the protocol stage thread pool, this means that execution MUST be done in a short delay, | |
568 | - * * if you need to do long time processing use a dedicated thread pool. | |
569 | - * | |
570 | - * @param registration - | |
571 | - */ | |
572 | - @Override | |
573 | - public void onAwakeDev(Registration registration) { | |
574 | - log.trace("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); | |
575 | - this.sendLogsToThingsboard(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LW2M_INFO + ": Client is awake!"); | |
576 | - //TODO: associate endpointId with device information. | |
577 | - } | |
578 | - | |
579 | - /** | |
580 | - * @param logMsg - text msg | |
581 | - * @param registrationId - Id of Registration LwM2M Client | |
582 | - */ | |
583 | - @Override | |
584 | - public void sendLogsToThingsboard(String registrationId, String logMsg) { | |
585 | - sendLogsToThingsboard(clientContext.getClientByRegistrationId(registrationId), logMsg); | |
586 | - } | |
587 | - | |
588 | - @Override | |
589 | - public void sendLogsToThingsboard(LwM2mClient client, String logMsg) { | |
590 | - if (logMsg != null && client != null && client.getSession() != null) { | |
591 | - if (logMsg.length() > 1024) { | |
592 | - logMsg = logMsg.substring(0, 1024); | |
593 | - } | |
594 | - this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), client.getSession()); | |
595 | - } | |
596 | - } | |
597 | - | |
598 | - /** | |
599 | - * #1 clientOnlyObserveAfterConnect == true | |
600 | - * - Only Observe Request to the client marked as observe from the profile configuration. | |
601 | - * #2. clientOnlyObserveAfterConnect == false | |
602 | - * После регистрации отправляю запрос на read всех ресурсов, которые после регистрации есть у клиента, | |
603 | - * а затем запрос на observe (edited) | |
604 | - * - Read Request to the client after registration to read all resource values for all objects | |
605 | - * - then Observe Request to the client marked as observe from the profile configuration. | |
606 | - * | |
607 | - * @param lwM2MClient - object with All parameters off client | |
608 | - */ | |
609 | - private void initClientTelemetry(LwM2mClient lwM2MClient) { | |
610 | - LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(lwM2MClient.getProfileId()); | |
611 | - Set<String> clientObjects = clientContext.getSupportedIdVerInClient(lwM2MClient); | |
612 | - if (clientObjects != null && clientObjects.size() > 0) { | |
613 | - if (LwM2mTransportUtil.LwM2MClientStrategy.CLIENT_STRATEGY_2.code == lwM2MClientProfile.getClientStrategy()) { | |
614 | - // #2 | |
615 | - lwM2MClient.getPendingReadRequests().addAll(clientObjects); | |
616 | - clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(lwM2MClient, path, READ, | |
617 | - null, this.config.getTimeout(), null)); | |
618 | - } | |
619 | - // #1 | |
620 | - this.initReadAttrTelemetryObserveToClient(lwM2MClient, READ, clientObjects); | |
621 | - this.initReadAttrTelemetryObserveToClient(lwM2MClient, OBSERVE, clientObjects); | |
622 | - this.initReadAttrTelemetryObserveToClient(lwM2MClient, WRITE_ATTRIBUTES, clientObjects); | |
623 | - this.initReadAttrTelemetryObserveToClient(lwM2MClient, DISCOVER, clientObjects); | |
624 | - } | |
625 | - } | |
626 | - | |
627 | - /** | |
628 | - * @param registration - | |
629 | - * @param lwM2mObject - | |
630 | - * @param pathIdVer - | |
631 | - */ | |
632 | - private void updateObjectResourceValue(Registration registration, LwM2mObject lwM2mObject, String pathIdVer) { | |
633 | - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); | |
634 | - lwM2mObject.getInstances().forEach((instanceId, instance) -> { | |
635 | - String pathInstance = pathIds.toString() + "/" + instanceId; | |
636 | - this.updateObjectInstanceResourceValue(registration, instance, pathInstance); | |
637 | - }); | |
638 | - } | |
639 | - | |
640 | - /** | |
641 | - * @param registration - | |
642 | - * @param lwM2mObjectInstance - | |
643 | - * @param pathIdVer - | |
644 | - */ | |
645 | - private void updateObjectInstanceResourceValue(Registration registration, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer) { | |
646 | - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); | |
647 | - lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { | |
648 | - String pathRez = pathIds.toString() + "/" + resourceId; | |
649 | - this.updateResourcesValue(registration, resource, pathRez); | |
650 | - }); | |
651 | - } | |
652 | - | |
653 | - /** | |
654 | - * Sending observe value of resources to thingsboard | |
655 | - * #1 Return old Value Resource from LwM2MClient | |
656 | - * #2 Update new Resources (replace old Resource Value on new Resource Value) | |
657 | - * #3 If fr_update -> UpdateFirmware | |
658 | - * #4 updateAttrTelemetry | |
659 | - * | |
660 | - * @param registration - Registration LwM2M Client | |
661 | - * @param lwM2mResource - LwM2mSingleResource response.getContent() | |
662 | - * @param path - resource | |
663 | - */ | |
664 | - private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { | |
665 | - LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); | |
666 | - if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) { | |
667 | - /** version != null | |
668 | - * set setClient_fw_info... = value | |
669 | - **/ | |
670 | - if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { | |
671 | - lwM2MClient.getFwUpdate().initReadValue(this, this.lwM2mTransportRequest, path); | |
672 | - } | |
673 | - if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { | |
674 | - lwM2MClient.getSwUpdate().initReadValue(this, this.lwM2mTransportRequest, path); | |
675 | - } | |
676 | - | |
677 | - if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path)) || | |
678 | - (convertPathFromObjectIdToIdVer(FW_STATE_ID, registration).equals(path))) { | |
679 | - LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(clientContext); | |
680 | - log.warn("93) path: [{}] value: [{}]", path, lwM2mResource.getValue()); | |
681 | - fwUpdate.updateStateOta(this, lwM2mTransportRequest, registration, path, ((Long) lwM2mResource.getValue()).intValue()); | |
682 | - } | |
683 | - Set<String> paths = new HashSet<>(); | |
684 | - paths.add(path); | |
685 | - this.updateAttrTelemetry(registration, paths); | |
686 | - } else { | |
687 | - log.error("Fail update Resource [{}]", lwM2mResource); | |
688 | - } | |
689 | - } | |
690 | - | |
691 | - | |
692 | - /** | |
693 | - * send Attribute and Telemetry to Thingsboard | |
694 | - * #1 - get AttrName/TelemetryName with value from LwM2MClient: | |
695 | - * -- resourceId == path from LwM2MClientProfile.postAttributeProfile/postTelemetryProfile/postObserveProfile | |
696 | - * -- AttrName/TelemetryName == resourceName from ModelObject.objectModel, value from ModelObject.instance.resource(resourceId) | |
697 | - * #2 - set Attribute/Telemetry | |
698 | - * | |
699 | - * @param registration - Registration LwM2M Client | |
700 | - */ | |
701 | - private void updateAttrTelemetry(Registration registration, Set<String> paths) { | |
702 | - try { | |
703 | - ResultsAddKeyValueProto results = this.getParametersFromProfile(registration, paths); | |
704 | - SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration); | |
705 | - if (results != null && sessionInfo != null) { | |
706 | - if (results.getResultAttributes().size() > 0) { | |
707 | - this.helper.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo); | |
708 | - } | |
709 | - if (results.getResultTelemetries().size() > 0) { | |
710 | - this.helper.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo); | |
711 | - } | |
712 | - } | |
713 | - } catch (Exception e) { | |
714 | - log.error("UpdateAttrTelemetry", e); | |
715 | - } | |
716 | - } | |
717 | - | |
718 | - private void initReadAttrTelemetryObserveToClient(LwM2mClient lwM2MClient, LwM2mTypeOper typeOper, Set<String> clientObjects) { | |
719 | - LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(lwM2MClient.getProfileId()); | |
720 | - Set<String> result = null; | |
721 | - ConcurrentHashMap<String, Object> params = null; | |
722 | - if (READ.equals(typeOper)) { | |
723 | - result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(), | |
724 | - new TypeReference<>() { | |
725 | - }); | |
726 | - result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(), | |
727 | - new TypeReference<>() { | |
728 | - })); | |
729 | - } else if (OBSERVE.equals(typeOper)) { | |
730 | - result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(), | |
731 | - new TypeReference<>() { | |
732 | - }); | |
733 | - } else if (DISCOVER.equals(typeOper)) { | |
734 | - result = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()).keySet(); | |
735 | - } else if (WRITE_ATTRIBUTES.equals(typeOper)) { | |
736 | - params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()); | |
737 | - result = params.keySet(); | |
738 | - } | |
739 | - sendRequestsToClient(lwM2MClient, typeOper, clientObjects, result, params); | |
740 | - } | |
741 | - | |
742 | - private void sendRequestsToClient(LwM2mClient lwM2MClient, LwM2mTypeOper operationType, Set<String> supportedObjectIds, Set<String> desiredObjectIds, ConcurrentHashMap<String, Object> params) { | |
743 | - if (desiredObjectIds != null && !desiredObjectIds.isEmpty()) { | |
744 | - Set<String> targetObjectIds = desiredObjectIds.stream().filter(target -> isSupportedTargetId(supportedObjectIds, target) | |
745 | - ).collect(Collectors.toUnmodifiableSet()); | |
746 | - if (!targetObjectIds.isEmpty()) { | |
747 | - //TODO: remove this side effect? | |
748 | - lwM2MClient.getPendingReadRequests().addAll(targetObjectIds); | |
749 | - targetObjectIds.forEach(target -> { | |
750 | - Object additionalParams = params != null ? params.get(target) : null; | |
751 | - lwM2mTransportRequest.sendAllRequest(lwM2MClient, target, operationType, additionalParams, this.config.getTimeout(), null); | |
752 | - }); | |
753 | - if (OBSERVE.equals(operationType)) { | |
754 | - lwM2MClient.initReadValue(this, null); | |
755 | - } | |
756 | - } | |
757 | - } | |
758 | - } | |
759 | - | |
760 | - private boolean isSupportedTargetId(Set<String> supportedIds, String targetId) { | |
761 | - String[] targetIdParts = targetId.split(LWM2M_SEPARATOR_PATH); | |
762 | - if (targetIdParts.length <= 1) { | |
763 | - return false; | |
764 | - } | |
765 | - String targetIdSearch = targetIdParts[0]; | |
766 | - for (int i = 1; i < targetIdParts.length; i++) { | |
767 | - targetIdSearch += "/" + targetIdParts[i]; | |
768 | - if (supportedIds.contains(targetIdSearch)) { | |
769 | - return true; | |
770 | - } | |
771 | - } | |
772 | - return false; | |
773 | - } | |
774 | - | |
775 | - private ConcurrentHashMap<String, Object> getPathForWriteAttributes(JsonObject objectJson) { | |
776 | - ConcurrentHashMap<String, Object> pathAttributes = new Gson().fromJson(objectJson.toString(), | |
777 | - new TypeToken<ConcurrentHashMap<String, Object>>() { | |
778 | - }.getType()); | |
779 | - return pathAttributes; | |
780 | - } | |
781 | - | |
782 | - private void onDeviceUpdate(LwM2mClient lwM2MClient, Device device, Optional<DeviceProfile> deviceProfileOpt) { | |
783 | - deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceProfileUpdate(Collections.singletonList(lwM2MClient), deviceProfile)); | |
784 | - lwM2MClient.onDeviceUpdate(device, deviceProfileOpt); | |
785 | - } | |
786 | - | |
787 | - /** | |
788 | - * // * @param attributes - new JsonObject | |
789 | - * // * @param telemetry - new JsonObject | |
790 | - * | |
791 | - * @param registration - Registration LwM2M Client | |
792 | - * @param path - | |
793 | - */ | |
794 | - private ResultsAddKeyValueProto getParametersFromProfile(Registration registration, Set<String> path) { | |
795 | - if (path != null && path.size() > 0) { | |
796 | - ResultsAddKeyValueProto results = new ResultsAddKeyValueProto(); | |
797 | - LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(registration); | |
798 | - List<TransportProtos.KeyValueProto> resultAttributes = new ArrayList<>(); | |
799 | - lwM2MClientProfile.getPostAttributeProfile().forEach(pathIdVer -> { | |
800 | - if (path.contains(pathIdVer.getAsString())) { | |
801 | - TransportProtos.KeyValueProto kvAttr = this.getKvToThingsboard(pathIdVer.getAsString(), registration); | |
802 | - if (kvAttr != null) { | |
803 | - resultAttributes.add(kvAttr); | |
804 | - } | |
805 | - } | |
806 | - }); | |
807 | - List<TransportProtos.KeyValueProto> resultTelemetries = new ArrayList<>(); | |
808 | - lwM2MClientProfile.getPostTelemetryProfile().forEach(pathIdVer -> { | |
809 | - if (path.contains(pathIdVer.getAsString())) { | |
810 | - TransportProtos.KeyValueProto kvAttr = this.getKvToThingsboard(pathIdVer.getAsString(), registration); | |
811 | - if (kvAttr != null) { | |
812 | - resultTelemetries.add(kvAttr); | |
813 | - } | |
814 | - } | |
815 | - }); | |
816 | - if (resultAttributes.size() > 0) { | |
817 | - results.setResultAttributes(resultAttributes); | |
818 | - } | |
819 | - if (resultTelemetries.size() > 0) { | |
820 | - results.setResultTelemetries(resultTelemetries); | |
821 | - } | |
822 | - return results; | |
823 | - } | |
824 | - return null; | |
825 | - } | |
826 | - | |
827 | - private TransportProtos.KeyValueProto getKvToThingsboard(String pathIdVer, Registration registration) { | |
828 | - LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint()); | |
829 | - JsonObject names = clientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile(); | |
830 | - if (names != null && names.has(pathIdVer)) { | |
831 | - String resourceName = names.get(pathIdVer).getAsString(); | |
832 | - if (resourceName != null && !resourceName.isEmpty()) { | |
833 | - try { | |
834 | - LwM2mResource resourceValue = lwM2MClient != null ? getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) : null; | |
835 | - if (resourceValue != null) { | |
836 | - ResourceModel.Type currentType = resourceValue.getType(); | |
837 | - ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); | |
838 | - Object valueKvProto = null; | |
839 | - if (resourceValue.isMultiInstances()) { | |
840 | - valueKvProto = new JsonObject(); | |
841 | - Object finalvalueKvProto = valueKvProto; | |
842 | - Gson gson = new GsonBuilder().create(); | |
843 | - ResourceModel.Type finalCurrentType = currentType; | |
844 | - resourceValue.getInstances().forEach((k, v) -> { | |
845 | - Object val = this.converter.convertValue(v, finalCurrentType, expectedType, | |
846 | - new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); | |
847 | - JsonElement element = gson.toJsonTree(val, val.getClass()); | |
848 | - ((JsonObject) finalvalueKvProto).add(String.valueOf(k), element); | |
849 | - }); | |
850 | - valueKvProto = gson.toJson(valueKvProto); | |
851 | - } else { | |
852 | - valueKvProto = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, | |
853 | - new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); | |
854 | - } | |
855 | - LwM2mOtaConvert lwM2mOtaConvert = convertOtaUpdateValueToString(pathIdVer, valueKvProto, currentType); | |
856 | - valueKvProto = lwM2mOtaConvert.getValue(); | |
857 | - currentType = lwM2mOtaConvert.getCurrentType(); | |
858 | - return valueKvProto != null ? this.helper.getKvAttrTelemetryToThingsboard(currentType, resourceName, valueKvProto, resourceValue.isMultiInstances()) : null; | |
859 | - } | |
860 | - } catch (Exception e) { | |
861 | - log.error("Failed to add parameters.", e); | |
862 | - } | |
863 | - } | |
864 | - } else { | |
865 | - log.error("Failed to add parameters. path: [{}], names: [{}]", pathIdVer, names); | |
866 | - } | |
867 | - return null; | |
868 | - } | |
869 | - | |
870 | - /** | |
871 | - * @param pathIdVer - path resource | |
872 | - * @return - value of Resource into format KvProto or null | |
873 | - */ | |
874 | - private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) { | |
875 | - LwM2mResource resourceValue = this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); | |
876 | - if (resourceValue != null) { | |
877 | - ResourceModel.Type currentType = resourceValue.getType(); | |
878 | - ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); | |
879 | - return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, | |
880 | - new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); | |
881 | - } else { | |
882 | - return null; | |
883 | - } | |
884 | - } | |
885 | - | |
886 | - /** | |
887 | - * @param lwM2MClient - | |
888 | - * @param path - | |
889 | - * @return - return value of Resource by idPath | |
890 | - */ | |
891 | - private LwM2mResource getResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) { | |
892 | - LwM2mResource lwm2mResourceValue = null; | |
893 | - ResourceValue resourceValue = lwM2MClient.getResources().get(path); | |
894 | - if (resourceValue != null) { | |
895 | - if (new LwM2mPath(convertPathFromIdVerToObjectId(path)).isResource()) { | |
896 | - lwm2mResourceValue = lwM2MClient.getResources().get(path).getLwM2mResource(); | |
897 | - } | |
898 | - } | |
899 | - return lwm2mResourceValue; | |
900 | - } | |
901 | - | |
902 | - /** | |
903 | - * Update resource (attribute) value on thingsboard after update value in client | |
904 | - * | |
905 | - * @param registration - | |
906 | - * @param path - | |
907 | - * @param request - | |
908 | - */ | |
909 | - public void onWriteResponseOk(Registration registration, String path, WriteRequest request) { | |
910 | - if (request.getNode() instanceof LwM2mResource) { | |
911 | - this.updateResourcesValue(registration, ((LwM2mResource) request.getNode()), path); | |
912 | - } else if (request.getNode() instanceof LwM2mObjectInstance) { | |
913 | - ((LwM2mObjectInstance) request.getNode()).getResources().forEach((resId, resource) -> { | |
914 | - this.updateResourcesValue(registration, resource, path + "/" + resId); | |
915 | - }); | |
916 | - } | |
917 | - | |
918 | - } | |
919 | - | |
920 | - /** | |
921 | - * #1 Read new, old Value (Attribute, Telemetry, Observe, KeyName) | |
922 | - * #2 Update in lwM2MClient: ...Profile if changes from update device | |
923 | - * #3 Equivalence test: old <> new Value (Attribute, Telemetry, Observe, KeyName) | |
924 | - * #3.1 Attribute isChange (add&del) | |
925 | - * #3.2 Telemetry isChange (add&del) | |
926 | - * #3.3 KeyName isChange (add) | |
927 | - * #3.4 attributeLwm2m isChange (update WrightAttribute: add/update/del) | |
928 | - * #4 update | |
929 | - * #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and send Value to thingsboard | |
930 | - * #4.2 del | |
931 | - * -- if add attributes includes del telemetry - result del for observe | |
932 | - * #5 | |
933 | - * #5.1 Observe isChange (add&del) | |
934 | - * #5.2 Observe.add | |
935 | - * -- path Attr/Telemetry includes newObserve and does not include oldObserve: send Request observe to Client | |
936 | - * #5.3 Observe.del | |
937 | - * -- different between newObserve and oldObserve: send Request cancel observe to client | |
938 | - * #6 | |
939 | - * #6.1 - update WriteAttribute | |
940 | - * #6.2 - del WriteAttribute | |
941 | - * | |
942 | - * @param clients - | |
943 | - * @param deviceProfile - | |
944 | - */ | |
945 | - private void onDeviceProfileUpdate(List<LwM2mClient> clients, DeviceProfile deviceProfile) { | |
946 | - LwM2mClientProfile lwM2MClientProfileOld = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone(); | |
947 | - if (clientContext.profileUpdate(deviceProfile) != null) { | |
948 | - // #1 | |
949 | - JsonArray attributeOld = lwM2MClientProfileOld.getPostAttributeProfile(); | |
950 | - Set<String> attributeSetOld = convertJsonArrayToSet(attributeOld); | |
951 | - JsonArray telemetryOld = lwM2MClientProfileOld.getPostTelemetryProfile(); | |
952 | - Set<String> telemetrySetOld = convertJsonArrayToSet(telemetryOld); | |
953 | - JsonArray observeOld = lwM2MClientProfileOld.getPostObserveProfile(); | |
954 | - JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile(); | |
955 | - JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile(); | |
956 | - | |
957 | - LwM2mClientProfile lwM2MClientProfileNew = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone(); | |
958 | - JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile(); | |
959 | - Set<String> attributeSetNew = convertJsonArrayToSet(attributeNew); | |
960 | - JsonArray telemetryNew = lwM2MClientProfileNew.getPostTelemetryProfile(); | |
961 | - Set<String> telemetrySetNew = convertJsonArrayToSet(telemetryNew); | |
962 | - JsonArray observeNew = lwM2MClientProfileNew.getPostObserveProfile(); | |
963 | - JsonObject keyNameNew = lwM2MClientProfileNew.getPostKeyNameProfile(); | |
964 | - JsonObject attributeLwm2mNew = lwM2MClientProfileNew.getPostAttributeLwm2mProfile(); | |
965 | - | |
966 | - // #3 | |
967 | - ResultsAnalyzerParameters sendAttrToThingsboard = new ResultsAnalyzerParameters(); | |
968 | - // #3.1 | |
969 | - if (!attributeOld.equals(attributeNew)) { | |
970 | - ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, | |
971 | - new TypeToken<Set<String>>() { | |
972 | - }.getType()), attributeSetNew); | |
973 | - sendAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); | |
974 | - sendAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); | |
975 | - } | |
976 | - // #3.2 | |
977 | - if (!telemetryOld.equals(telemetryNew)) { | |
978 | - ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, | |
979 | - new TypeToken<Set<String>>() { | |
980 | - }.getType()), telemetrySetNew); | |
981 | - sendAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); | |
982 | - sendAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); | |
983 | - } | |
984 | - // #3.3 | |
985 | - if (!keyNameOld.equals(keyNameNew)) { | |
986 | - ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), | |
987 | - new TypeToken<ConcurrentHashMap<String, String>>() { | |
988 | - }.getType()), | |
989 | - new Gson().fromJson(keyNameNew.toString(), new TypeToken<ConcurrentHashMap<String, String>>() { | |
990 | - }.getType())); | |
991 | - sendAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); | |
992 | - } | |
993 | - | |
994 | - // #3.4, #6 | |
995 | - if (!attributeLwm2mOld.equals(attributeLwm2mNew)) { | |
996 | - this.getAnalyzerAttributeLwm2m(clients, attributeLwm2mOld, attributeLwm2mNew); | |
997 | - } | |
998 | - | |
999 | - // #4.1 add | |
1000 | - if (sendAttrToThingsboard.getPathPostParametersAdd().size() > 0) { | |
1001 | - // update value in Resources | |
1002 | - clients.forEach(client -> { | |
1003 | - this.readObserveFromProfile(client, sendAttrToThingsboard.getPathPostParametersAdd(), READ); | |
1004 | - }); | |
1005 | - } | |
1006 | - // #4.2 del | |
1007 | - if (sendAttrToThingsboard.getPathPostParametersDel().size() > 0) { | |
1008 | - ResultsAnalyzerParameters sendAttrToThingsboardDel = this.getAnalyzerParameters(sendAttrToThingsboard.getPathPostParametersAdd(), sendAttrToThingsboard.getPathPostParametersDel()); | |
1009 | - sendAttrToThingsboard.setPathPostParametersDel(sendAttrToThingsboardDel.getPathPostParametersDel()); | |
1010 | - } | |
1011 | - | |
1012 | - // #5.1 | |
1013 | - if (!observeOld.equals(observeNew)) { | |
1014 | - Set<String> observeSetOld = new Gson().fromJson(observeOld, new TypeToken<Set<String>>() { | |
1015 | - }.getType()); | |
1016 | - Set<String> observeSetNew = new Gson().fromJson(observeNew, new TypeToken<Set<String>>() { | |
1017 | - }.getType()); | |
1018 | - //#5.2 add | |
1019 | - // path Attr/Telemetry includes newObserve | |
1020 | - attributeSetOld.addAll(telemetrySetOld); | |
1021 | - ResultsAnalyzerParameters sendObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeSetOld); // add observe | |
1022 | - attributeSetNew.addAll(telemetrySetNew); | |
1023 | - ResultsAnalyzerParameters sendObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeSetNew); // add observe | |
1024 | - // does not include oldObserve | |
1025 | - ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sendObserveToClientOld.getPathPostParametersAdd(), sendObserveToClientNew.getPathPostParametersAdd()); | |
1026 | - // send Request observe to Client | |
1027 | - clients.forEach(client -> { | |
1028 | - Registration registration = client.getRegistration(); | |
1029 | - if (postObserveAnalyzer.getPathPostParametersAdd().size() > 0) { | |
1030 | - this.readObserveFromProfile(client, postObserveAnalyzer.getPathPostParametersAdd(), OBSERVE); | |
1031 | - } | |
1032 | - // 5.3 del | |
1033 | - // send Request cancel observe to Client | |
1034 | - if (postObserveAnalyzer.getPathPostParametersDel().size() > 0) { | |
1035 | - this.cancelObserveFromProfile(client, postObserveAnalyzer.getPathPostParametersDel()); | |
1036 | - } | |
1037 | - }); | |
1038 | - } | |
1039 | - } | |
1040 | - } | |
1041 | - | |
1042 | - /** | |
1043 | - * Compare old list with new list after change AttrTelemetryObserve in config Profile | |
1044 | - * | |
1045 | - * @param parametersOld - | |
1046 | - * @param parametersNew - | |
1047 | - * @return ResultsAnalyzerParameters: add && new | |
1048 | - */ | |
1049 | - private ResultsAnalyzerParameters getAnalyzerParameters(Set<String> parametersOld, Set<String> parametersNew) { | |
1050 | - ResultsAnalyzerParameters analyzerParameters = null; | |
1051 | - if (!parametersOld.equals(parametersNew)) { | |
1052 | - analyzerParameters = new ResultsAnalyzerParameters(); | |
1053 | - analyzerParameters.setPathPostParametersAdd(parametersNew | |
1054 | - .stream().filter(p -> !parametersOld.contains(p)).collect(Collectors.toSet())); | |
1055 | - analyzerParameters.setPathPostParametersDel(parametersOld | |
1056 | - .stream().filter(p -> !parametersNew.contains(p)).collect(Collectors.toSet())); | |
1057 | - } | |
1058 | - return analyzerParameters; | |
1059 | - } | |
1060 | - | |
1061 | - private ResultsAnalyzerParameters getAnalyzerParametersIn(Set<String> parametersObserve, Set<String> parameters) { | |
1062 | - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); | |
1063 | - analyzerParameters.setPathPostParametersAdd(parametersObserve | |
1064 | - .stream().filter(parameters::contains).collect(Collectors.toSet())); | |
1065 | - return analyzerParameters; | |
1066 | - } | |
1067 | - | |
1068 | - /** | |
1069 | - * Update Resource value after change RezAttrTelemetry in config Profile | |
1070 | - * send response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue() | |
1071 | - * | |
1072 | - * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] | |
1073 | - */ | |
1074 | - private void readObserveFromProfile(LwM2mClient client, Set<String> targets, LwM2mTypeOper typeOper) { | |
1075 | - targets.forEach(target -> { | |
1076 | - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(target)); | |
1077 | - if (pathIds.isResource()) { | |
1078 | - if (READ.equals(typeOper)) { | |
1079 | - lwM2mTransportRequest.sendAllRequest(client, target, typeOper, | |
1080 | - null, this.config.getTimeout(), null); | |
1081 | - } else if (OBSERVE.equals(typeOper)) { | |
1082 | - lwM2mTransportRequest.sendAllRequest(client, target, typeOper, | |
1083 | - null, this.config.getTimeout(), null); | |
1084 | - } | |
1085 | - } | |
1086 | - }); | |
1087 | - } | |
1088 | - | |
1089 | - private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentHashMap<String, String> keyNameOld, ConcurrentHashMap<String, String> keyNameNew) { | |
1090 | - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); | |
1091 | - Set<String> paths = keyNameNew.entrySet() | |
1092 | - .stream() | |
1093 | - .filter(e -> !e.getValue().equals(keyNameOld.get(e.getKey()))) | |
1094 | - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).keySet(); | |
1095 | - analyzerParameters.setPathPostParametersAdd(paths); | |
1096 | - return analyzerParameters; | |
1097 | - } | |
1098 | - | |
1099 | - /** | |
1100 | - * #3.4, #6 | |
1101 | - * #6 | |
1102 | - * #6.1 - send update WriteAttribute | |
1103 | - * #6.2 - send empty WriteAttribute | |
1104 | - * | |
1105 | - * @param attributeLwm2mOld - | |
1106 | - * @param attributeLwm2mNew - | |
1107 | - * @return | |
1108 | - */ | |
1109 | - private void getAnalyzerAttributeLwm2m(List<LwM2mClient> clients, JsonObject attributeLwm2mOld, JsonObject attributeLwm2mNew) { | |
1110 | - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); | |
1111 | - ConcurrentHashMap<String, Object> lwm2mAttributesOld = new Gson().fromJson(attributeLwm2mOld.toString(), | |
1112 | - new TypeToken<ConcurrentHashMap<String, Object>>() { | |
1113 | - }.getType()); | |
1114 | - ConcurrentHashMap<String, Object> lwm2mAttributesNew = new Gson().fromJson(attributeLwm2mNew.toString(), | |
1115 | - new TypeToken<ConcurrentHashMap<String, Object>>() { | |
1116 | - }.getType()); | |
1117 | - Set<String> pathOld = lwm2mAttributesOld.keySet(); | |
1118 | - Set<String> pathNew = lwm2mAttributesNew.keySet(); | |
1119 | - analyzerParameters.setPathPostParametersAdd(pathNew | |
1120 | - .stream().filter(p -> !pathOld.contains(p)).collect(Collectors.toSet())); | |
1121 | - analyzerParameters.setPathPostParametersDel(pathOld | |
1122 | - .stream().filter(p -> !pathNew.contains(p)).collect(Collectors.toSet())); | |
1123 | - Set<String> pathCommon = pathNew | |
1124 | - .stream().filter(p -> pathOld.contains(p)).collect(Collectors.toSet()); | |
1125 | - Set<String> pathCommonChange = pathCommon | |
1126 | - .stream().filter(p -> !lwm2mAttributesOld.get(p).equals(lwm2mAttributesNew.get(p))).collect(Collectors.toSet()); | |
1127 | - analyzerParameters.getPathPostParametersAdd().addAll(pathCommonChange); | |
1128 | - // #6 | |
1129 | - // #6.2 | |
1130 | - if (analyzerParameters.getPathPostParametersAdd().size() > 0) { | |
1131 | - clients.forEach(client -> { | |
1132 | - Set<String> clientObjects = clientContext.getSupportedIdVerInClient(client); | |
1133 | - Set<String> pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) | |
1134 | - .collect(Collectors.toUnmodifiableSet()); | |
1135 | - if (!pathSend.isEmpty()) { | |
1136 | - ConcurrentHashMap<String, Object> finalParams = lwm2mAttributesNew; | |
1137 | - pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(client, target, WRITE_ATTRIBUTES, | |
1138 | - finalParams.get(target), this.config.getTimeout(), null)); | |
1139 | - } | |
1140 | - }); | |
1141 | - } | |
1142 | - // #6.2 | |
1143 | - if (analyzerParameters.getPathPostParametersDel().size() > 0) { | |
1144 | - clients.forEach(client -> { | |
1145 | - Registration registration = client.getRegistration(); | |
1146 | - Set<String> clientObjects = clientContext.getSupportedIdVerInClient(client); | |
1147 | - Set<String> pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) | |
1148 | - .collect(Collectors.toUnmodifiableSet()); | |
1149 | - if (!pathSend.isEmpty()) { | |
1150 | - pathSend.forEach(target -> { | |
1151 | - Map<String, Object> params = (Map<String, Object>) lwm2mAttributesOld.get(target); | |
1152 | - params.clear(); | |
1153 | - params.put(OBJECT_VERSION, ""); | |
1154 | - lwM2mTransportRequest.sendAllRequest(client, target, WRITE_ATTRIBUTES, params, this.config.getTimeout(), null); | |
1155 | - }); | |
1156 | - } | |
1157 | - }); | |
1158 | - } | |
1159 | - | |
1160 | - } | |
1161 | - | |
1162 | - private void cancelObserveFromProfile(LwM2mClient lwM2mClient, Set<String> paramAnallyzer) { | |
1163 | - paramAnallyzer.forEach(pathIdVer -> { | |
1164 | - if (this.getResourceValueFromLwM2MClient(lwM2mClient, pathIdVer) != null) { | |
1165 | - lwM2mTransportRequest.sendAllRequest(lwM2mClient, pathIdVer, OBSERVE_CANCEL, null, this.config.getTimeout(), null); | |
1166 | - } | |
1167 | - } | |
1168 | - ); | |
1169 | - } | |
1170 | - | |
1171 | - private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object valueNew, String path) { | |
1172 | - if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) { | |
1173 | - lwM2mTransportRequest.sendAllRequest(lwM2MClient, path, WRITE_REPLACE, valueNew, this.config.getTimeout(), null); | |
1174 | - } else { | |
1175 | - log.error("Failed update resource [{}] [{}]", path, valueNew); | |
1176 | - String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad", | |
1177 | - LOG_LW2M_ERROR, path, valueNew); | |
1178 | - this.sendLogsToThingsboard(lwM2MClient, logMsg); | |
1179 | - log.info("Failed update resource [{}] [{}]", path, valueNew); | |
1180 | - } | |
1181 | - } | |
1182 | - | |
1183 | - /** | |
1184 | - * @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...) | |
1185 | - * config attr/telemetry... in profile | |
1186 | - */ | |
1187 | - public void onToTransportUpdateCredentials(TransportProtos.ToTransportUpdateCredentialsProto updateCredentials) { | |
1188 | - log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList()); | |
1189 | - } | |
1190 | - | |
1191 | - /** | |
1192 | - * Get path to resource from profile equal keyName | |
1193 | - * | |
1194 | - * @param sessionInfo - | |
1195 | - * @param name - | |
1196 | - * @return - | |
1197 | - */ | |
1198 | - public String getPresentPathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { | |
1199 | - LwM2mClientProfile profile = clientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); | |
1200 | - LwM2mClient lwM2mClient = clientContext.getClientBySessionInfo(sessionInfo); | |
1201 | - return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() | |
1202 | - .filter(e -> e.getValue().getAsString().equals(name) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().map(Map.Entry::getKey) | |
1203 | - .orElse(null); | |
1204 | - } | |
1205 | - | |
1206 | - /** | |
1207 | - * 1. FirmwareUpdate: | |
1208 | - * - msg.getSharedUpdatedList().forEach(tsKvProto -> {tsKvProto.getKv().getKey().indexOf(FIRMWARE_UPDATE_PREFIX, 0) == 0 | |
1209 | - * 2. Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values | |
1210 | - * - Get path resource by result attributesResponse | |
1211 | - * | |
1212 | - * @param attributesResponse - | |
1213 | - * @param sessionInfo - | |
1214 | - */ | |
1215 | - public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) { | |
1216 | - try { | |
1217 | - List<TransportProtos.TsKvProto> tsKvProtos = attributesResponse.getSharedAttributeListList(); | |
1218 | - this.updateAttributeFromThingsboard(tsKvProtos, sessionInfo); | |
1219 | - } catch (Exception e) { | |
1220 | - log.error("", e); | |
1221 | - } | |
1222 | - } | |
1223 | - | |
1224 | - /** | |
1225 | - * #1.1 If two names have equal path => last time attribute | |
1226 | - * #2.1 if there is a difference in values between the current resource values and the shared attribute values | |
1227 | - * => send to client Request Update of value (new value from shared attribute) | |
1228 | - * and LwM2MClient.delayedRequests.add(path) | |
1229 | - * #2.1 if there is not a difference in values between the current resource values and the shared attribute values | |
1230 | - * | |
1231 | - * @param tsKvProtos | |
1232 | - * @param sessionInfo | |
1233 | - */ | |
1234 | - public void updateAttributeFromThingsboard(List<TransportProtos.TsKvProto> tsKvProtos, TransportProtos.SessionInfoProto sessionInfo) { | |
1235 | - LwM2mClient lwM2MClient = clientContext.getClientBySessionInfo(sessionInfo); | |
1236 | - if (lwM2MClient != null) { | |
1237 | - log.warn("1) UpdateAttributeFromThingsboard, tsKvProtos [{}]", tsKvProtos); | |
1238 | - tsKvProtos.forEach(tsKvProto -> { | |
1239 | - String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, tsKvProto.getKv().getKey()); | |
1240 | - if (pathIdVer != null) { | |
1241 | - // #1.1 | |
1242 | - if (lwM2MClient.getDelayedRequests().containsKey(pathIdVer) && tsKvProto.getTs() > lwM2MClient.getDelayedRequests().get(pathIdVer).getTs()) { | |
1243 | - lwM2MClient.getDelayedRequests().put(pathIdVer, tsKvProto); | |
1244 | - } else if (!lwM2MClient.getDelayedRequests().containsKey(pathIdVer)) { | |
1245 | - lwM2MClient.getDelayedRequests().put(pathIdVer, tsKvProto); | |
1246 | - } | |
1247 | - } | |
1248 | - }); | |
1249 | - // #2.1 | |
1250 | - lwM2MClient.getDelayedRequests().forEach((pathIdVer, tsKvProto) -> { | |
1251 | - this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), | |
1252 | - getValueFromKvProto(tsKvProto.getKv()), pathIdVer); | |
1253 | - }); | |
1254 | - } else { | |
1255 | - log.error("UpdateAttributeFromThingsboard, lwM2MClient is null"); | |
1256 | - } | |
1257 | - } | |
1258 | - | |
1259 | - /** | |
1260 | - * @param lwM2MClient - | |
1261 | - * @return SessionInfoProto - | |
1262 | - */ | |
1263 | - private SessionInfoProto getSessionInfo(LwM2mClient lwM2MClient) { | |
1264 | - if (lwM2MClient != null && lwM2MClient.getSession() != null) { | |
1265 | - return lwM2MClient.getSession(); | |
1266 | - } | |
1267 | - return null; | |
1268 | - } | |
1269 | - | |
1270 | - /** | |
1271 | - * @param registration - Registration LwM2M Client | |
1272 | - * @return - sessionInfo after access connect client | |
1273 | - */ | |
1274 | - public SessionInfoProto getSessionInfoOrCloseSession(Registration registration) { | |
1275 | - return getSessionInfo(clientContext.getClientByEndpoint(registration.getEndpoint())); | |
1276 | - } | |
1277 | - | |
1278 | - /** | |
1279 | - * if sessionInfo removed from sessions, then new registerAsyncSession | |
1280 | - * | |
1281 | - * @param sessionInfo - | |
1282 | - */ | |
1283 | - private void reportActivityAndRegister(SessionInfoProto sessionInfo) { | |
1284 | - if (sessionInfo != null && transportService.reportActivity(sessionInfo) == null) { | |
1285 | - transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo, transportService)); | |
1286 | - this.reportActivitySubscription(sessionInfo); | |
1287 | - } | |
1288 | - } | |
1289 | - | |
1290 | - private void reportActivity() { | |
1291 | - clientContext.getLwM2mClients().forEach(client -> reportActivityAndRegister(client.getSession())); | |
1292 | - } | |
1293 | - | |
1294 | - /** | |
1295 | - * #1. !!! sharedAttr === profileAttr !!! | |
1296 | - * - If there is a difference in values between the current resource values and the shared attribute values | |
1297 | - * - when the client connects to the server | |
1298 | - * #1.1 get attributes name from profile include name resources in ModelObject if resource isWritable | |
1299 | - * #1.2 #1 size > 0 => send Request getAttributes to thingsboard | |
1300 | - * #2. FirmwareAttribute subscribe: | |
1301 | - * | |
1302 | - * @param lwM2MClient - LwM2M Client | |
1303 | - */ | |
1304 | - public void putDelayedUpdateResourcesThingsboard(LwM2mClient lwM2MClient) { | |
1305 | - SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); | |
1306 | - if (sessionInfo != null) { | |
1307 | - //#1.1 | |
1308 | - ConcurrentMap<String, String> keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient); | |
1309 | - if (keyNamesMap.values().size() > 0) { | |
1310 | - try { | |
1311 | - //#1.2 | |
1312 | - TransportProtos.GetAttributeRequestMsg getAttributeMsg = adaptor.convertToGetAttributes(null, keyNamesMap.values()); | |
1313 | - transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); | |
1314 | - } catch (AdaptorException e) { | |
1315 | - log.trace("Failed to decode get attributes request", e); | |
1316 | - } | |
1317 | - } | |
1318 | - | |
1319 | - } | |
1320 | - } | |
1321 | - | |
1322 | - public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient, LwM2mClientRpcRequest rpcRequest) { | |
1323 | - if (lwM2MClient.getRegistration().getSupportedVersion(FW_5_ID) != null) { | |
1324 | - SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); | |
1325 | - if (sessionInfo != null) { | |
1326 | - DefaultLwM2MTransportMsgHandler handler = this; | |
1327 | - this.transportService.process(sessionInfo, createOtaPackageRequestMsg(sessionInfo, OtaPackageType.FIRMWARE.name()), | |
1328 | - new TransportServiceCallback<>() { | |
1329 | - @Override | |
1330 | - public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { | |
1331 | - if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus()) | |
1332 | - && response.getType().equals(OtaPackageType.FIRMWARE.name())) { | |
1333 | - LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(clientContext); | |
1334 | - if (rpcRequest != null) { | |
1335 | - fwUpdate.setStateUpdate(INITIATED.name()); | |
1336 | - } | |
1337 | - if (!FAILED.name().equals(fwUpdate.getStateUpdate())) { | |
1338 | - log.warn("7) firmware start with ver: [{}]", response.getVersion()); | |
1339 | - fwUpdate.setRpcRequest(rpcRequest); | |
1340 | - fwUpdate.setCurrentVersion(response.getVersion()); | |
1341 | - fwUpdate.setCurrentTitle(response.getTitle()); | |
1342 | - fwUpdate.setCurrentId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())); | |
1343 | - if (rpcRequest == null) { | |
1344 | - fwUpdate.sendReadObserveInfo(lwM2mTransportRequest); | |
1345 | - } else { | |
1346 | - fwUpdate.writeFwSwWare(handler, lwM2mTransportRequest); | |
1347 | - } | |
1348 | - } else { | |
1349 | - String msgError = String.format("OtaPackage device: %s, version: %s, stateUpdate: %s", | |
1350 | - lwM2MClient.getDeviceName(), response.getVersion(), fwUpdate.getStateUpdate()); | |
1351 | - log.warn("7_1 [{}]", msgError); | |
1352 | - } | |
1353 | - } else { | |
1354 | - String msgError = String.format("OtaPackage device: %s, responseStatus: %s", | |
1355 | - lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); | |
1356 | - log.trace(msgError); | |
1357 | - if (rpcRequest != null) { | |
1358 | - sendErrorRpcResponse(rpcRequest, msgError, sessionInfo); | |
1359 | - } | |
1360 | - } | |
1361 | - } | |
1362 | - | |
1363 | - @Override | |
1364 | - public void onError(Throwable e) { | |
1365 | - log.trace("Failed to process firmwareUpdate ", e); | |
1366 | - } | |
1367 | - }); | |
1368 | - } | |
1369 | - } | |
1370 | - } | |
1371 | - | |
1372 | - public void getInfoSoftwareUpdate(LwM2mClient lwM2MClient, LwM2mClientRpcRequest rpcRequest) { | |
1373 | - if (lwM2MClient.getRegistration().getSupportedVersion(SW_ID) != null) { | |
1374 | - SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); | |
1375 | - if (sessionInfo != null) { | |
1376 | - DefaultLwM2MTransportMsgHandler handler = this; | |
1377 | - transportService.process(sessionInfo, createOtaPackageRequestMsg(sessionInfo, OtaPackageType.SOFTWARE.name()), | |
1378 | - new TransportServiceCallback<>() { | |
1379 | - @Override | |
1380 | - public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { | |
1381 | - if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus()) | |
1382 | - && response.getType().equals(OtaPackageType.SOFTWARE.name())) { | |
1383 | - lwM2MClient.getSwUpdate().setRpcRequest(rpcRequest); | |
1384 | - lwM2MClient.getSwUpdate().setCurrentVersion(response.getVersion()); | |
1385 | - lwM2MClient.getSwUpdate().setCurrentTitle(response.getTitle()); | |
1386 | - lwM2MClient.getSwUpdate().setCurrentId(new OtaPackageId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())).getId()); | |
1387 | - lwM2MClient.getSwUpdate().sendReadObserveInfo(lwM2mTransportRequest); | |
1388 | - if (rpcRequest == null) { | |
1389 | - lwM2MClient.getSwUpdate().sendReadObserveInfo(lwM2mTransportRequest); | |
1390 | - } else { | |
1391 | - lwM2MClient.getSwUpdate().writeFwSwWare(handler, lwM2mTransportRequest); | |
1392 | - } | |
1393 | - } else { | |
1394 | - log.trace("Software [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); | |
1395 | - } | |
1396 | - } | |
1397 | - | |
1398 | - @Override | |
1399 | - public void onError(Throwable e) { | |
1400 | - log.trace("Failed to process softwareUpdate ", e); | |
1401 | - } | |
1402 | - }); | |
1403 | - } | |
1404 | - } | |
1405 | - } | |
1406 | - | |
1407 | - private TransportProtos.GetOtaPackageRequestMsg createOtaPackageRequestMsg(SessionInfoProto sessionInfo, String nameFwSW) { | |
1408 | - return TransportProtos.GetOtaPackageRequestMsg.newBuilder() | |
1409 | - .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) | |
1410 | - .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) | |
1411 | - .setTenantIdMSB(sessionInfo.getTenantIdMSB()) | |
1412 | - .setTenantIdLSB(sessionInfo.getTenantIdLSB()) | |
1413 | - .setType(nameFwSW) | |
1414 | - .build(); | |
1415 | - } | |
1416 | - | |
1417 | - /** | |
1418 | - * !!! sharedAttr === profileAttr !!! | |
1419 | - * Get names or keyNames from profile: resources IsWritable | |
1420 | - * | |
1421 | - * @param lwM2MClient - | |
1422 | - * @return ArrayList keyNames from profile profileAttr && IsWritable | |
1423 | - */ | |
1424 | - private ConcurrentMap<String, String> getNamesFromProfileForSharedAttributes(LwM2mClient lwM2MClient) { | |
1425 | - | |
1426 | - LwM2mClientProfile profile = clientContext.getProfile(lwM2MClient.getProfileId()); | |
1427 | - return new Gson().fromJson(profile.getPostKeyNameProfile().toString(), | |
1428 | - new TypeToken<ConcurrentHashMap<String, String>>() { | |
1429 | - }.getType()); | |
1430 | - } | |
1431 | - | |
1432 | - private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { | |
1433 | - ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config | |
1434 | - .getModelProvider()); | |
1435 | - Integer objectId = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)).getObjectId(); | |
1436 | - String objectVer = validateObjectVerFromKey(pathIdVer); | |
1437 | - return resourceModel != null && (isWritableNotOptional ? | |
1438 | - objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() : | |
1439 | - objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId))); | |
1440 | - } | |
1441 | - | |
1442 | - public LwM2MTransportServerConfig getConfig() { | |
1443 | - return this.config; | |
1444 | - } | |
1445 | - | |
1446 | - private void reportActivitySubscription(TransportProtos.SessionInfoProto sessionInfo) { | |
1447 | - transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() | |
1448 | - .setAttributeSubscription(true) | |
1449 | - .setRpcSubscription(true) | |
1450 | - .setLastActivityTime(System.currentTimeMillis()) | |
1451 | - .build(), TransportServiceCallback.EMPTY); | |
1452 | - } | |
1453 | -} |
... | ... | @@ -26,8 +26,8 @@ import org.eclipse.leshan.server.californium.LeshanServer; |
26 | 26 | import org.eclipse.leshan.server.californium.LeshanServerBuilder; |
27 | 27 | import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; |
28 | 28 | import org.eclipse.leshan.server.model.LwM2mModelProvider; |
29 | -import org.eclipse.leshan.server.security.EditableSecurityStore; | |
30 | 29 | import org.springframework.stereotype.Component; |
30 | +import org.thingsboard.server.cache.ota.OtaPackageDataCache; | |
31 | 31 | import org.thingsboard.server.common.data.StringUtils; |
32 | 32 | import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; |
33 | 33 | import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
... | ... | @@ -35,8 +35,8 @@ import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; |
35 | 35 | import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; |
36 | 36 | import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier; |
37 | 37 | import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; |
38 | -import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore; | |
39 | 38 | import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore; |
39 | +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; | |
40 | 40 | import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; |
41 | 41 | |
42 | 42 | import javax.annotation.PostConstruct; |
... | ... | @@ -81,7 +81,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { |
81 | 81 | private final LwM2mTransportContext context; |
82 | 82 | private final LwM2MTransportServerConfig config; |
83 | 83 | private final LwM2mTransportServerHelper helper; |
84 | - private final DefaultLwM2MTransportMsgHandler handler; | |
84 | + private final OtaPackageDataCache otaPackageDataCache; | |
85 | + private final DefaultLwM2MUplinkMsgHandler handler; | |
85 | 86 | private final CaliforniumRegistrationStore registrationStore; |
86 | 87 | private final TbSecurityStore securityStore; |
87 | 88 | private final LwM2mClientContext lwM2mClientContext; |
... | ... | @@ -104,8 +105,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { |
104 | 105 | * "coap://host:port/{path}/{token}/{nameFile}" |
105 | 106 | */ |
106 | 107 | |
107 | - | |
108 | - LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(handler, FIRMWARE_UPDATE_COAP_RECOURSE); | |
108 | + LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(otaPackageDataCache, FIRMWARE_UPDATE_COAP_RECOURSE); | |
109 | 109 | this.server.coap().getServer().add(otaCoapResource); |
110 | 110 | this.startLhServer(); |
111 | 111 | this.context.setServer(server); | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.lwm2m.server; | |
17 | + | |
18 | +public enum LwM2MFirmwareUpdateStrategy { | |
19 | + OBJ_5_BINARY(1, "ObjectId 5, Binary"), | |
20 | + OBJ_5_TEMP_URL(2, "ObjectId 5, URI"), | |
21 | + OBJ_19_BINARY(3, "ObjectId 19, Binary"); | |
22 | + | |
23 | + public int code; | |
24 | + public String type; | |
25 | + | |
26 | + LwM2MFirmwareUpdateStrategy(int code, String type) { | |
27 | + this.code = code; | |
28 | + this.type = type; | |
29 | + } | |
30 | + | |
31 | + public static LwM2MFirmwareUpdateStrategy fromStrategyFwByType(String type) { | |
32 | + for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) { | |
33 | + if (to.type.equals(type)) { | |
34 | + return to; | |
35 | + } | |
36 | + } | |
37 | + throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type)); | |
38 | + } | |
39 | + | |
40 | + public static LwM2MFirmwareUpdateStrategy fromStrategyFwByCode(int code) { | |
41 | + for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) { | |
42 | + if (to.code == code) { | |
43 | + return to; | |
44 | + } | |
45 | + } | |
46 | + throw new IllegalArgumentException(String.format("Unsupported FW Strategy code : %s", code)); | |
47 | + } | |
48 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.lwm2m.server; | |
17 | + | |
18 | +public enum LwM2MSoftwareUpdateStrategy { | |
19 | + BINARY(1, "ObjectId 9, Binary"), | |
20 | + TEMP_URL(2, "ObjectId 9, URI"); | |
21 | + | |
22 | + public int code; | |
23 | + public String type; | |
24 | + | |
25 | + LwM2MSoftwareUpdateStrategy(int code, String type) { | |
26 | + this.code = code; | |
27 | + this.type = type; | |
28 | + } | |
29 | + | |
30 | + public static LwM2MSoftwareUpdateStrategy fromStrategySwByType(String type) { | |
31 | + for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) { | |
32 | + if (to.type.equals(type)) { | |
33 | + return to; | |
34 | + } | |
35 | + } | |
36 | + throw new IllegalArgumentException(String.format("Unsupported SW Strategy type : %s", type)); | |
37 | + } | |
38 | + | |
39 | + public static LwM2MSoftwareUpdateStrategy fromStrategySwByCode(int code) { | |
40 | + for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) { | |
41 | + if (to.code == code) { | |
42 | + return to; | |
43 | + } | |
44 | + } | |
45 | + throw new IllegalArgumentException(String.format("Unsupported SW Strategy code : %s", code)); | |
46 | + } | |
47 | + | |
48 | +} | ... | ... |
... | ... | @@ -84,10 +84,10 @@ public class LwM2mNetworkConfig { |
84 | 84 | Create new instance of udp endpoint context matcher. |
85 | 85 | Params: |
86 | 86 | checkAddress |
87 | - – true with address check, (STRICT, UDP) | |
87 | + – true with address check, (STRICT, UDP) - if port Registration of client is changed - it is bad | |
88 | 88 | - false, without |
89 | 89 | */ |
90 | - coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "STRICT"); | |
90 | + coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED"); | |
91 | 91 | /** |
92 | 92 | https://tools.ietf.org/html/rfc7959#section-2.9.3 |
93 | 93 | The block size (number of bytes) to use when doing a blockwise transfer. \ |
... | ... | @@ -103,7 +103,7 @@ public class LwM2mNetworkConfig { |
103 | 103 | */ |
104 | 104 | coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); |
105 | 105 | |
106 | - coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4); | |
106 | + coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 10); | |
107 | 107 | |
108 | 108 | return coapConfig; |
109 | 109 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.lwm2m.server; | |
17 | + | |
18 | +import lombok.Getter; | |
19 | + | |
20 | +/** | |
21 | + * Define the behavior of a write request. | |
22 | + */ | |
23 | +public enum LwM2mOperationType { | |
24 | + | |
25 | + READ(0, "Read", true), | |
26 | + DISCOVER(1, "Discover", true), | |
27 | + DISCOVER_ALL(2, "DiscoverAll", false), | |
28 | + OBSERVE_READ_ALL(3, "ObserveReadAll", false), | |
29 | + | |
30 | + OBSERVE(4, "Observe", true), | |
31 | + OBSERVE_CANCEL(5, "ObserveCancel", true), | |
32 | + OBSERVE_CANCEL_ALL(6, "ObserveCancelAll", false), | |
33 | + EXECUTE(7, "Execute", true), | |
34 | + /** | |
35 | + * Replaces the Object Instance or the Resource(s) with the new value provided in the “Write” operation. (see | |
36 | + * section 5.3.3 of the LW M2M spec). | |
37 | + * if all resources are to be replaced | |
38 | + */ | |
39 | + WRITE_REPLACE(8, "WriteReplace", true), | |
40 | + | |
41 | + /** | |
42 | + * Adds or updates Resources provided in the new value and leaves other existing Resources unchanged. (see section | |
43 | + * 5.3.3 of the LW M2M spec). | |
44 | + * if this is a partial update request | |
45 | + */ | |
46 | + WRITE_UPDATE(9, "WriteUpdate", true), | |
47 | + WRITE_ATTRIBUTES(10, "WriteAttributes", true), | |
48 | + DELETE(11, "Delete", true), | |
49 | + | |
50 | + // only for RPC | |
51 | + FW_UPDATE(12, "FirmwareUpdate", false); | |
52 | + | |
53 | +// FW_READ_INFO(12, "FirmwareReadInfo"), | |
54 | +// SW_READ_INFO(15, "SoftwareReadInfo"), | |
55 | +// SW_UPDATE(16, "SoftwareUpdate"), | |
56 | +// SW_UNINSTALL(18, "SoftwareUninstall"); | |
57 | + | |
58 | + @Getter | |
59 | + private final int code; | |
60 | + @Getter | |
61 | + private final String type; | |
62 | + @Getter | |
63 | + private final boolean hasObjectId; | |
64 | + | |
65 | + LwM2mOperationType(int code, String type, boolean hasObjectId) { | |
66 | + this.code = code; | |
67 | + this.type = type; | |
68 | + this.hasObjectId = hasObjectId; | |
69 | + } | |
70 | + | |
71 | + public static LwM2mOperationType fromType(String type) { | |
72 | + for (LwM2mOperationType to : LwM2mOperationType.values()) { | |
73 | + if (to.type.equals(type)) { | |
74 | + return to; | |
75 | + } | |
76 | + } | |
77 | + return null; | |
78 | + } | |
79 | +} | ... | ... |
... | ... | @@ -23,18 +23,18 @@ import org.eclipse.leshan.server.queue.PresenceListener; |
23 | 23 | import org.eclipse.leshan.server.registration.Registration; |
24 | 24 | import org.eclipse.leshan.server.registration.RegistrationListener; |
25 | 25 | import org.eclipse.leshan.server.registration.RegistrationUpdate; |
26 | +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; | |
26 | 27 | |
27 | 28 | import java.util.Collection; |
28 | 29 | |
29 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; | |
30 | -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; | |
30 | +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; | |
31 | 31 | |
32 | 32 | @Slf4j |
33 | 33 | public class LwM2mServerListener { |
34 | 34 | |
35 | - private final LwM2mTransportMsgHandler service; | |
35 | + private final LwM2mUplinkMsgHandler service; | |
36 | 36 | |
37 | - public LwM2mServerListener(LwM2mTransportMsgHandler service) { | |
37 | + public LwM2mServerListener(LwM2mUplinkMsgHandler service) { | |
38 | 38 | this.service = service; |
39 | 39 | } |
40 | 40 | |
... | ... | @@ -86,30 +86,24 @@ public class LwM2mServerListener { |
86 | 86 | |
87 | 87 | @Override |
88 | 88 | public void cancelled(Observation observation) { |
89 | - String msg = String.format("%s: Canceled Observation %s.", LOG_LW2M_INFO, observation.getPath()); | |
90 | - service.sendLogsToThingsboard(observation.getRegistrationId(), msg); | |
91 | - log.warn(msg); | |
89 | + log.trace("Canceled Observation {}.", observation.getPath()); | |
92 | 90 | } |
93 | 91 | |
94 | 92 | @Override |
95 | 93 | public void onResponse(Observation observation, Registration registration, ObserveResponse response) { |
96 | 94 | if (registration != null) { |
97 | - service.onUpdateValueAfterReadResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), | |
98 | - registration), response, null); | |
95 | + service.onUpdateValueAfterReadResponse(registration, convertObjectIdToVersionedId(observation.getPath().toString(), registration), response); | |
99 | 96 | } |
100 | 97 | } |
101 | 98 | |
102 | 99 | @Override |
103 | 100 | public void onError(Observation observation, Registration registration, Exception error) { |
104 | - log.error(String.format("Unable to handle notification of [%s:%s]", observation.getRegistrationId(), observation.getPath()), error); | |
101 | + log.error("Unable to handle notification of [{}:{}]", observation.getRegistrationId(), observation.getPath(), error); | |
105 | 102 | } |
106 | 103 | |
107 | 104 | @Override |
108 | 105 | public void newObservation(Observation observation, Registration registration) { |
109 | - String msg = String.format("%s: Successful start newObservation %s.", LOG_LW2M_INFO, | |
110 | - observation.getPath()); | |
111 | - log.warn(msg); | |
112 | - service.sendLogsToThingsboard(registration.getId(), msg); | |
106 | + log.trace("Successful start newObservation {}.", observation.getPath()); | |
113 | 107 | } |
114 | 108 | }; |
115 | 109 | } | ... | ... |
... | ... | @@ -34,6 +34,9 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotifica |
34 | 34 | import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; |
35 | 35 | import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; |
36 | 36 | import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; |
37 | +import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService; | |
38 | +import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcRequestHandler; | |
39 | +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; | |
37 | 40 | |
38 | 41 | import java.util.Optional; |
39 | 42 | import java.util.UUID; |
... | ... | @@ -41,19 +44,21 @@ import java.util.UUID; |
41 | 44 | @Slf4j |
42 | 45 | @RequiredArgsConstructor |
43 | 46 | public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? super Void>>, SessionMsgListener { |
44 | - private final DefaultLwM2MTransportMsgHandler handler; | |
47 | + private final LwM2mUplinkMsgHandler handler; | |
48 | + private final LwM2MAttributesService attributesService; | |
49 | + private final LwM2MRpcRequestHandler rpcHandler; | |
45 | 50 | private final TransportProtos.SessionInfoProto sessionInfo; |
46 | 51 | private final TransportService transportService; |
47 | 52 | |
48 | 53 | @Override |
49 | 54 | public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) { |
50 | - this.handler.onGetAttributesResponse(getAttributesResponse, this.sessionInfo); | |
55 | + this.attributesService.onGetAttributesResponse(getAttributesResponse, this.sessionInfo); | |
51 | 56 | } |
52 | 57 | |
53 | 58 | @Override |
54 | 59 | public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { |
55 | - this.handler.onAttributeUpdate(attributeUpdateNotification, this.sessionInfo); | |
56 | - } | |
60 | + this.attributesService.onAttributesUpdate(attributeUpdateNotification, this.sessionInfo); | |
61 | + } | |
57 | 62 | |
58 | 63 | @Override |
59 | 64 | public void onRemoteSessionCloseCommand(UUID sessionId, SessionCloseNotificationProto sessionCloseNotification) { |
... | ... | @@ -77,7 +82,7 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s |
77 | 82 | |
78 | 83 | @Override |
79 | 84 | public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { |
80 | - this.handler.onToDeviceRpcRequest(toDeviceRequest, this.sessionInfo); | |
85 | + this.rpcHandler.onToDeviceRpcRequest(toDeviceRequest, this.sessionInfo); | |
81 | 86 | if (toDeviceRequest.getPersisted()) { |
82 | 87 | RpcStatus status; |
83 | 88 | if (toDeviceRequest.getOneway()) { |
... | ... | @@ -97,7 +102,7 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s |
97 | 102 | |
98 | 103 | @Override |
99 | 104 | public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { |
100 | - this.handler.onToServerRpcResponse(toServerResponse); | |
105 | + this.rpcHandler.onToServerRpcResponse(toServerResponse); | |
101 | 106 | } |
102 | 107 | |
103 | 108 | @Override | ... | ... |
... | ... | @@ -24,6 +24,9 @@ import org.eclipse.californium.core.observe.ObserveRelation; |
24 | 24 | import org.eclipse.californium.core.server.resources.CoapExchange; |
25 | 25 | import org.eclipse.californium.core.server.resources.Resource; |
26 | 26 | import org.eclipse.californium.core.server.resources.ResourceObserver; |
27 | +import org.thingsboard.server.cache.ota.OtaPackageDataCache; | |
28 | +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; | |
29 | +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; | |
27 | 30 | |
28 | 31 | import java.util.UUID; |
29 | 32 | import java.util.concurrent.ConcurrentHashMap; |
... | ... | @@ -37,11 +40,11 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.S |
37 | 40 | public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { |
38 | 41 | private final ConcurrentMap<String, ObserveRelation> tokenToObserveRelationMap = new ConcurrentHashMap<>(); |
39 | 42 | private final ConcurrentMap<String, AtomicInteger> tokenToObserveNotificationSeqMap = new ConcurrentHashMap<>(); |
40 | - private final LwM2mTransportMsgHandler handler; | |
43 | + private final OtaPackageDataCache otaPackageDataCache; | |
41 | 44 | |
42 | - public LwM2mTransportCoapResource(LwM2mTransportMsgHandler handler, String name) { | |
45 | + public LwM2mTransportCoapResource(OtaPackageDataCache otaPackageDataCache, String name) { | |
43 | 46 | super(name); |
44 | - this.handler = handler; | |
47 | + this.otaPackageDataCache = otaPackageDataCache; | |
45 | 48 | this.setObservable(true); // enable observing |
46 | 49 | this.addObserver(new CoapResourceObserver()); |
47 | 50 | } |
... | ... | @@ -140,9 +143,9 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { |
140 | 143 | response.setPayload(fwData); |
141 | 144 | if (exchange.getRequestOptions().getBlock2() != null) { |
142 | 145 | int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); |
143 | - boolean moreFlag = fwData.length > chunkSize; | |
144 | - response.getOptions().setBlock2(chunkSize, moreFlag, 0); | |
145 | - log.warn("92) with blokc2 Send currentId: [{}], length: [{}], chunkSize [{}], moreFlag [{}]", currentId.toString(), fwData.length, chunkSize, moreFlag); | |
146 | + boolean lastFlag = fwData.length > chunkSize; | |
147 | + response.getOptions().setBlock2(chunkSize, lastFlag, 0); | |
148 | + log.warn("92) with blokc2 Send currentId: [{}], length: [{}], chunkSize [{}], moreFlag [{}]", currentId.toString(), fwData.length, chunkSize, lastFlag); | |
146 | 149 | } |
147 | 150 | else { |
148 | 151 | log.warn("92) with block1 Send currentId: [{}], length: [{}], ", currentId.toString(), fwData.length); |
... | ... | @@ -152,7 +155,7 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { |
152 | 155 | } |
153 | 156 | |
154 | 157 | private byte[] getOtaData(UUID currentId) { |
155 | - return ((DefaultLwM2MTransportMsgHandler) handler).otaPackageDataCache.get(currentId.toString()); | |
158 | + return otaPackageDataCache.get(currentId.toString()); | |
156 | 159 | } |
157 | 160 | |
158 | 161 | } | ... | ... |