Commit f1f41851cc4e3995ad391b20a9cd81fd83ab921c

Authored by YevhenBondarenko
2 parents e58d5b2d 1f9b4c09

merge with master

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
... ...
... ... @@ -42,5 +42,5 @@ public interface OAuth2Service {
42 42
43 43 List<OAuth2Registration> findAllRegistrations();
44 44
45   - String findCallbackUrlScheme(UUID registrationId, String pkgName);
  45 + String findAppSecret(UUID registrationId, String pkgName);
46 46 }
... ...
... ... @@ -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 +}
... ...
  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;
... ...
... ... @@ -16,5 +16,5 @@
16 16 package org.thingsboard.server.common.data.oauth2;
17 17
18 18 public enum MapperType {
19   - BASIC, CUSTOM, GITHUB;
  19 + BASIC, CUSTOM, GITHUB, APPLE;
20 20 }
... ...
... ... @@ -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 }
... ...
... ... @@ -30,5 +30,5 @@ import lombok.ToString;
30 30 @Builder
31 31 public class OAuth2MobileInfo {
32 32 private String pkgName;
33   - private String callbackUrlScheme;
  33 + private String appSecret;
34 34 }
... ...
... ... @@ -16,5 +16,5 @@
16 16 package org.thingsboard.server.common.msg.session;
17 17
18 18 public enum FeatureType {
19   - ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION, FIRMWARE, SOFTWARE
  19 + ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION
20 20 }
... ...
... ... @@ -30,10 +30,7 @@ public enum SessionMsgType {
30 30
31 31 SESSION_OPEN, SESSION_CLOSE,
32 32
33   - CLAIM_REQUEST(),
34   -
35   - GET_FIRMWARE_REQUEST,
36   - GET_SOFTWARE_REQUEST;
  33 + CLAIM_REQUEST();
37 34
38 35 private final boolean requiresRulesProcessing;
39 36
... ...
... ... @@ -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
... ...
... ... @@ -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
... ...
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 }
... ...