Commit 854307fd780068e429f601f1853ec1bd0fd1ebcb

Authored by Igor Kulikov
2 parents 69271305 aea24389

Merge branch 'master' of github.com:thingsboard/thingsboard

Showing 86 changed files with 1778 additions and 581 deletions
... ... @@ -197,3 +197,17 @@ $$;
197 197 ALTER TABLE api_usage_state
198 198 ADD COLUMN IF NOT EXISTS alarm_exec VARCHAR(32);
199 199 UPDATE api_usage_state SET alarm_exec = 'ENABLED' WHERE alarm_exec IS NULL;
  200 +
  201 +CREATE TABLE IF NOT EXISTS rpc (
  202 + id uuid NOT NULL CONSTRAINT rpc_pkey PRIMARY KEY,
  203 + created_time bigint NOT NULL,
  204 + tenant_id uuid NOT NULL,
  205 + device_id uuid NOT NULL,
  206 + expiration_time bigint NOT NULL,
  207 + request varchar(10000000) NOT NULL,
  208 + response varchar(10000000),
  209 + status varchar(255) NOT NULL
  210 +);
  211 +
  212 +CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id);
  213 +
... ...
... ... @@ -65,6 +65,7 @@ import org.thingsboard.server.dao.relation.RelationService;
65 65 import org.thingsboard.server.dao.resource.ResourceService;
66 66 import org.thingsboard.server.dao.rule.RuleChainService;
67 67 import org.thingsboard.server.dao.rule.RuleNodeStateService;
  68 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
68 69 import org.thingsboard.server.dao.tenant.TenantProfileService;
69 70 import org.thingsboard.server.dao.tenant.TenantService;
70 71 import org.thingsboard.server.dao.timeseries.TimeseriesService;
... ... @@ -80,9 +81,9 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService;
80 81 import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
81 82 import org.thingsboard.server.service.mail.MailExecutorService;
82 83 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
83   -import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
84 84 import org.thingsboard.server.service.queue.TbClusterService;
85 85 import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
  86 +import org.thingsboard.server.service.rpc.TbRpcService;
86 87 import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
87 88 import org.thingsboard.server.service.script.JsExecutorService;
88 89 import org.thingsboard.server.service.script.JsInvokeService;
... ... @@ -303,23 +304,33 @@ public class ActorSystemContext {
303 304
304 305 @Lazy
305 306 @Autowired(required = false)
306   - @Getter private EdgeService edgeService;
  307 + @Getter
  308 + private EdgeService edgeService;
307 309
308 310 @Lazy
309 311 @Autowired(required = false)
310   - @Getter private EdgeEventService edgeEventService;
  312 + @Getter
  313 + private EdgeEventService edgeEventService;
311 314
312 315 @Lazy
313 316 @Autowired(required = false)
314   - @Getter private EdgeRpcService edgeRpcService;
  317 + @Getter
  318 + private EdgeRpcService edgeRpcService;
315 319
316 320 @Lazy
317 321 @Autowired(required = false)
318   - @Getter private ResourceService resourceService;
  322 + @Getter
  323 + private ResourceService resourceService;
319 324
320 325 @Lazy
321 326 @Autowired(required = false)
322   - @Getter private OtaPackageService otaPackageService;
  327 + @Getter
  328 + private OtaPackageService otaPackageService;
  329 +
  330 + @Lazy
  331 + @Autowired(required = false)
  332 + @Getter
  333 + private TbRpcService tbRpcService;
323 334
324 335 @Value("${actors.session.max_concurrent_sessions_per_device:1}")
325 336 @Getter
... ...
... ... @@ -46,7 +46,7 @@ public class DeviceActor extends ContextAwareActor {
46 46 super.init(ctx);
47 47 log.debug("[{}][{}] Starting device actor.", processor.tenantId, processor.deviceId);
48 48 try {
49   - processor.initSessionTimeout(ctx);
  49 + processor.init(ctx);
50 50 log.debug("[{}][{}] Device actor started.", processor.tenantId, processor.deviceId);
51 51 } catch (Exception e) {
52 52 log.warn("[{}][{}] Unknown failure", processor.tenantId, processor.deviceId, e);
... ...
... ... @@ -23,6 +23,7 @@ import com.google.common.util.concurrent.MoreExecutors;
23 23 import com.google.protobuf.InvalidProtocolBufferException;
24 24 import lombok.extern.slf4j.Slf4j;
25 25 import org.apache.commons.collections.CollectionUtils;
  26 +import org.thingsboard.common.util.JacksonUtil;
26 27 import org.thingsboard.rule.engine.api.RpcError;
27 28 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
28 29 import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
... ... @@ -38,12 +39,17 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
38 39 import org.thingsboard.server.common.data.edge.EdgeEventType;
39 40 import org.thingsboard.server.common.data.id.DeviceId;
40 41 import org.thingsboard.server.common.data.id.EdgeId;
  42 +import org.thingsboard.server.common.data.id.RpcId;
41 43 import org.thingsboard.server.common.data.id.TenantId;
42 44 import org.thingsboard.server.common.data.kv.AttributeKey;
43 45 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
44 46 import org.thingsboard.server.common.data.kv.KvEntry;
  47 +import org.thingsboard.server.common.data.page.PageData;
  48 +import org.thingsboard.server.common.data.page.PageLink;
45 49 import org.thingsboard.server.common.data.relation.EntityRelation;
46 50 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  51 +import org.thingsboard.server.common.data.rpc.Rpc;
  52 +import org.thingsboard.server.common.data.rpc.RpcStatus;
47 53 import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
48 54 import org.thingsboard.server.common.data.security.DeviceCredentials;
49 55 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
... ... @@ -52,8 +58,8 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
52 58 import org.thingsboard.server.common.msg.queue.TbCallback;
53 59 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
54 60 import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
55   -import org.thingsboard.server.gen.transport.TransportProtos;
56 61 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
  62 +import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
57 63 import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
58 64 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
59 65 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
... ... @@ -68,10 +74,12 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionType;
68 74 import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
69 75 import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg;
70 76 import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto;
  77 +import org.thingsboard.server.gen.transport.TransportProtos.ToDevicePersistedRpcResponseMsg;
71 78 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
72 79 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
73 80 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
74 81 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
  82 +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
75 83 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
76 84 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
77 85 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
... ... @@ -162,20 +170,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
162 170
163 171 void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) {
164 172 ToDeviceRpcRequest request = msg.getMsg();
165   - ToDeviceRpcRequestBody body = request.getBody();
166   - ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder()
167   - .setRequestId(rpcSeq++)
168   - .setMethodName(body.getMethod())
169   - .setParams(body.getParams())
170   - .setExpirationTime(request.getExpirationTime())
171   - .setRequestIdMSB(request.getId().getMostSignificantBits())
172   - .setRequestIdLSB(request.getId().getLeastSignificantBits())
173   - .build();
  173 + ToDeviceRpcRequestMsg rpcRequest = creteToDeviceRpcRequestMsg(request);
174 174
175 175 long timeout = request.getExpirationTime() - System.currentTimeMillis();
  176 + boolean persisted = request.isPersisted();
  177 +
176 178 if (timeout <= 0) {
177 179 log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, request.getId(), request.getExpirationTime());
  180 + if (persisted) {
  181 + createRpc(request, RpcStatus.TIMEOUT);
  182 + }
178 183 return;
  184 + } else if (persisted) {
  185 + createRpc(request, RpcStatus.QUEUED);
179 186 }
180 187
181 188 boolean sent;
... ... @@ -192,10 +199,16 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
192 199 syncSessionSet.add(key);
193 200 }
194 201 });
195   - log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]",syncSessionSet, rpcSubscriptions);
  202 + log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions);
196 203 syncSessionSet.forEach(rpcSubscriptions::remove);
197 204 }
198 205
  206 + if (persisted && !(sent || request.isOneway())) {
  207 + ObjectNode response = JacksonUtil.newObjectNode();
  208 + response.put("rpcId", request.getId().toString());
  209 + systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), JacksonUtil.toString(response), null));
  210 + }
  211 +
199 212 if (request.isOneway() && sent) {
200 213 log.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
201 214 systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null));
... ... @@ -209,6 +222,32 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
209 222 }
210 223 }
211 224
  225 + private Rpc createRpc(ToDeviceRpcRequest request, RpcStatus status) {
  226 + Rpc rpc = new Rpc(new RpcId(request.getId()));
  227 + rpc.setCreatedTime(System.currentTimeMillis());
  228 + rpc.setTenantId(tenantId);
  229 + rpc.setDeviceId(deviceId);
  230 + rpc.setExpirationTime(request.getExpirationTime());
  231 + rpc.setRequest(JacksonUtil.valueToTree(request));
  232 + rpc.setStatus(status);
  233 + systemContext.getTbRpcService().save(tenantId, rpc);
  234 + return systemContext.getTbRpcService().save(tenantId, rpc);
  235 + }
  236 +
  237 + private ToDeviceRpcRequestMsg creteToDeviceRpcRequestMsg(ToDeviceRpcRequest request) {
  238 + ToDeviceRpcRequestBody body = request.getBody();
  239 + return ToDeviceRpcRequestMsg.newBuilder()
  240 + .setRequestId(rpcSeq++)
  241 + .setMethodName(body.getMethod())
  242 + .setParams(body.getParams())
  243 + .setExpirationTime(request.getExpirationTime())
  244 + .setRequestIdMSB(request.getId().getMostSignificantBits())
  245 + .setRequestIdLSB(request.getId().getLeastSignificantBits())
  246 + .setOneway(request.isOneway())
  247 + .setPersisted(request.isPersisted())
  248 + .build();
  249 + }
  250 +
212 251 void processRpcResponsesFromEdge(TbActorCtx context, FromDeviceRpcResponseActorMsg responseMsg) {
213 252 log.debug("[{}] Processing rpc command response from edge session", deviceId);
214 253 ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
... ... @@ -230,6 +269,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
230 269 ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId());
231 270 if (requestMd != null) {
232 271 log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
  272 + systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.TIMEOUT, null);
233 273 systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
234 274 null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION));
235 275 }
... ... @@ -271,7 +311,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
271 311 .setExpirationTime(request.getExpirationTime())
272 312 .setRequestIdMSB(request.getId().getMostSignificantBits())
273 313 .setRequestIdLSB(request.getId().getLeastSignificantBits())
  314 + .setOneway(request.isOneway())
  315 + .setPersisted(request.isPersisted())
274 316 .build();
  317 +
275 318 sendToTransport(rpcRequest, sessionId, nodeId);
276 319 };
277 320 }
... ... @@ -279,31 +322,39 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
279 322 void process(TbActorCtx context, TransportToDeviceActorMsgWrapper wrapper) {
280 323 TransportToDeviceActorMsg msg = wrapper.getMsg();
281 324 TbCallback callback = wrapper.getCallback();
  325 + var sessionInfo = msg.getSessionInfo();
  326 +
282 327 if (msg.hasSessionEvent()) {
283   - processSessionStateMsgs(msg.getSessionInfo(), msg.getSessionEvent());
  328 + processSessionStateMsgs(sessionInfo, msg.getSessionEvent());
284 329 }
285 330 if (msg.hasSubscribeToAttributes()) {
286   - processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToAttributes());
  331 + processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToAttributes());
287 332 }
288 333 if (msg.hasSubscribeToRPC()) {
289   - processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToRPC());
  334 + processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToRPC());
  335 + }
  336 + if (msg.hasSendPendingRPC()) {
  337 + sendPendingRequests(context, getSessionId(sessionInfo), sessionInfo);
290 338 }
291 339 if (msg.hasGetAttributes()) {
292   - handleGetAttributesRequest(context, msg.getSessionInfo(), msg.getGetAttributes());
  340 + handleGetAttributesRequest(context, sessionInfo, msg.getGetAttributes());
293 341 }
294 342 if (msg.hasToDeviceRPCCallResponse()) {
295   - processRpcResponses(context, msg.getSessionInfo(), msg.getToDeviceRPCCallResponse());
  343 + processRpcResponses(context, sessionInfo, msg.getToDeviceRPCCallResponse());
296 344 }
297 345 if (msg.hasSubscriptionInfo()) {
298   - handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo());
  346 + handleSessionActivity(context, sessionInfo, msg.getSubscriptionInfo());
299 347 }
300 348 if (msg.hasClaimDevice()) {
301   - handleClaimDeviceMsg(context, msg.getSessionInfo(), msg.getClaimDevice());
  349 + handleClaimDeviceMsg(context, sessionInfo, msg.getClaimDevice());
  350 + }
  351 + if (msg.hasPersistedRpcResponseMsg()) {
  352 + processPersistedRpcResponses(context, sessionInfo, msg.getPersistedRpcResponseMsg());
302 353 }
303 354 callback.onSuccess();
304 355 }
305 356
306   - private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg) {
  357 + private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) {
307 358 DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
308 359 systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
309 360 }
... ... @@ -442,11 +493,22 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
442 493 if (success) {
443 494 systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
444 495 responseMsg.getPayload(), null));
  496 + if (requestMd.getMsg().getMsg().isPersisted()) {
  497 + systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.SUCCESSFUL, JacksonUtil.toJsonNode(responseMsg.getPayload()));
  498 + }
445 499 } else {
446 500 log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
  501 + if (requestMd.getMsg().getMsg().isPersisted()) {
  502 + systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.FAILED, JacksonUtil.toJsonNode(responseMsg.getPayload()));
  503 + }
447 504 }
448 505 }
449 506
  507 + private void processPersistedRpcResponses(TbActorCtx context, SessionInfoProto sessionInfo, ToDevicePersistedRpcResponseMsg responseMsg) {
  508 + UUID rpcId = new UUID(responseMsg.getRequestIdMSB(), responseMsg.getRequestIdLSB());
  509 + systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), RpcStatus.valueOf(responseMsg.getStatus()), null);
  510 + }
  511 +
450 512 private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) {
451 513 UUID sessionId = getSessionId(sessionInfo);
452 514 if (subscribeCmd.getUnsubscribe()) {
... ... @@ -565,7 +627,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
565 627
566 628 void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) {
567 629 log.info("2) LwM2Mtype: ");
568   - TransportProtos.ToTransportUpdateCredentialsProto.Builder notification = TransportProtos.ToTransportUpdateCredentialsProto.newBuilder();
  630 + ToTransportUpdateCredentialsProto.Builder notification = ToTransportUpdateCredentialsProto.newBuilder();
569 631 notification.addCredentialsId(deviceCredentials.getCredentialsId());
570 632 notification.addCredentialsValue(deviceCredentials.getCredentialsValue());
571 633 ToTransportMsg msg = ToTransportMsg.newBuilder()
... ... @@ -640,7 +702,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
640 702 ListenableFuture<EdgeEvent> future = systemContext.getEdgeEventService().saveAsync(edgeEvent);
641 703 Futures.addCallback(future, new FutureCallback<EdgeEvent>() {
642 704 @Override
643   - public void onSuccess( EdgeEvent result) {
  705 + public void onSuccess(EdgeEvent result) {
644 706 systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId);
645 707 }
646 708
... ... @@ -756,8 +818,26 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
756 818 .addAllSessions(sessionsList).build().toByteArray());
757 819 }
758 820
759   - void initSessionTimeout(TbActorCtx ctx) {
  821 + void init(TbActorCtx ctx) {
760 822 schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(), systemContext.getSessionReportTimeout(), systemContext.getSessionReportTimeout());
  823 + PageLink pageLink = new PageLink(1024);
  824 + PageData<Rpc> pageData;
  825 + do {
  826 + pageData = systemContext.getTbRpcService().findAllByDeviceIdAndStatus(tenantId, deviceId, RpcStatus.QUEUED, pageLink);
  827 + pageData.getData().forEach(rpc -> {
  828 + ToDeviceRpcRequest msg = JacksonUtil.convertValue(rpc.getRequest(), ToDeviceRpcRequest.class);
  829 + long timeout = rpc.getExpirationTime() - System.currentTimeMillis();
  830 + if (timeout <= 0) {
  831 + rpc.setStatus(RpcStatus.TIMEOUT);
  832 + systemContext.getTbRpcService().save(tenantId, rpc);
  833 + } else {
  834 + registerPendingRpcRequest(ctx, new ToDeviceRpcRequestActorMsg(systemContext.getServiceId(), msg), false, creteToDeviceRpcRequestMsg(msg), timeout);
  835 + }
  836 + });
  837 + if (pageData.hasNext()) {
  838 + pageLink = pageLink.nextPageLink();
  839 + }
  840 + } while (pageData.hasNext());
761 841 }
762 842
763 843 void checkSessionsTimeout() {
... ...
... ... @@ -69,6 +69,7 @@ import org.thingsboard.server.common.data.id.EntityId;
69 69 import org.thingsboard.server.common.data.id.EntityIdFactory;
70 70 import org.thingsboard.server.common.data.id.EntityViewId;
71 71 import org.thingsboard.server.common.data.id.OtaPackageId;
  72 +import org.thingsboard.server.common.data.id.RpcId;
72 73 import org.thingsboard.server.common.data.id.TbResourceId;
73 74 import org.thingsboard.server.common.data.id.RuleChainId;
74 75 import org.thingsboard.server.common.data.id.RuleNodeId;
... ... @@ -83,6 +84,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
83 84 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
84 85 import org.thingsboard.server.common.data.plugin.ComponentType;
85 86 import org.thingsboard.server.common.data.relation.EntityRelation;
  87 +import org.thingsboard.server.common.data.rpc.Rpc;
86 88 import org.thingsboard.server.common.data.rule.RuleChain;
87 89 import org.thingsboard.server.common.data.rule.RuleChainType;
88 90 import org.thingsboard.server.common.data.rule.RuleNode;
... ... @@ -106,6 +108,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
106 108 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
107 109 import org.thingsboard.server.dao.oauth2.OAuth2Service;
108 110 import org.thingsboard.server.dao.relation.RelationService;
  111 +import org.thingsboard.server.dao.rpc.RpcService;
109 112 import org.thingsboard.server.dao.rule.RuleChainService;
110 113 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
111 114 import org.thingsboard.server.dao.tenant.TenantProfileService;
... ... @@ -246,6 +249,9 @@ public abstract class BaseController {
246 249 protected OtaPackageStateService otaPackageStateService;
247 250
248 251 @Autowired
  252 + protected RpcService rpcService;
  253 +
  254 + @Autowired
249 255 protected TbQueueProducerProvider producerProvider;
250 256
251 257 @Autowired
... ... @@ -786,6 +792,18 @@ public abstract class BaseController {
786 792 }
787 793 }
788 794
  795 + Rpc checkRpcId(RpcId rpcId, Operation operation) throws ThingsboardException {
  796 + try {
  797 + validateId(rpcId, "Incorrect rpcId " + rpcId);
  798 + Rpc rpc = rpcService.findById(getCurrentUser().getTenantId(), rpcId);
  799 + checkNotNull(rpc);
  800 + accessControlService.checkPermission(getCurrentUser(), Resource.RPC, operation, rpcId, rpc);
  801 + return rpc;
  802 + } catch (Exception e) {
  803 + throw handleException(e, false);
  804 + }
  805 + }
  806 +
789 807 @SuppressWarnings("unchecked")
790 808 protected <I extends EntityId> I emptyId(EntityType entityType) {
791 809 return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
... ...
... ... @@ -29,6 +29,7 @@ import org.springframework.web.bind.annotation.PathVariable;
29 29 import org.springframework.web.bind.annotation.RequestBody;
30 30 import org.springframework.web.bind.annotation.RequestMapping;
31 31 import org.springframework.web.bind.annotation.RequestMethod;
  32 +import org.springframework.web.bind.annotation.RequestParam;
32 33 import org.springframework.web.bind.annotation.ResponseBody;
33 34 import org.springframework.web.bind.annotation.RestController;
34 35 import org.springframework.web.context.request.async.DeferredResult;
... ... @@ -38,8 +39,13 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
38 39 import org.thingsboard.server.common.data.exception.ThingsboardException;
39 40 import org.thingsboard.server.common.data.id.DeviceId;
40 41 import org.thingsboard.server.common.data.id.EntityId;
  42 +import org.thingsboard.server.common.data.id.RpcId;
41 43 import org.thingsboard.server.common.data.id.TenantId;
42 44 import org.thingsboard.server.common.data.id.UUIDBased;
  45 +import org.thingsboard.server.common.data.page.PageData;
  46 +import org.thingsboard.server.common.data.page.PageLink;
  47 +import org.thingsboard.server.common.data.rpc.Rpc;
  48 +import org.thingsboard.server.common.data.rpc.RpcStatus;
43 49 import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
44 50 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
45 51 import org.thingsboard.server.queue.util.TbCoreComponent;
... ... @@ -93,6 +99,52 @@ public class RpcController extends BaseController {
93 99 return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
94 100 }
95 101
  102 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  103 + @RequestMapping(value = "/persisted/{rpcId}", method = RequestMethod.GET)
  104 + @ResponseBody
  105 + public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
  106 + checkParameter("RpcId", strRpc);
  107 + try {
  108 + RpcId rpcId = new RpcId(UUID.fromString(strRpc));
  109 + return checkRpcId(rpcId, Operation.READ);
  110 + } catch (Exception e) {
  111 + throw handleException(e);
  112 + }
  113 + }
  114 +
  115 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  116 + @RequestMapping(value = "/persisted/{deviceId}", method = RequestMethod.GET)
  117 + @ResponseBody
  118 + public PageData<Rpc> getPersistedRpcByDevice(@PathVariable("deviceId") String strDeviceId,
  119 + @RequestParam int pageSize,
  120 + @RequestParam int page,
  121 + @RequestParam RpcStatus rpcStatus,
  122 + @RequestParam(required = false) String textSearch,
  123 + @RequestParam(required = false) String sortProperty,
  124 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  125 + checkParameter("DeviceId", strDeviceId);
  126 + try {
  127 + TenantId tenantId = getCurrentUser().getTenantId();
  128 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  129 + DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId));
  130 + return checkNotNull(rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink));
  131 + } catch (Exception e) {
  132 + throw handleException(e);
  133 + }
  134 + }
  135 +
  136 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  137 + @RequestMapping(value = "/persisted/{rpcId}", method = RequestMethod.DELETE)
  138 + @ResponseBody
  139 + public void deleteResource(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
  140 + checkParameter("RpcId", strRpc);
  141 + try {
  142 + rpcService.deleteRpc(getTenantId(), new RpcId(UUID.fromString(strRpc)));
  143 + } catch (Exception e) {
  144 + throw handleException(e);
  145 + }
  146 + }
  147 +
96 148 private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException {
97 149 try {
98 150 JsonNode rpcRequestBody = jsonMapper.readTree(requestBody);
... ... @@ -103,6 +155,7 @@ public class RpcController extends BaseController {
103 155 long timeout = rpcRequestBody.has("timeout") ? rpcRequestBody.get("timeout").asLong() : defaultTimeout;
104 156 long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout);
105 157 UUID rpcRequestUUID = rpcRequestBody.has("requestUUID") ? UUID.fromString(rpcRequestBody.get("requestUUID").asText()) : UUID.randomUUID();
  158 + boolean persisted = rpcRequestBody.has("persisted") && rpcRequestBody.get("persisted").asBoolean();
106 159 accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() {
107 160 @Override
108 161 public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
... ... @@ -111,7 +164,8 @@ public class RpcController extends BaseController {
111 164 deviceId,
112 165 oneWay,
113 166 expTime,
114   - body
  167 + body,
  168 + persisted
115 169 );
116 170 deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse), currentUser);
117 171 }
... ...
... ... @@ -157,6 +157,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService {
157 157 metaData.putValue("originServiceId", serviceId);
158 158 metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime()));
159 159 metaData.putValue("oneway", Boolean.toString(msg.isOneway()));
  160 + metaData.putValue("persisted", Boolean.toString(msg.isPersisted()));
160 161
161 162 Device device = deviceService.findDeviceById(msg.getTenantId(), msg.getDeviceId());
162 163 if (device != null) {
... ...
... ... @@ -100,7 +100,7 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi
100 100 @Override
101 101 public void sendRpcRequestToDevice(RuleEngineDeviceRpcRequest src, Consumer<RuleEngineDeviceRpcResponse> consumer) {
102 102 ToDeviceRpcRequest request = new ToDeviceRpcRequest(src.getRequestUUID(), src.getTenantId(), src.getDeviceId(),
103   - src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()));
  103 + src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()), src.isPersisted());
104 104 forwardRpcRequestToDeviceActor(request, response -> {
105 105 if (src.isRestApiCall()) {
106 106 sendRpcResponseToTbCore(src.getOriginServiceId(), response);
... ...
  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.rpc;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.RequiredArgsConstructor;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.springframework.stereotype.Service;
  22 +import org.thingsboard.common.util.JacksonUtil;
  23 +import org.thingsboard.server.common.data.id.DeviceId;
  24 +import org.thingsboard.server.common.data.id.RpcId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.page.PageData;
  27 +import org.thingsboard.server.common.data.page.PageLink;
  28 +import org.thingsboard.server.common.data.rpc.Rpc;
  29 +import org.thingsboard.server.common.data.rpc.RpcStatus;
  30 +import org.thingsboard.server.common.msg.TbMsg;
  31 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  32 +import org.thingsboard.server.dao.rpc.RpcService;
  33 +import org.thingsboard.server.queue.util.TbCoreComponent;
  34 +import org.thingsboard.server.service.queue.TbClusterService;
  35 +
  36 +@TbCoreComponent
  37 +@Service
  38 +@RequiredArgsConstructor
  39 +@Slf4j
  40 +public class TbRpcService {
  41 + private final RpcService rpcService;
  42 + private final TbClusterService tbClusterService;
  43 +
  44 + public Rpc save(TenantId tenantId, Rpc rpc) {
  45 + Rpc saved = rpcService.save(rpc);
  46 + pushRpcMsgToRuleEngine(tenantId, saved);
  47 + return saved;
  48 + }
  49 +
  50 + public void save(TenantId tenantId, RpcId rpcId, RpcStatus newStatus, JsonNode response) {
  51 + Rpc foundRpc = rpcService.findById(tenantId, rpcId);
  52 + if (foundRpc != null) {
  53 + foundRpc.setStatus(newStatus);
  54 + if (response != null) {
  55 + foundRpc.setResponse(response);
  56 + }
  57 + Rpc saved = rpcService.save(foundRpc);
  58 + pushRpcMsgToRuleEngine(tenantId, saved);
  59 + } else {
  60 + log.warn("[{}] Failed to update RPC status because RPC was already deleted", rpcId);
  61 + }
  62 + }
  63 +
  64 + private void pushRpcMsgToRuleEngine(TenantId tenantId, Rpc rpc) {
  65 + TbMsg msg = TbMsg.newMsg("RPC_" + rpc.getStatus().name(), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc));
  66 + tbClusterService.pushMsgToRuleEngine(tenantId, rpc.getId(), msg, null);
  67 + }
  68 +
  69 + public Rpc findRpcById(TenantId tenantId, RpcId rpcId) {
  70 + return rpcService.findById(tenantId, rpcId);
  71 + }
  72 +
  73 + public PageData<Rpc> findAllByDeviceIdAndStatus(TenantId tenantId, DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) {
  74 + return rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink);
  75 + }
  76 +
  77 +}
... ...
... ... @@ -47,11 +47,13 @@ import org.thingsboard.server.common.data.id.EntityId;
47 47 import org.thingsboard.server.common.data.id.EntityIdFactory;
48 48 import org.thingsboard.server.common.data.id.EntityViewId;
49 49 import org.thingsboard.server.common.data.id.OtaPackageId;
  50 +import org.thingsboard.server.common.data.id.RpcId;
50 51 import org.thingsboard.server.common.data.id.RuleChainId;
51 52 import org.thingsboard.server.common.data.id.RuleNodeId;
52 53 import org.thingsboard.server.common.data.id.TbResourceId;
53 54 import org.thingsboard.server.common.data.id.TenantId;
54 55 import org.thingsboard.server.common.data.id.UserId;
  56 +import org.thingsboard.server.common.data.rpc.Rpc;
55 57 import org.thingsboard.server.common.data.rule.RuleChain;
56 58 import org.thingsboard.server.common.data.rule.RuleNode;
57 59 import org.thingsboard.server.controller.HttpValidationCallback;
... ... @@ -65,6 +67,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService;
65 67 import org.thingsboard.server.dao.exception.IncorrectParameterException;
66 68 import org.thingsboard.server.dao.ota.OtaPackageService;
67 69 import org.thingsboard.server.dao.resource.ResourceService;
  70 +import org.thingsboard.server.dao.rpc.RpcService;
68 71 import org.thingsboard.server.dao.rule.RuleChainService;
69 72 import org.thingsboard.server.dao.tenant.TenantService;
70 73 import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
... ... @@ -137,6 +140,9 @@ public class AccessValidator {
137 140 @Autowired
138 141 protected OtaPackageService otaPackageService;
139 142
  143 + @Autowired
  144 + protected RpcService rpcService;
  145 +
140 146 private ExecutorService executor;
141 147
142 148 @PostConstruct
... ... @@ -235,6 +241,9 @@ public class AccessValidator {
235 241 case OTA_PACKAGE:
236 242 validateOtaPackage(currentUser, operation, entityId, callback);
237 243 return;
  244 + case RPC:
  245 + validateRpc(currentUser, operation, entityId, callback);
  246 + return;
238 247 default:
239 248 //TODO: add support of other entities
240 249 throw new IllegalStateException("Not Implemented!");
... ... @@ -261,6 +270,22 @@ public class AccessValidator {
261 270 }
262 271 }
263 272
  273 + private void validateRpc(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
  274 + ListenableFuture<Rpc> rpcFurure = rpcService.findRpcByIdAsync(currentUser.getTenantId(), new RpcId(entityId.getId()));
  275 + Futures.addCallback(rpcFurure, getCallback(callback, rpc -> {
  276 + if (rpc == null) {
  277 + return ValidationResult.entityNotFound("Rpc with requested id wasn't found!");
  278 + } else {
  279 + try {
  280 + accessControlService.checkPermission(currentUser, Resource.RPC, operation, entityId, rpc);
  281 + } catch (ThingsboardException e) {
  282 + return ValidationResult.accessDenied(e.getMessage());
  283 + }
  284 + return ValidationResult.ok(rpc);
  285 + }
  286 + }), executor);
  287 + }
  288 +
264 289 private void validateDeviceProfile(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
265 290 if (currentUser.isSystemAdmin()) {
266 291 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
... ...
... ... @@ -41,6 +41,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
41 41 put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker);
42 42 put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
43 43 put(Resource.EDGE, customerEntityPermissionChecker);
  44 + put(Resource.RPC, rpcPermissionChecker);
44 45 }
45 46
46 47 private static final PermissionChecker customerEntityPermissionChecker =
... ... @@ -138,4 +139,22 @@ public class CustomerUserPermissions extends AbstractPermissions {
138 139 }
139 140
140 141 };
  142 +
  143 + private static final PermissionChecker rpcPermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ) {
  144 +
  145 + @Override
  146 + @SuppressWarnings("unchecked")
  147 + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) {
  148 + if (!super.hasPermission(user, operation, entityId, entity)) {
  149 + return false;
  150 + }
  151 + if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) {
  152 + return true;
  153 + }
  154 + if (!user.getTenantId().equals(entity.getTenantId())) {
  155 + return false;
  156 + }
  157 + return true;
  158 + }
  159 + };
141 160 }
... ...
... ... @@ -39,7 +39,8 @@ public enum Resource {
39 39 API_USAGE_STATE(EntityType.API_USAGE_STATE),
40 40 TB_RESOURCE(EntityType.TB_RESOURCE),
41 41 OTA_PACKAGE(EntityType.OTA_PACKAGE),
42   - EDGE(EntityType.EDGE);
  42 + EDGE(EntityType.EDGE),
  43 + RPC(EntityType.RPC);
43 44
44 45 private final EntityType entityType;
45 46
... ...
... ... @@ -44,6 +44,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
44 44 put(Resource.TB_RESOURCE, tbResourcePermissionChecker);
45 45 put(Resource.OTA_PACKAGE, tenantEntityPermissionChecker);
46 46 put(Resource.EDGE, tenantEntityPermissionChecker);
  47 + put(Resource.RPC, tenantEntityPermissionChecker);
47 48 }
48 49
49 50 public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {
... ...
... ... @@ -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();
... ...
  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.ttl.rpc;
  17 +
  18 +import lombok.RequiredArgsConstructor;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Value;
  21 +import org.springframework.scheduling.annotation.Scheduled;
  22 +import org.springframework.stereotype.Service;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.page.PageData;
  25 +import org.thingsboard.server.common.data.page.PageLink;
  26 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
  27 +import org.thingsboard.server.common.msg.queue.ServiceType;
  28 +import org.thingsboard.server.dao.rpc.RpcDao;
  29 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
  30 +import org.thingsboard.server.dao.tenant.TenantDao;
  31 +import org.thingsboard.server.queue.discovery.PartitionService;
  32 +import org.thingsboard.server.queue.util.TbCoreComponent;
  33 +
  34 +import java.util.Date;
  35 +import java.util.Optional;
  36 +import java.util.concurrent.TimeUnit;
  37 +
  38 +@TbCoreComponent
  39 +@Service
  40 +@Slf4j
  41 +@RequiredArgsConstructor
  42 +public class RpcCleanUpService {
  43 + @Value("${sql.ttl.rpc.enabled}")
  44 + private boolean ttlTaskExecutionEnabled;
  45 +
  46 + private final TenantDao tenantDao;
  47 + private final PartitionService partitionService;
  48 + private final TbTenantProfileCache tenantProfileCache;
  49 + private final RpcDao rpcDao;
  50 +
  51 + @Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.rpc.checking_interval})}", fixedDelayString = "${sql.ttl.rpc.checking_interval}")
  52 + public void cleanUp() {
  53 + if (ttlTaskExecutionEnabled) {
  54 + PageLink tenantsBatchRequest = new PageLink(10_000, 0);
  55 + PageData<TenantId> tenantsIds;
  56 + do {
  57 + tenantsIds = tenantDao.findTenantsIds(tenantsBatchRequest);
  58 + for (TenantId tenantId : tenantsIds.getData()) {
  59 + if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
  60 + continue;
  61 + }
  62 +
  63 + Optional<DefaultTenantProfileConfiguration> tenantProfileConfiguration = tenantProfileCache.get(tenantId).getProfileConfiguration();
  64 + if (tenantProfileConfiguration.isEmpty() || tenantProfileConfiguration.get().getRpcTtlDays() == 0) {
  65 + continue;
  66 + }
  67 +
  68 + long ttl = TimeUnit.DAYS.toMillis(tenantProfileConfiguration.get().getRpcTtlDays());
  69 + long expirationTime = System.currentTimeMillis() - ttl;
  70 +
  71 + long totalRemoved = rpcDao.deleteOutdatedRpcByTenantId(tenantId, expirationTime);
  72 +
  73 + if (totalRemoved > 0) {
  74 + log.info("Removed {} outdated rpc(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));
  75 + }
  76 + }
  77 +
  78 + tenantsBatchRequest = tenantsBatchRequest.nextPageLink();
  79 + } while (tenantsIds.hasNext());
  80 + }
  81 + }
  82 +
  83 +}
... ...
... ... @@ -276,6 +276,9 @@ sql:
276 276 alarms:
277 277 checking_interval: "${SQL_ALARMS_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours
278 278 removal_batch_size: "${SQL_ALARMS_TTL_REMOVAL_BATCH_SIZE:3000}" # To delete outdated alarms not all at once but in batches
  279 + rpc:
  280 + enabled: "${SQL_TTL_RPC_ENABLED:true}"
  281 + checking_interval: "${SQL_RPC_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours
279 282
280 283 # Actor system parameters
281 284 actors:
... ...
... ... @@ -75,13 +75,11 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
75 75 return device;
76 76 }
77 77
78   - //TODO: use different endpoints to isolate tests.
79   - @Ignore()
80 78 @Test
81 79 public void testConnectAndObserveTelemetry() throws Exception {
82 80 createDeviceProfile(TRANSPORT_CONFIGURATION);
83 81 X509ClientCredentials credentials = new X509ClientCredentials();
84   - credentials.setEndpoint(endpoint+1);
  82 + credentials.setEndpoint(endpoint);
85 83 Device device = createDevice(credentials);
86 84
87 85 SingleEntityFilter sef = new SingleEntityFilter();
... ... @@ -99,7 +97,7 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
99 97 wsClient.waitForReply();
100 98
101 99 wsClient.registerWaitForUpdate();
102   - LwM2MTestClient client = new LwM2MTestClient(executor, endpoint+1);
  100 + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint);
103 101 Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded());
104 102 client.init(security, coapConfig);
105 103 String msg = wsClient.waitForUpdate();
... ...
... ... @@ -14,7 +14,7 @@
14 14 <logger name="org.springframework.boot.test" level="WARN"/>
15 15 <logger name="org.apache.cassandra" level="WARN"/>
16 16 <logger name="org.cassandraunit" level="INFO"/>
17   - <logger name="org.eclipse.leshan" level="TRACE"/>
  17 + <logger name="org.eclipse.leshan" level="INFO"/>
18 18
19 19
20 20 <root level="WARN">
... ...
  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.dao.rpc;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.id.DeviceId;
  20 +import org.thingsboard.server.common.data.id.RpcId;
  21 +import org.thingsboard.server.common.data.id.TenantId;
  22 +import org.thingsboard.server.common.data.page.PageData;
  23 +import org.thingsboard.server.common.data.page.PageLink;
  24 +import org.thingsboard.server.common.data.rpc.Rpc;
  25 +import org.thingsboard.server.common.data.rpc.RpcStatus;
  26 +
  27 +public interface RpcService {
  28 + Rpc save(Rpc rpc);
  29 +
  30 + void deleteRpc(TenantId tenantId, RpcId id);
  31 +
  32 + void deleteAllRpcByTenantId(TenantId tenantId);
  33 +
  34 + Rpc findById(TenantId tenantId, RpcId id);
  35 +
  36 + ListenableFuture<Rpc> findRpcByIdAsync(TenantId tenantId, RpcId id);
  37 +
  38 + PageData<Rpc> findAllByDeviceIdAndStatus(TenantId tenantId, DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink);
  39 +}
... ...
... ... @@ -76,6 +76,12 @@ public class DataConstants {
76 76
77 77 public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE";
78 78
  79 + public static final String RPC_QUEUED = "RPC_QUEUED";
  80 + public static final String RPC_DELIVERED = "RPC_DELIVERED";
  81 + public static final String RPC_SUCCESSFUL = "RPC_SUCCESSFUL";
  82 + public static final String RPC_TIMEOUT = "RPC_TIMEOUT";
  83 + public static final String RPC_FAILED = "RPC_FAILED";
  84 +
79 85 public static final String DEFAULT_SECRET_KEY = "";
80 86 public static final String SECRET_KEY_FIELD_NAME = "secretKey";
81 87 public static final String DURATION_MS_FIELD_NAME = "durationMs";
... ...
... ... @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
19 19 * @author Andrew Shvayka
20 20 */
21 21 public enum EntityType {
22   - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE;
  22 + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC;
23 23 }
... ...
... ... @@ -75,6 +75,8 @@ public class EntityIdFactory {
75 75 return new OtaPackageId(uuid);
76 76 case EDGE:
77 77 return new EdgeId(uuid);
  78 + case RPC:
  79 + return new RpcId(uuid);
78 80 }
79 81 throw new IllegalArgumentException("EntityType " + type + " is not supported!");
80 82 }
... ...
  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.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonIgnore;
  20 +import com.fasterxml.jackson.annotation.JsonProperty;
  21 +import org.thingsboard.server.common.data.EntityType;
  22 +
  23 +import java.util.UUID;
  24 +
  25 +public final class RpcId extends UUIDBased implements EntityId {
  26 +
  27 + private static final long serialVersionUID = 1L;
  28 +
  29 + @JsonCreator
  30 + public RpcId(@JsonProperty("id") UUID id) {
  31 + super(id);
  32 + }
  33 +
  34 + @JsonIgnore
  35 + @Override
  36 + public EntityType getEntityType() {
  37 + return EntityType.RPC;
  38 + }
  39 +}
... ...
  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.rpc;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import org.thingsboard.server.common.data.BaseData;
  22 +import org.thingsboard.server.common.data.HasTenantId;
  23 +import org.thingsboard.server.common.data.id.DeviceId;
  24 +import org.thingsboard.server.common.data.id.RpcId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +
  27 +@Data
  28 +@EqualsAndHashCode(callSuper = true)
  29 +public class Rpc extends BaseData<RpcId> implements HasTenantId {
  30 + private TenantId tenantId;
  31 + private DeviceId deviceId;
  32 + private long expirationTime;
  33 + private JsonNode request;
  34 + private JsonNode response;
  35 + private RpcStatus status;
  36 +
  37 + public Rpc() {
  38 + super();
  39 + }
  40 +
  41 + public Rpc(RpcId id) {
  42 + super(id);
  43 + }
  44 +
  45 + public Rpc(Rpc rpc) {
  46 + super(rpc);
  47 + this.tenantId = rpc.getTenantId();
  48 + this.deviceId = rpc.getDeviceId();
  49 + this.expirationTime = rpc.getExpirationTime();
  50 + this.request = rpc.getRequest();
  51 + this.response = rpc.getResponse();
  52 + this.status = rpc.getStatus();
  53 + }
  54 +}
... ...
  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.rpc;
  17 +
  18 +public enum RpcStatus {
  19 + QUEUED, DELIVERED, SUCCESSFUL, TIMEOUT, FAILED
  20 +}
... ...
... ... @@ -56,6 +56,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
56 56
57 57 private int defaultStorageTtlDays;
58 58 private int alarmsTtlDays;
  59 + private int rpcTtlDays;
59 60
60 61 private double warnThreshold;
61 62
... ...
... ... @@ -34,5 +34,6 @@ public class ToDeviceRpcRequest implements Serializable {
34 34 private final boolean oneway;
35 35 private final long expirationTime;
36 36 private final ToDeviceRpcRequestBody body;
  37 + private final boolean persisted;
37 38 }
38 39
... ...
... ... @@ -318,6 +318,9 @@ message SubscribeToRPCMsg {
318 318 SessionType sessionType = 2;
319 319 }
320 320
  321 +message SendPendingRPCMsg {
  322 +}
  323 +
321 324 message ToDeviceRpcRequestMsg {
322 325 int32 requestId = 1;
323 326 string methodName = 2;
... ... @@ -325,6 +328,8 @@ message ToDeviceRpcRequestMsg {
325 328 int64 expirationTime = 4;
326 329 int64 requestIdMSB = 5;
327 330 int64 requestIdLSB = 6;
  331 + bool oneway = 7;
  332 + bool persisted = 8;
328 333 }
329 334
330 335 message ToDeviceRpcResponseMsg {
... ... @@ -332,6 +337,13 @@ message ToDeviceRpcResponseMsg {
332 337 string payload = 2;
333 338 }
334 339
  340 +message ToDevicePersistedRpcResponseMsg {
  341 + int32 requestId = 1;
  342 + int64 requestIdMSB = 2;
  343 + int64 requestIdLSB = 3;
  344 + string status = 4;
  345 +}
  346 +
335 347 message ToServerRpcRequestMsg {
336 348 int32 requestId = 1;
337 349 string methodName = 2;
... ... @@ -435,6 +447,8 @@ message TransportToDeviceActorMsg {
435 447 SubscriptionInfoProto subscriptionInfo = 7;
436 448 ClaimDeviceMsg claimDevice = 8;
437 449 ProvisionDeviceRequestMsg provisionDevice = 9;
  450 + ToDevicePersistedRpcResponseMsg persistedRpcResponseMsg = 10;
  451 + SendPendingRPCMsg sendPendingRPC = 11;
438 452 }
439 453
440 454 message TransportToRuleEngineMsg {
... ...
... ... @@ -44,6 +44,7 @@ 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.rpc.RpcStatus;
47 48 import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
48 49 import org.thingsboard.server.common.msg.session.FeatureType;
49 50 import org.thingsboard.server.common.msg.session.SessionMsgType;
... ... @@ -332,14 +333,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
332 333 break;
333 334 case TO_SERVER_RPC_REQUEST:
334 335 transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor,
335   - transportConfigurationContainer.getRpcRequestDynamicMessageBuilder()), timeout);
  336 + transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), sessionInfo), timeout);
336 337 transportService.process(sessionInfo,
337 338 coapTransportAdaptor.convertToServerRpcRequest(sessionId, request),
338 339 new CoapNoOpCallback(exchange));
339 340 break;
340 341 case GET_ATTRIBUTES_REQUEST:
341 342 transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor,
342   - transportConfigurationContainer.getRpcRequestDynamicMessageBuilder()), timeout);
  343 + transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), sessionInfo), timeout);
343 344 transportService.process(sessionInfo,
344 345 coapTransportAdaptor.convertToGetAttributes(sessionId, request),
345 346 new CoapNoOpCallback(exchange));
... ... @@ -362,12 +363,12 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
362 363
363 364 private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, String token) {
364 365 tokenToSessionInfoMap.putIfAbsent(token, sessionInfo);
365   - transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder));
  366 + transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder, sessionInfo));
366 367 transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null);
367 368 }
368 369
369   - private CoapSessionListener getCoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) {
370   - return new CoapSessionListener(this, exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder);
  370 + private CoapSessionListener getCoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, TransportProtos.SessionInfoProto sessionInfo) {
  371 + return new CoapSessionListener(this, exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder, sessionInfo);
371 372 }
372 373
373 374 private String getTokenFromRequest(Request request) {
... ... @@ -455,12 +456,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
455 456 private final CoapExchange exchange;
456 457 private final CoapTransportAdaptor coapTransportAdaptor;
457 458 private final DynamicMessage.Builder rpcRequestDynamicMessageBuilder;
  459 + private final TransportProtos.SessionInfoProto sessionInfo;
458 460
459   - CoapSessionListener(CoapTransportResource coapTransportResource, CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) {
  461 + CoapSessionListener(CoapTransportResource coapTransportResource, CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, TransportProtos.SessionInfoProto sessionInfo) {
460 462 this.coapTransportResource = coapTransportResource;
461 463 this.exchange = exchange;
462 464 this.coapTransportAdaptor = coapTransportAdaptor;
463 465 this.rpcRequestDynamicMessageBuilder = rpcRequestDynamicMessageBuilder;
  466 + this.sessionInfo = sessionInfo;
464 467 }
465 468
466 469 @Override
... ... @@ -503,11 +506,31 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
503 506
504 507 @Override
505 508 public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) {
  509 + boolean successful;
506 510 try {
507 511 exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg, rpcRequestDynamicMessageBuilder));
  512 + successful = true;
508 513 } catch (AdaptorException e) {
509 514 log.trace("Failed to reply due to error", e);
510 515 exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR);
  516 + successful = false;
  517 + }
  518 + if (msg.getPersisted()) {
  519 + RpcStatus status;
  520 + if (!successful) {
  521 + status = RpcStatus.FAILED;
  522 + } else if (msg.getOneway()) {
  523 + status = RpcStatus.SUCCESSFUL;
  524 + } else {
  525 + status = RpcStatus.DELIVERED;
  526 + }
  527 + TransportProtos.ToDevicePersistedRpcResponseMsg responseMsg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder()
  528 + .setRequestId(msg.getRequestId())
  529 + .setRequestIdLSB(msg.getRequestIdLSB())
  530 + .setRequestIdMSB(msg.getRequestIdMSB())
  531 + .setStatus(status.name())
  532 + .build();
  533 + coapTransportResource.transportService.process(sessionInfo, responseMsg, TransportServiceCallback.EMPTY);
511 534 }
512 535 }
513 536
... ...
... ... @@ -18,11 +18,12 @@ package org.thingsboard.server.transport.coap;
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.eclipse.californium.core.CoapResource;
20 20 import org.eclipse.californium.core.CoapServer;
  21 +import org.eclipse.californium.core.network.config.NetworkConfig;
21 22 import org.springframework.beans.factory.annotation.Autowired;
22 23 import org.springframework.stereotype.Service;
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.TbTransportService;
26 27 import org.thingsboard.server.common.data.ota.OtaPackageType;
27 28 import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource;
28 29
... ... @@ -30,6 +31,8 @@ import javax.annotation.PostConstruct;
30 31 import javax.annotation.PreDestroy;
31 32 import java.net.UnknownHostException;
32 33
  34 +import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME;
  35 +
33 36 @Service("CoapTransportService")
34 37 @TbCoapServerComponent
35 38 @Slf4j
... ... @@ -52,6 +55,14 @@ public class CoapTransportService implements TbTransportService {
52 55 public void init() throws UnknownHostException {
53 56 log.info("Starting CoAP transport...");
54 57 coapServer = coapServerService.getCoapServer();
  58 + coapServer.getConfig().setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true);
  59 + coapServer.getConfig().setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true);
  60 + coapServer.getConfig().setLong(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME);
  61 + coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024);
  62 + coapServer.getConfig().setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED");
  63 + coapServer.getConfig().setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024);
  64 + coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024);
  65 + coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 10);
55 66 CoapResource api = new CoapResource(API);
56 67 api.add(new CoapTransportResource(coapTransportContext, coapServerService, V1));
57 68
... ...
... ... @@ -24,6 +24,7 @@ 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.common.util.ThingsBoardExecutors;
27 28 import org.thingsboard.server.common.data.DeviceTransportType;
28 29 import org.thingsboard.server.common.data.StringUtils;
29 30 import org.thingsboard.server.common.data.ota.OtaPackageType;
... ... @@ -34,6 +35,7 @@ import org.thingsboard.server.gen.transport.TransportProtos;
34 35 import java.util.List;
35 36 import java.util.Optional;
36 37 import java.util.UUID;
  38 +import java.util.concurrent.ExecutorService;
37 39
38 40 @Slf4j
39 41 public class OtaPackageTransportResource extends AbstractCoapTransportResource {
... ... @@ -43,9 +45,9 @@ public class OtaPackageTransportResource extends AbstractCoapTransportResource {
43 45
44 46 public OtaPackageTransportResource(CoapTransportContext ctx, OtaPackageType otaPackageType) {
45 47 super(ctx, otaPackageType.getKeyPrefix());
46   - this.setObservable(true);
47   - this.addObserver(new OtaPackageTransportResource.CoapResourceObserver());
48 48 this.otaPackageType = otaPackageType;
  49 +
  50 + this.setObservable(true);
49 51 }
50 52
51 53 @Override
... ... @@ -138,43 +140,10 @@ public class OtaPackageTransportResource extends AbstractCoapTransportResource {
138 140 response.setPayload(data);
139 141 if (exchange.getRequestOptions().getBlock2() != null) {
140 142 int chunkSize = exchange.getRequestOptions().getBlock2().getSzx();
141   - boolean lastFlag = data.length > chunkSize;
142   - response.getOptions().setUriPath(exchange.getRequestOptions().getUriPathString());
  143 + boolean lastFlag = data.length <= chunkSize;
143 144 response.getOptions().setBlock2(chunkSize, lastFlag, 0);
144 145 }
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   -
  146 + transportContext.getExecutor().submit(() -> exchange.respond(response));
178 147 }
179 148 }
180 149
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.http;
17 17
18 18 import com.google.gson.JsonObject;
19 19 import com.google.gson.JsonParser;
  20 +import lombok.RequiredArgsConstructor;
20 21 import lombok.extern.slf4j.Slf4j;
21 22 import org.springframework.beans.factory.annotation.Autowired;
22 23 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
... ... @@ -34,9 +35,10 @@ import org.springframework.web.bind.annotation.RequestParam;
34 35 import org.springframework.web.bind.annotation.RestController;
35 36 import org.springframework.web.context.request.async.DeferredResult;
36 37 import org.thingsboard.server.common.data.DeviceTransportType;
37   -import org.thingsboard.server.common.data.ota.OtaPackageType;
38 38 import org.thingsboard.server.common.data.TbTransportService;
39 39 import org.thingsboard.server.common.data.id.DeviceId;
  40 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  41 +import org.thingsboard.server.common.data.rpc.RpcStatus;
40 42 import org.thingsboard.server.common.transport.SessionMsgListener;
41 43 import org.thingsboard.server.common.transport.TransportContext;
42 44 import org.thingsboard.server.common.transport.TransportService;
... ... @@ -95,7 +97,9 @@ public class DeviceApiController implements TbTransportService {
95 97 request.addAllSharedAttributeNames(sharedKeySet);
96 98 }
97 99 TransportService transportService = transportContext.getTransportService();
98   - transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), transportContext.getDefaultTimeout());
  100 + transportService.registerSyncSession(sessionInfo,
  101 + new HttpSessionListener(responseWriter, transportContext.getTransportService(), sessionInfo),
  102 + transportContext.getDefaultTimeout());
99 103 transportService.process(sessionInfo, request.build(), new SessionCloseOnErrorCallback(transportService, sessionInfo));
100 104 }));
101 105 return responseWriter;
... ... @@ -151,7 +155,8 @@ public class DeviceApiController implements TbTransportService {
151 155 transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
152 156 new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
153 157 TransportService transportService = transportContext.getTransportService();
154   - transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter),
  158 + transportService.registerSyncSession(sessionInfo,
  159 + new HttpSessionListener(responseWriter, transportContext.getTransportService(), sessionInfo),
155 160 timeout == 0 ? transportContext.getDefaultTimeout() : timeout);
156 161 transportService.process(sessionInfo, SubscribeToRPCMsg.getDefaultInstance(),
157 162 new SessionCloseOnErrorCallback(transportService, sessionInfo));
... ... @@ -181,7 +186,9 @@ public class DeviceApiController implements TbTransportService {
181 186 new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
182 187 JsonObject request = new JsonParser().parse(json).getAsJsonObject();
183 188 TransportService transportService = transportContext.getTransportService();
184   - transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), transportContext.getDefaultTimeout());
  189 + transportService.registerSyncSession(sessionInfo,
  190 + new HttpSessionListener(responseWriter, transportContext.getTransportService(), sessionInfo),
  191 + transportContext.getDefaultTimeout());
185 192 transportService.process(sessionInfo, ToServerRpcRequestMsg.newBuilder().setRequestId(0)
186 193 .setMethodName(request.get("method").getAsString())
187 194 .setParams(request.get("params").toString()).build(),
... ... @@ -198,7 +205,8 @@ public class DeviceApiController implements TbTransportService {
198 205 transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
199 206 new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
200 207 TransportService transportService = transportContext.getTransportService();
201   - transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter),
  208 + transportService.registerSyncSession(sessionInfo,
  209 + new HttpSessionListener(responseWriter, transportContext.getTransportService(), sessionInfo),
202 210 timeout == 0 ? transportContext.getDefaultTimeout() : timeout);
203 211 transportService.process(sessionInfo, SubscribeToAttributeUpdatesMsg.getDefaultInstance(),
204 212 new SessionCloseOnErrorCallback(transportService, sessionInfo));
... ... @@ -372,13 +380,12 @@ public class DeviceApiController implements TbTransportService {
372 380 }
373 381 }
374 382
  383 + @RequiredArgsConstructor
375 384 private static class HttpSessionListener implements SessionMsgListener {
376 385
377 386 private final DeferredResult<ResponseEntity> responseWriter;
378   -
379   - HttpSessionListener(DeferredResult<ResponseEntity> responseWriter) {
380   - this.responseWriter = responseWriter;
381   - }
  387 + private final TransportService transportService;
  388 + private final SessionInfoProto sessionInfo;
382 389
383 390 @Override
384 391 public void onGetAttributesResponse(GetAttributeResponseMsg msg) {
... ... @@ -399,6 +406,21 @@ public class DeviceApiController implements TbTransportService {
399 406 @Override
400 407 public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg msg) {
401 408 responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg, true).toString(), HttpStatus.OK));
  409 + if (msg.getPersisted()) {
  410 + RpcStatus status;
  411 + if (msg.getOneway()) {
  412 + status = RpcStatus.SUCCESSFUL;
  413 + } else {
  414 + status = RpcStatus.DELIVERED;
  415 + }
  416 + TransportProtos.ToDevicePersistedRpcResponseMsg responseMsg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder()
  417 + .setRequestId(msg.getRequestId())
  418 + .setRequestIdLSB(msg.getRequestIdLSB())
  419 + .setRequestIdMSB(msg.getRequestIdMSB())
  420 + .setStatus(status.name())
  421 + .build();
  422 + transportService.process(sessionInfo, responseMsg, TransportServiceCallback.EMPTY);
  423 + }
402 424 }
403 425
404 426 @Override
... ...
... ... @@ -29,12 +29,11 @@ import org.springframework.stereotype.Service;
29 29 import org.thingsboard.common.util.JacksonUtil;
30 30 import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration;
31 31 import org.thingsboard.server.gen.transport.TransportProtos;
32   -import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
33 32 import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
  33 +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
34 34 import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener;
35 35 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
36 36 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
37   -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
38 37
39 38 import java.io.IOException;
40 39 import java.security.GeneralSecurityException;
... ... @@ -46,6 +45,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L
46 45 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
47 46 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY;
48 47 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getBootstrapParametersFromThingsboard;
  48 +import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP;
49 49
50 50 @Slf4j
51 51 @Service("LwM2MBootstrapSecurityStore")
... ... @@ -68,7 +68,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
68 68
69 69 @Override
70 70 public Iterator<SecurityInfo> getAllByEndpoint(String endPoint) {
71   - TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
  71 + TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, BOOTSTRAP);
72 72 if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
73 73 /* add value to store from BootstrapJson */
74 74 this.setBootstrapConfigScurityInfo(store);
... ... @@ -92,7 +92,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
92 92
93 93 @Override
94 94 public SecurityInfo getByIdentity(String identity) {
95   - TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
  95 + TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, BOOTSTRAP);
96 96 if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
97 97 /* add value to store from BootstrapJson */
98 98 this.setBootstrapConfigScurityInfo(store);
... ... @@ -155,7 +155,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
155 155 LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class);
156 156 UUID sessionUUiD = UUID.randomUUID();
157 157 TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits());
158   - context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, null, null, sessionInfo));
  158 + context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, null, null, sessionInfo, context.getTransportService()));
159 159 if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
160 160 lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
161 161 lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer);
... ...
... ... @@ -142,7 +142,7 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
142 142 URI uri = null;
143 143 try {
144 144 uri = Resources.getResource(keyStorePathFile).toURI();
145   - log.error("URI: {}", uri);
  145 + log.info("URI: {}", uri);
146 146 File keyStoreFile = new File(uri);
147 147 InputStream inKeyStore = new FileInputStream(keyStoreFile);
148 148 keyStoreValue = KeyStore.getInstance(keyStoreType);
... ...
... ... @@ -33,7 +33,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
33 33 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
34 34 import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
35 35 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
36   -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
  36 +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer;
37 37
38 38 import java.io.IOException;
39 39 import java.security.GeneralSecurityException;
... ... @@ -45,6 +45,7 @@ import static org.eclipse.leshan.core.SecurityMode.NO_SEC;
45 45 import static org.eclipse.leshan.core.SecurityMode.PSK;
46 46 import static org.eclipse.leshan.core.SecurityMode.RPK;
47 47 import static org.eclipse.leshan.core.SecurityMode.X509;
  48 +import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP;
48 49
49 50 @Slf4j
50 51 @Component
... ... @@ -55,13 +56,15 @@ public class LwM2mCredentialsSecurityInfoValidator {
55 56 private final LwM2mTransportContext context;
56 57 private final LwM2MTransportServerConfig config;
57 58
58   - public TbLwM2MSecurityInfo getEndpointSecurityInfoByCredentialsId(String credentialsId, LwM2mTransportUtil.LwM2mTypeServer keyValue) {
  59 + public TbLwM2MSecurityInfo getEndpointSecurityInfoByCredentialsId(String credentialsId, LwM2mTypeServer keyValue) {
59 60 CountDownLatch latch = new CountDownLatch(1);
60 61 final TbLwM2MSecurityInfo[] resultSecurityStore = new TbLwM2MSecurityInfo[1];
  62 + log.trace("Validating credentials [{}]", credentialsId);
61 63 context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(credentialsId).build(),
62 64 new TransportServiceCallback<>() {
63 65 @Override
64 66 public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  67 + log.trace("Validated credentials: [{}] [{}]", credentialsId, msg);
65 68 String credentialsBody = msg.getCredentials();
66 69 resultSecurityStore[0] = createSecurityInfo(credentialsId, credentialsBody, keyValue);
67 70 resultSecurityStore[0].setMsg(msg);
... ... @@ -91,11 +94,11 @@ public class LwM2mCredentialsSecurityInfoValidator {
91 94 * @param keyValue -
92 95 * @return SecurityInfo
93 96 */
94   - private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) {
  97 + private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTypeServer keyValue) {
95 98 TbLwM2MSecurityInfo result = new TbLwM2MSecurityInfo();
96 99 LwM2MCredentials credentials = JacksonUtil.fromString(jsonStr, LwM2MCredentials.class);
97 100 if (credentials != null) {
98   - if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) {
  101 + if (keyValue.equals(BOOTSTRAP)) {
99 102 result.setBootstrapCredentialConfig(credentials.getBootstrap());
100 103 if (LwM2MSecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) {
101 104 PSKClientCredentials pskClientConfig = (PSKClientCredentials) credentials.getClient();
... ...
... ... @@ -42,9 +42,8 @@ import org.thingsboard.server.common.transport.util.SslUtil;
42 42 import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
43 43 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
44 44 import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
45   -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
46   -import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore;
47 45 import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
  46 +import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore;
48 47
49 48 import javax.annotation.PostConstruct;
50 49 import javax.security.auth.x500.X500Principal;
... ... @@ -57,6 +56,8 @@ import java.security.cert.X509Certificate;
57 56 import java.util.Arrays;
58 57 import java.util.List;
59 58
  59 +import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
  60 +
60 61 @Slf4j
61 62 @Component
62 63 @TbLwM2mTransportComponent
... ... @@ -66,7 +67,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
66 67 private final TbLwM2MDtlsSessionStore sessionStorage;
67 68 private final LwM2MTransportServerConfig config;
68 69 private final LwM2mCredentialsSecurityInfoValidator securityInfoValidator;
69   - private final TbEditableSecurityStore securityStore;
  70 + private final TbMainSecurityStore securityStore;
70 71
71 72 @SuppressWarnings("deprecation")
72 73 private StaticCertificateVerifier staticCertificateVerifier;
... ... @@ -117,7 +118,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
117 118
118 119 String strCert = SslUtil.getCertificateString(cert);
119 120 String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
120   - TbLwM2MSecurityInfo securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, LwM2mTransportUtil.LwM2mTypeServer.CLIENT);
  121 + TbLwM2MSecurityInfo securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, CLIENT);
121 122 ValidateDeviceCredentialsResponse msg = securityInfo != null ? securityInfo.getMsg() : null;
122 123 if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) {
123 124 LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class);
... ... @@ -133,7 +134,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
133 134 if (msg.hasDeviceInfo() && deviceProfile != null) {
134 135 sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg));
135 136 try {
136   - securityStore.put(securityInfo);
  137 + securityStore.putX509(securityInfo);
137 138 } catch (NonUniqueSecurityInfoException e) {
138 139 log.trace("Failed to add security info: {}", securityInfo, e);
139 140 }
... ...
... ... @@ -65,7 +65,7 @@ import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE
65 65 import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256;
66 66 import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8;
67 67 import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig;
68   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE;
  68 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_UPDATE_COAP_RECOURSE;
69 69
70 70 @Slf4j
71 71 @Component
... ...
... ... @@ -23,7 +23,10 @@ import org.jetbrains.annotations.NotNull;
23 23 import org.thingsboard.server.common.data.Device;
24 24 import org.thingsboard.server.common.data.DeviceProfile;
25 25 import org.thingsboard.server.common.data.ResourceType;
  26 +import org.thingsboard.server.common.data.rpc.RpcStatus;
26 27 import org.thingsboard.server.common.transport.SessionMsgListener;
  28 +import org.thingsboard.server.common.transport.TransportService;
  29 +import org.thingsboard.server.common.transport.TransportServiceCallback;
27 30 import org.thingsboard.server.gen.transport.TransportProtos;
28 31 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
29 32 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
... ... @@ -45,6 +48,7 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
45 48 private final LwM2MAttributesService attributesService;
46 49 private final LwM2MRpcRequestHandler rpcHandler;
47 50 private final TransportProtos.SessionInfoProto sessionInfo;
  51 + private final TransportService transportService;
48 52
49 53 @Override
50 54 public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) {
... ... @@ -78,7 +82,22 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
78 82
79 83 @Override
80 84 public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) {
81   - this.rpcHandler.onToDeviceRpcRequest(toDeviceRequest,this.sessionInfo);
  85 + this.rpcHandler.onToDeviceRpcRequest(toDeviceRequest, this.sessionInfo);
  86 + if (toDeviceRequest.getPersisted()) {
  87 + RpcStatus status;
  88 + if (toDeviceRequest.getOneway()) {
  89 + status = RpcStatus.SUCCESSFUL;
  90 + } else {
  91 + status = RpcStatus.DELIVERED;
  92 + }
  93 + TransportProtos.ToDevicePersistedRpcResponseMsg responseMsg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder()
  94 + .setRequestId(toDeviceRequest.getRequestId())
  95 + .setRequestIdLSB(toDeviceRequest.getRequestIdLSB())
  96 + .setRequestIdMSB(toDeviceRequest.getRequestIdMSB())
  97 + .setStatus(status.name())
  98 + .build();
  99 + transportService.process(sessionInfo, responseMsg, TransportServiceCallback.EMPTY);
  100 + }
82 101 }
83 102
84 103 @Override
... ...
... ... @@ -25,16 +25,14 @@ 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 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;
30 28
31 29 import java.util.UUID;
32 30 import java.util.concurrent.ConcurrentHashMap;
33 31 import java.util.concurrent.ConcurrentMap;
34 32 import java.util.concurrent.atomic.AtomicInteger;
35 33
36   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE;
37   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SOFTWARE_UPDATE_COAP_RECOURSE;
  34 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_UPDATE_COAP_RECOURSE;
  35 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SOFTWARE_UPDATE_COAP_RECOURSE;
38 36
39 37 @Slf4j
40 38 public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource {
... ... @@ -143,7 +141,7 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource {
143 141 response.setPayload(fwData);
144 142 if (exchange.getRequestOptions().getBlock2() != null) {
145 143 int chunkSize = exchange.getRequestOptions().getBlock2().getSzx();
146   - boolean lastFlag = fwData.length > chunkSize;
  144 + boolean lastFlag = fwData.length <= chunkSize;
147 145 response.getOptions().setBlock2(chunkSize, lastFlag, 0);
148 146 log.warn("92) with blokc2 Send currentId: [{}], length: [{}], chunkSize [{}], moreFlag [{}]", currentId.toString(), fwData.length, chunkSize, lastFlag);
149 147 }
... ...
... ... @@ -43,13 +43,13 @@ import org.thingsboard.server.common.data.DeviceTransportType;
43 43 import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration;
44 44 import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
45 45 import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
46   -import org.thingsboard.server.common.data.ota.OtaPackageKey;
47   -import org.thingsboard.server.common.data.ota.OtaPackageType;
48   -import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
49   -import org.thingsboard.server.common.data.ota.OtaPackageUtil;
50 46 import org.thingsboard.server.common.transport.TransportServiceCallback;
51 47 import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
52 48 import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue;
  49 +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult;
  50 +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState;
  51 +import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult;
  52 +import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState;
53 53 import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler;
54 54
55 55 import java.util.ArrayList;
... ... @@ -77,277 +77,20 @@ import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING;
77 77 import static org.eclipse.leshan.core.model.ResourceModel.Type.TIME;
78 78 import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
79 79 import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
80   -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED;
81   -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING;
82   -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED;
83   -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED;
84   -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING;
85   -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED;
  80 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID;
  81 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID;
  82 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID;
  83 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_UPDATE_STATE_ID;
86 84
87 85 @Slf4j
88 86 public class LwM2mTransportUtil {
89 87
90   - public static final String EVENT_AWAKE = "AWAKE";
91   - public static final String RESPONSE_REQUEST_CHANNEL = "RESP_REQ";
92   - public static final String RESPONSE_CHANNEL = "RESP";
93   - public static final String OBSERVE_CHANNEL = "OBSERVE";
94   -
95   - public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0";
96   - public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
97   - public static final String BOOTSTRAP = "bootstrap";
98   - public static final String SERVERS = "servers";
99   - public static final String LWM2M_SERVER = "lwm2mServer";
100   - public static final String BOOTSTRAP_SERVER = "bootstrapServer";
101   - public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr";
102   - public static final String ATTRIBUTE = "attribute";
103   - public static final String TELEMETRY = "telemetry";
104   - public static final String KEY_NAME = "keyName";
105   - public static final String OBSERVE_LWM2M = "observe";
106   - public static final String ATTRIBUTE_LWM2M = "attributeLwm2m";
107   -
108   - private static final String REQUEST = "/request";
109   - private static final String ATTRIBUTES = "/" + ATTRIBUTE;
110   - public static final String TELEMETRIES = "/" + TELEMETRY;
111   - public static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST;
112   - public static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/";
113   -
114   - public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms
  88 + public static final String LWM2M_VERSION_DEFAULT = "1.0";
115 89
116 90 public static final String LOG_LWM2M_TELEMETRY = "logLwm2m";
117 91 public static final String LOG_LWM2M_INFO = "info";
118 92 public static final String LOG_LWM2M_ERROR = "error";
119 93 public static final String LOG_LWM2M_WARN = "warn";
120   - public static final String LOG_LWM2M_VALUE = "value";
121   -
122   - public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized";
123   - public static final String LWM2M_VERSION_DEFAULT = "1.0";
124   -
125   - // Firmware
126   - public static final String FIRMWARE_UPDATE_COAP_RECOURSE = "tbfw";
127   - public static final String FW_UPDATE = "Firmware update";
128   - public static final Integer FW_5_ID = 5;
129   - public static final Integer FW_19_ID = 19;
130   -
131   - // Package W
132   - public static final String FW_PACKAGE_5_ID = "/5/0/0";
133   - public static final String FW_PACKAGE_19_ID = "/19/0/0";
134   - // Package URI
135   - public static final String FW_PACKAGE_URI_ID = "/5/0/1";
136   - // State R
137   - public static final String FW_STATE_ID = "/5/0/3";
138   - // Update Result R
139   - public static final String FW_RESULT_ID = "/5/0/5";
140   -
141   - public static final String FW_DELIVERY_METHOD = "/5/0/9";
142   -
143   - // PkgName R
144   - public static final String FW_NAME_ID = "/5/0/6";
145   - // PkgVersion R
146   - public static final String FW_5_VER_ID = "/5/0/7";
147   -
148   - /**
149   - * Quectel@Hi15RM1-HLB_V1.0@BC68JAR01A10,V150R100C20B300SP7,V150R100C20B300SP7@8
150   - * BC68JAR01A10
151   - * # Request prodct type number
152   - * ATI
153   - * Quectel
154   - * BC68
155   - * Revision:BC68JAR01A10
156   - */
157   - public static final String FW_3_VER_ID = "/3/0/3";
158   - // Update E
159   - public static final String FW_UPDATE_ID = "/5/0/2";
160   -
161   - // Software
162   - public static final String SOFTWARE_UPDATE_COAP_RECOURSE = "softwareUpdateCoapRecourse";
163   - public static final String SW_UPDATE = "Software update";
164   - public static final Integer SW_ID = 9;
165   - // Package W
166   - public static final String SW_PACKAGE_ID = "/9/0/2";
167   - // Package URI
168   - public static final String SW_PACKAGE_URI_ID = "/9/0/3";
169   - // Update State R
170   - public static final String SW_UPDATE_STATE_ID = "/9/0/7";
171   - // Update Result R
172   - public static final String SW_RESULT_ID = "/9/0/9";
173   - // PkgName R
174   - public static final String SW_NAME_ID = "/9/0/0";
175   - // PkgVersion R
176   - public static final String SW_VER_ID = "/9/0/1";
177   - // Install E
178   - public static final String SW_INSTALL_ID = "/9/0/4";
179   - // Uninstall E
180   - public static final String SW_UN_INSTALL_ID = "/9/0/6";
181   -
182   - public enum LwM2mTypeServer {
183   - BOOTSTRAP(0, "bootstrap"),
184   - CLIENT(1, "client");
185   -
186   - public int code;
187   - public String type;
188   -
189   - LwM2mTypeServer(int code, String type) {
190   - this.code = code;
191   - this.type = type;
192   - }
193   -
194   - public static LwM2mTypeServer fromLwM2mTypeServer(String type) {
195   - for (LwM2mTypeServer sm : LwM2mTypeServer.values()) {
196   - if (sm.type.equals(type)) {
197   - return sm;
198   - }
199   - }
200   - throw new IllegalArgumentException(String.format("Unsupported typeServer type : %d", type));
201   - }
202   - }
203   -
204   - public static Optional<OtaPackageUpdateStatus> toOtaPackageUpdateStatus(UpdateResultFw updateResultFw) {
205   - switch (updateResultFw) {
206   - case INITIAL:
207   - return Optional.empty();
208   - case UPDATE_SUCCESSFULLY:
209   - return Optional.of(UPDATED);
210   - case NOT_ENOUGH:
211   - case OUT_OFF_MEMORY:
212   - case CONNECTION_LOST:
213   - case INTEGRITY_CHECK_FAILURE:
214   - case UNSUPPORTED_TYPE:
215   - case INVALID_URI:
216   - case UPDATE_FAILED:
217   - case UNSUPPORTED_PROTOCOL:
218   - return Optional.of(FAILED);
219   - default:
220   - throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", updateResultFw.name());
221   - }
222   - }
223   -
224   - public static Optional<OtaPackageUpdateStatus> toOtaPackageUpdateStatus(UpdateStateFw updateStateFw) {
225   - switch (updateStateFw) {
226   - case IDLE:
227   - return Optional.empty();
228   - case DOWNLOADING:
229   - return Optional.of(DOWNLOADING);
230   - case DOWNLOADED:
231   - return Optional.of(DOWNLOADED);
232   - case UPDATING:
233   - return Optional.of(UPDATING);
234   - default:
235   - throw new CodecException("Invalid value stateFw %d for FirmwareUpdateStatus.", updateStateFw);
236   - }
237   - }
238   -
239   - /**
240   - * SW Update State R
241   - * 0: INITIAL Before downloading. (see 5.1.2.1)
242   - * 1: DOWNLOAD STARTED The downloading process has started and is on-going. (see 5.1.2.2)
243   - * 2: DOWNLOADED The package has been completely downloaded (see 5.1.2.3)
244   - * 3: DELIVERED In that state, the package has been correctly downloaded and is ready to be installed. (see 5.1.2.4)
245   - * If executing the Install Resource failed, the state remains at DELIVERED.
246   - * If executing the Install Resource was successful, the state changes from DELIVERED to INSTALLED.
247   - * After executing the UnInstall Resource, the state changes to INITIAL.
248   - * 4: INSTALLED
249   - */
250   - public enum UpdateStateSw {
251   - INITIAL(0, "Initial"),
252   - DOWNLOAD_STARTED(1, "DownloadStarted"),
253   - DOWNLOADED(2, "Downloaded"),
254   - DELIVERED(3, "Delivered"),
255   - INSTALLED(4, "Installed");
256   -
257   - public int code;
258   - public String type;
259   -
260   - UpdateStateSw(int code, String type) {
261   - this.code = code;
262   - this.type = type;
263   - }
264   -
265   - public static UpdateStateSw fromUpdateStateSwByType(String type) {
266   - for (UpdateStateSw to : UpdateStateSw.values()) {
267   - if (to.type.equals(type)) {
268   - return to;
269   - }
270   - }
271   - throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", type));
272   - }
273   -
274   - public static UpdateStateSw fromUpdateStateSwByCode(int code) {
275   - for (UpdateStateSw to : UpdateStateSw.values()) {
276   - if (to.code == code) {
277   - return to;
278   - }
279   - }
280   - throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", code));
281   - }
282   - }
283   -
284   - /**
285   - * SW Update Result
286   - * Contains the result of downloading or installing/uninstalling the software
287   - * 0: Initial value.
288   - * - Prior to download any new package in the Device, Update Result MUST be reset to this initial value.
289   - * - One side effect of executing the Uninstall resource is to reset Update Result to this initial value "0".
290   - * 1: Downloading.
291   - * - The package downloading process is on-going.
292   - * 2: Software successfully installed.
293   - * 3: Successfully Downloaded and package integrity verified
294   - * (( 4-49, for expansion, of other scenarios))
295   - * ** Failed
296   - * 50: Not enough storage for the new software package.
297   - * 51: Out of memory during downloading process.
298   - * 52: Connection lost during downloading process.
299   - * 53: Package integrity check failure.
300   - * 54: Unsupported package type.
301   - * 56: Invalid URI
302   - * 57: Device defined update error
303   - * 58: Software installation failure
304   - * 59: Uninstallation Failure during forUpdate(arg=0)
305   - * 60-200 : (for expansion, selection to be in blocks depending on new introduction of features)
306   - * This Resource MAY be reported by sending Observe operation.
307   - */
308   - public enum UpdateResultSw {
309   - INITIAL(0, "Initial value", false),
310   - DOWNLOADING(1, "Downloading", false),
311   - SUCCESSFULLY_INSTALLED(2, "Software successfully installed", false),
312   - SUCCESSFULLY_DOWNLOADED_VERIFIED(3, "Successfully Downloaded and package integrity verified", false),
313   - NOT_ENOUGH_STORAGE(50, "Not enough storage for the new software package", true),
314   - OUT_OFF_MEMORY(51, "Out of memory during downloading process", true),
315   - CONNECTION_LOST(52, "Connection lost during downloading process", false),
316   - PACKAGE_CHECK_FAILURE(53, "Package integrity check failure.", false),
317   - UNSUPPORTED_PACKAGE_TYPE(54, "Unsupported package type", false),
318   - INVALID_URI(56, "Invalid URI", true),
319   - UPDATE_ERROR(57, "Device defined update error", true),
320   - INSTALL_FAILURE(58, "Software installation failure", true),
321   - UN_INSTALL_FAILURE(59, "Uninstallation Failure during forUpdate(arg=0)", true);
322   -
323   - public int code;
324   - public String type;
325   - public boolean isAgain;
326   -
327   - UpdateResultSw(int code, String type, boolean isAgain) {
328   - this.code = code;
329   - this.type = type;
330   - this.isAgain = isAgain;
331   - }
332   -
333   - public static UpdateResultSw fromUpdateResultSwByType(String type) {
334   - for (UpdateResultSw to : UpdateResultSw.values()) {
335   - if (to.type.equals(type)) {
336   - return to;
337   - }
338   - }
339   - throw new IllegalArgumentException(String.format("Unsupported SW Update Result type : %s", type));
340   - }
341   -
342   - public static UpdateResultSw fromUpdateResultSwByCode(int code) {
343   - for (UpdateResultSw to : UpdateResultSw.values()) {
344   - if (to.code == code) {
345   - return to;
346   - }
347   - }
348   - throw new IllegalArgumentException(String.format("Unsupported SW Update Result code : %s", code));
349   - }
350   - }
351 94
352 95 public enum LwM2MClientStrategy {
353 96 CLIENT_STRATEGY_1(1, "Read only resources marked as observation"),
... ... @@ -380,45 +123,7 @@ public class LwM2mTransportUtil {
380 123 }
381 124 }
382 125
383   - /**
384   - * FirmwareUpdateStatus {
385   - * DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED
386   - */
387   - public static OtaPackageUpdateStatus EqualsSwSateToFirmwareUpdateStatus(UpdateStateSw updateStateSw, UpdateResultSw updateResultSw) {
388   - switch (updateResultSw) {
389   - case INITIAL:
390   - switch (updateStateSw) {
391   - case INITIAL:
392   - case DOWNLOAD_STARTED:
393   - return DOWNLOADING;
394   - case DOWNLOADED:
395   - return DOWNLOADED;
396   - case DELIVERED:
397   - return VERIFIED;
398   - }
399   - case DOWNLOADING:
400   - return DOWNLOADING;
401   - case SUCCESSFULLY_INSTALLED:
402   - return UPDATED;
403   - case SUCCESSFULLY_DOWNLOADED_VERIFIED:
404   - return VERIFIED;
405   - case NOT_ENOUGH_STORAGE:
406   - case OUT_OFF_MEMORY:
407   - case CONNECTION_LOST:
408   - case PACKAGE_CHECK_FAILURE:
409   - case UNSUPPORTED_PACKAGE_TYPE:
410   - case INVALID_URI:
411   - case UPDATE_ERROR:
412   - case INSTALL_FAILURE:
413   - case UN_INSTALL_FAILURE:
414   - return FAILED;
415   - default:
416   - throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", updateStateSw.name(), updateResultSw.name());
417   - }
418   - }
419   -
420   -
421   - public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath
  126 + public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath
422 127 resourcePath) throws CodecException {
423 128 switch (type) {
424 129 case BOOLEAN:
... ... @@ -443,19 +148,19 @@ public class LwM2mTransportUtil {
443 148 if (path != null) {
444 149 if (FW_STATE_ID.equals(path)) {
445 150 lwM2mOtaConvert.setCurrentType(STRING);
446   - lwM2mOtaConvert.setValue(UpdateStateFw.fromStateFwByCode(((Long) value).intValue()).type);
  151 + lwM2mOtaConvert.setValue(FirmwareUpdateState.fromStateFwByCode(((Long) value).intValue()).type);
447 152 return lwM2mOtaConvert;
448 153 } else if (FW_RESULT_ID.equals(path)) {
449 154 lwM2mOtaConvert.setCurrentType(STRING);
450   - lwM2mOtaConvert.setValue(UpdateResultFw.fromUpdateResultFwByCode(((Long) value).intValue()).getType());
  155 + lwM2mOtaConvert.setValue(FirmwareUpdateResult.fromUpdateResultFwByCode(((Long) value).intValue()).getType());
451 156 return lwM2mOtaConvert;
452 157 } else if (SW_UPDATE_STATE_ID.equals(path)) {
453 158 lwM2mOtaConvert.setCurrentType(STRING);
454   - lwM2mOtaConvert.setValue(UpdateStateSw.fromUpdateStateSwByCode(((Long) value).intValue()).type);
  159 + lwM2mOtaConvert.setValue(SoftwareUpdateState.fromUpdateStateSwByCode(((Long) value).intValue()).type);
455 160 return lwM2mOtaConvert;
456 161 } else if (SW_RESULT_ID.equals(path)) {
457 162 lwM2mOtaConvert.setCurrentType(STRING);
458   - lwM2mOtaConvert.setValue(UpdateResultSw.fromUpdateResultSwByCode(((Long) value).intValue()).type);
  163 + lwM2mOtaConvert.setValue(SoftwareUpdateResult.fromUpdateResultSwByCode(((Long) value).intValue()).type);
459 164 return lwM2mOtaConvert;
460 165 }
461 166 }
... ... @@ -477,18 +182,6 @@ public class LwM2mTransportUtil {
477 182 return null;
478 183 }
479 184
480   -// public static LwM2mClientProfile getNewProfileParameters(JsonObject profilesConfigData, TenantId tenantId) {
481   -// LwM2mClientProfile lwM2MClientProfile = new LwM2mClientProfile();
482   -// lwM2MClientProfile.setTenantId(tenantId);
483   -// lwM2MClientProfile.setPostClientLwM2mSettings(profilesConfigData.get(CLIENT_LWM2M_SETTINGS).getAsJsonObject());
484   -// lwM2MClientProfile.setPostKeyNameProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEY_NAME).getAsJsonObject());
485   -// lwM2MClientProfile.setPostAttributeProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).getAsJsonArray());
486   -// lwM2MClientProfile.setPostTelemetryProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).getAsJsonArray());
487   -// lwM2MClientProfile.setPostObserveProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE_LWM2M).getAsJsonArray());
488   -// lwM2MClientProfile.setPostAttributeLwm2mProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).getAsJsonObject());
489   -// return lwM2MClientProfile;
490   -// }
491   -
492 185 public static Lwm2mDeviceProfileTransportConfiguration toLwM2MClientProfile(DeviceProfile deviceProfile) {
493 186 DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
494 187 if (transportConfiguration.getType().equals(DeviceTransportType.LWM2M)) {
... ... @@ -603,7 +296,6 @@ public class LwM2mTransportUtil {
603 296 if (keyArray.length > 1 && keyArray[1].split(LWM2M_SEPARATOR_KEY).length == 2) {
604 297 return pathIdVer;
605 298 } else {
606   - LwM2mPath pathObjId = new LwM2mPath(pathIdVer);
607 299 return convertObjectIdToVersionedId(pathIdVer, registration);
608 300 }
609 301 }
... ... @@ -743,19 +435,6 @@ public class LwM2mTransportUtil {
743 435 }
744 436 }
745 437
746   - public static boolean isFwSwWords(String pathName) {
747   - return OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName)
748   - || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE).equals(pathName)
749   - || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM).equals(pathName)
750   - || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName)
751   - || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.SIZE).equals(pathName)
752   - || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION).equals(pathName)
753   - || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE).equals(pathName)
754   - || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM).equals(pathName)
755   - || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName)
756   - || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.SIZE).equals(pathName);
757   - }
758   -
759 438 /**
760 439 * @param lwM2MClient -
761 440 * @param path -
... ...
... ... @@ -33,12 +33,10 @@ import org.eclipse.leshan.server.security.SecurityInfo;
33 33 import org.thingsboard.server.common.data.Device;
34 34 import org.thingsboard.server.common.data.DeviceProfile;
35 35 import org.thingsboard.server.common.data.id.TenantId;
36   -import org.thingsboard.server.common.data.ota.OtaPackageType;
37 36 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
38 37 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
39 38 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
40 39 import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest;
41   -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
42 40
43 41 import java.util.Collection;
44 42 import java.util.Map;
... ... @@ -53,7 +51,7 @@ import java.util.concurrent.locks.ReentrantLock;
53 51 import java.util.stream.Collectors;
54 52
55 53 import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
56   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.TRANSPORT_DEFAULT_LWM2M_VERSION;
  54 +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_VERSION_DEFAULT;
57 55 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId;
58 56 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName;
59 57 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
... ... @@ -307,7 +305,7 @@ public class LwM2mClient implements Cloneable {
307 305 LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path));
308 306 String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());
309 307 String verRez = getVerFromPathIdVerOrId(path);
310   - return verRez == null ? TRANSPORT_DEFAULT_LWM2M_VERSION.equals(verSupportedObject) : verRez.equals(verSupportedObject);
  308 + return verRez == null ? LWM2M_VERSION_DEFAULT.equals(verSupportedObject) : verRez.equals(verSupportedObject);
311 309 }
312 310
313 311 /**
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client;
17 17
18 18 import lombok.RequiredArgsConstructor;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.SecurityMode;
20 21 import org.eclipse.leshan.core.model.ResourceModel;
21 22 import org.eclipse.leshan.core.node.LwM2mPath;
22 23 import org.eclipse.leshan.server.registration.Registration;
... ... @@ -30,7 +31,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
30 31 import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
31 32 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
32 33 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
33   -import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore;
  34 +import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore;
34 35
35 36 import java.util.Arrays;
36 37 import java.util.Collection;
... ... @@ -54,7 +55,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
54 55
55 56 private final LwM2mTransportContext context;
56 57 private final LwM2MTransportServerConfig config;
57   - private final TbEditableSecurityStore securityStore;
  58 + private final TbMainSecurityStore securityStore;
58 59 private final Map<String, LwM2mClient> lwM2mClientsByEndpoint = new ConcurrentHashMap<>();
59 60 private final Map<String, LwM2mClient> lwM2mClientsByRegistrationId = new ConcurrentHashMap<>();
60 61 private final Map<UUID, Lwm2mDeviceProfileTransportConfiguration> profiles = new ConcurrentHashMap<>();
... ... @@ -75,6 +76,9 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
75 76 oldSession = lwM2MClient.getSession();
76 77 TbLwM2MSecurityInfo securityInfo = securityStore.getTbLwM2MSecurityInfoByEndpoint(lwM2MClient.getEndpoint());
77 78 if (securityInfo.getSecurityMode() != null) {
  79 + if (SecurityMode.X509.equals(securityInfo.getSecurityMode())) {
  80 + securityStore.registerX509(registration.getEndpoint(), registration.getId());
  81 + }
78 82 if (securityInfo.getDeviceProfile() != null) {
79 83 profileUpdate(securityInfo.getDeviceProfile());
80 84 if (securityInfo.getSecurityInfo() != null) {
... ... @@ -124,7 +128,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
124 128 if (currentRegistration.getId().equals(registration.getId())) {
125 129 lwM2MClient.setState(LwM2MClientState.UNREGISTERED);
126 130 lwM2mClientsByEndpoint.remove(lwM2MClient.getEndpoint());
127   - this.securityStore.remove(lwM2MClient.getEndpoint());
  131 + this.securityStore.remove(lwM2MClient.getEndpoint(), registration.getId());
128 132 UUID profileId = lwM2MClient.getProfileId();
129 133 if (profileId != null) {
130 134 Optional<LwM2mClient> otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst();
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.ota;
17 17
18 18 import lombok.RequiredArgsConstructor;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.node.codec.CodecException;
20 21 import org.eclipse.leshan.core.request.ContentFormat;
21 22 import org.springframework.beans.factory.annotation.Autowired;
22 23 import org.springframework.context.annotation.Lazy;
... ... @@ -24,6 +25,7 @@ import org.springframework.stereotype.Service;
24 25 import org.thingsboard.common.util.DonAsynchron;
25 26 import org.thingsboard.server.cache.ota.OtaPackageDataCache;
26 27 import org.thingsboard.server.common.data.StringUtils;
  28 +import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration;
27 29 import org.thingsboard.server.common.data.ota.OtaPackageKey;
28 30 import org.thingsboard.server.common.data.ota.OtaPackageType;
29 31 import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
... ... @@ -32,11 +34,7 @@ import org.thingsboard.server.common.transport.TransportServiceCallback;
32 34 import org.thingsboard.server.gen.transport.TransportProtos;
33 35 import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
34 36 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
35   -import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy;
36 37 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
37   -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
38   -import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw;
39   -import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw;
40 38 import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService;
41 39 import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
42 40 import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
... ... @@ -47,6 +45,13 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequ
47 45 import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest;
48 46 import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback;
49 47 import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
  48 +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy;
  49 +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareDeliveryMethod;
  50 +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult;
  51 +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState;
  52 +import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy;
  53 +import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult;
  54 +import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState;
50 55 import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
51 56
52 57 import javax.annotation.PostConstruct;
... ... @@ -59,8 +64,13 @@ import java.util.UUID;
59 64 import java.util.concurrent.ConcurrentHashMap;
60 65
61 66 import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
  67 +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED;
  68 +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING;
  69 +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED;
  70 +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED;
  71 +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING;
  72 +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED;
62 73 import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey;
63   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE;
64 74 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY;
65 75 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId;
66 76
... ... @@ -77,13 +87,30 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
77 87 public static final String SOFTWARE_TITLE = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE);
78 88 public static final String SOFTWARE_URL = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.URL);
79 89
  90 + public static final String FIRMWARE_UPDATE_COAP_RECOURSE = "tbfw";
  91 + public static final String SOFTWARE_UPDATE_COAP_RECOURSE = "tbsw";
80 92 private static final String FW_PACKAGE_5_ID = "/5/0/0";
81 93 private static final String FW_URL_ID = "/5/0/1";
82 94 private static final String FW_EXECUTE_ID = "/5/0/2";
83   - private static final String FW_NAME_ID = "/5/0/6";
84   - private static final String FW_VER_ID = "/5/0/7";
  95 + public static final String FW_STATE_ID = "/5/0/3";
  96 + public static final String FW_RESULT_ID = "/5/0/5";
  97 + public static final String FW_NAME_ID = "/5/0/6";
  98 + public static final String FW_5_VER_ID = "/5/0/7";
  99 + /**
  100 + * Quectel@Hi15RM1-HLB_V1.0@BC68JAR01A10,V150R100C20B300SP7,V150R100C20B300SP7@8
  101 + * Revision:BC68JAR01A10
  102 + */
  103 + public static final String FW_3_VER_ID = "/3/0/3";
  104 + public static final String FW_DELIVERY_METHOD = "/5/0/9";
  105 +
85 106 private static final String SW_NAME_ID = "/9/0/0";
86 107 private static final String SW_VER_ID = "/9/0/1";
  108 + public static final String SW_PACKAGE_ID = "/9/0/2";
  109 + public static final String SW_PACKAGE_URI_ID = "/9/0/3";
  110 + public static final String SW_INSTALL_ID = "/9/0/4";
  111 + public static final String SW_UPDATE_STATE_ID = "/9/0/7";
  112 + public static final String SW_RESULT_ID = "/9/0/9";
  113 + public static final String SW_UN_INSTALL_ID = "/9/0/6";
87 114
88 115 private final Map<String, LwM2MClientOtaInfo> fwStates = new ConcurrentHashMap<>();
89 116 private final Map<String, LwM2MClientOtaInfo> swStates = new ConcurrentHashMap<>();
... ... @@ -175,6 +202,24 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
175 202 }
176 203
177 204 @Override
  205 + public void onCurrentFirmwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) {
  206 + log.debug("[{}] Current fw strategy: {}", client.getEndpoint(), configuration.getFwUpdateStrategy());
  207 + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
  208 + fwInfo.setFwStrategy(LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(configuration.getFwUpdateStrategy()));
  209 + fwInfo.setBaseUrl(configuration.getFwUpdateRecourse());
  210 + startFirmwareUpdateIfNeeded(client, fwInfo);
  211 + }
  212 +
  213 + @Override
  214 + public void onCurrentSoftwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) {
  215 + log.debug("[{}] Current sw strategy: {}", client.getEndpoint(), configuration.getSwUpdateStrategy());
  216 + LwM2MClientOtaInfo swInfo = getOrInitSwInfo(client);
  217 + swInfo.setSwStrategy(LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(configuration.getSwUpdateStrategy()));
  218 + swInfo.setBaseUrl(configuration.getSwUpdateRecourse());
  219 + startSoftwareUpdateIfNeeded(client, swInfo);
  220 + }
  221 +
  222 + @Override
178 223 public void onCurrentFirmwareVersion3Update(LwM2mClient client, String version) {
179 224 log.debug("[{}] Current fw version: {}", client.getEndpoint(), version);
180 225 LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
... ... @@ -192,12 +237,12 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
192 237 public void onCurrentFirmwareStateUpdate(LwM2mClient client, Long stateCode) {
193 238 log.debug("[{}] Current fw state: {}", client.getEndpoint(), stateCode);
194 239 LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
195   - UpdateStateFw state = UpdateStateFw.fromStateFwByCode(stateCode.intValue());
196   - if (UpdateStateFw.DOWNLOADED.equals(state)) {
  240 + FirmwareUpdateState state = FirmwareUpdateState.fromStateFwByCode(stateCode.intValue());
  241 + if (FirmwareUpdateState.DOWNLOADED.equals(state)) {
197 242 executeFwUpdate(client);
198 243 }
199 244 fwInfo.setUpdateState(state);
200   - Optional<OtaPackageUpdateStatus> status = LwM2mTransportUtil.toOtaPackageUpdateStatus(state);
  245 + Optional<OtaPackageUpdateStatus> status = this.toOtaPackageUpdateStatus(state);
201 246 status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo,
202 247 otaStatus, "Firmware Update State: " + state.name()));
203 248 }
... ... @@ -206,8 +251,8 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
206 251 public void onCurrentFirmwareResultUpdate(LwM2mClient client, Long code) {
207 252 log.debug("[{}] Current fw result: {}", client.getEndpoint(), code);
208 253 LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
209   - UpdateResultFw result = UpdateResultFw.fromUpdateResultFwByCode(code.intValue());
210   - Optional<OtaPackageUpdateStatus> status = LwM2mTransportUtil.toOtaPackageUpdateStatus(result);
  254 + FirmwareUpdateResult result = FirmwareUpdateResult.fromUpdateResultFwByCode(code.intValue());
  255 + Optional<OtaPackageUpdateStatus> status = this.toOtaPackageUpdateStatus(result);
211 256 status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo,
212 257 otaStatus, "Firmware Update Result: " + result.name()));
213 258 if (result.isAgain() && fwInfo.getRetryAttempts() <= 2) {
... ... @@ -250,6 +295,10 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
250 295 }
251 296 }
252 297
  298 + private void startSoftwareUpdateIfNeeded(LwM2mClient client, LwM2MClientOtaInfo swInfo) {
  299 +
  300 + }
  301 +
253 302 private void startFirmwareUpdateUsingUrl(LwM2mClient client, String url) {
254 303 String targetIdVer = convertObjectIdToVersionedId(FW_URL_ID, client.getRegistration());
255 304 TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(url).timeout(config.getTimeout()).build();
... ... @@ -276,10 +325,10 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
276 325 if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) {
277 326 UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB());
278 327 LwM2MFirmwareUpdateStrategy strategy;
279   - if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == 2) {
280   - strategy = fwInfo.getStrategy();
  328 + if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == FirmwareDeliveryMethod.BOTH.code) {
  329 + strategy = fwInfo.getFwStrategy();
281 330 } else {
282   - strategy = fwInfo.getDeliveryMethod() == 0 ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY;
  331 + strategy = fwInfo.getDeliveryMethod() == FirmwareDeliveryMethod.PULL.code ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY;
283 332 }
284 333 switch (strategy) {
285 334 case OBJ_5_BINARY:
... ... @@ -328,9 +377,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
328 377 return Optional.empty();
329 378 }
330 379
331   - private LwM2MClientOtaInfo getOrInitFwInfo(LwM2mClient client) {
  380 + public LwM2MClientOtaInfo getOrInitFwInfo(LwM2mClient client) {
332 381 //TODO: fetch state from the cache or DB.
333   - return fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> {
  382 + return this.fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> {
334 383 var profile = clientContext.getProfile(client.getProfileId());
335 384 return new LwM2MClientOtaInfo(endpoint, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy(),
336 385 profile.getClientLwM2mSettings().getFwUpdateRecourse());
... ... @@ -357,4 +406,76 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
357 406 helper.sendParametersOnThingsboardTelemetry(result, client.getSession());
358 407 }
359 408
  409 + private static Optional<OtaPackageUpdateStatus> toOtaPackageUpdateStatus(FirmwareUpdateResult fwUpdateResult) {
  410 + switch (fwUpdateResult) {
  411 + case INITIAL:
  412 + return Optional.empty();
  413 + case UPDATE_SUCCESSFULLY:
  414 + return Optional.of(UPDATED);
  415 + case NOT_ENOUGH:
  416 + case OUT_OFF_MEMORY:
  417 + case CONNECTION_LOST:
  418 + case INTEGRITY_CHECK_FAILURE:
  419 + case UNSUPPORTED_TYPE:
  420 + case INVALID_URI:
  421 + case UPDATE_FAILED:
  422 + case UNSUPPORTED_PROTOCOL:
  423 + return Optional.of(FAILED);
  424 + default:
  425 + throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", fwUpdateResult.name());
  426 + }
  427 + }
  428 +
  429 + private static Optional<OtaPackageUpdateStatus> toOtaPackageUpdateStatus(FirmwareUpdateState firmwareUpdateState) {
  430 + switch (firmwareUpdateState) {
  431 + case IDLE:
  432 + return Optional.empty();
  433 + case DOWNLOADING:
  434 + return Optional.of(DOWNLOADING);
  435 + case DOWNLOADED:
  436 + return Optional.of(DOWNLOADED);
  437 + case UPDATING:
  438 + return Optional.of(UPDATING);
  439 + default:
  440 + throw new CodecException("Invalid value stateFw %d for FirmwareUpdateStatus.", firmwareUpdateState);
  441 + }
  442 + }
  443 +
  444 + /**
  445 + * FirmwareUpdateStatus {
  446 + * DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED
  447 + */
  448 + public static Optional<OtaPackageUpdateStatus> toSwSateResultUpdateStatus(SoftwareUpdateState softwareUpdateState, SoftwareUpdateResult softwareUpdateResult) {
  449 + switch (softwareUpdateResult) {
  450 + case INITIAL:
  451 + switch (softwareUpdateState) {
  452 + case INITIAL:
  453 + case DOWNLOAD_STARTED:
  454 + return Optional.of(DOWNLOADING);
  455 + case DOWNLOADED:
  456 + return Optional.of(DOWNLOADED);
  457 + case DELIVERED:
  458 + return Optional.of(VERIFIED);
  459 + }
  460 + case DOWNLOADING:
  461 + return Optional.of(DOWNLOADING);
  462 + case SUCCESSFULLY_INSTALLED:
  463 + return Optional.of(UPDATED);
  464 + case SUCCESSFULLY_DOWNLOADED_VERIFIED:
  465 + return Optional.of(VERIFIED);
  466 + case NOT_ENOUGH_STORAGE:
  467 + case OUT_OFF_MEMORY:
  468 + case CONNECTION_LOST:
  469 + case PACKAGE_CHECK_FAILURE:
  470 + case UNSUPPORTED_PACKAGE_TYPE:
  471 + case INVALID_URI:
  472 + case UPDATE_ERROR:
  473 + case INSTALL_FAILURE:
  474 + case UN_INSTALL_FAILURE:
  475 + return Optional.of(FAILED);
  476 + default:
  477 + throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", softwareUpdateState.name(), softwareUpdateResult.name());
  478 + }
  479 + }
  480 +
360 481 }
... ...
... ... @@ -18,9 +18,10 @@ package org.thingsboard.server.transport.lwm2m.server.ota;
18 18 import lombok.Data;
19 19 import org.thingsboard.server.common.data.StringUtils;
20 20 import org.thingsboard.server.common.data.ota.OtaPackageType;
21   -import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy;
22   -import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw;
23   -import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw;
  21 +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy;
  22 +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult;
  23 +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState;
  24 +import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy;
24 25
25 26 import java.util.Optional;
26 27
... ... @@ -44,9 +45,10 @@ public class LwM2MClientOtaInfo {
44 45 private Integer deliveryMethod;
45 46
46 47 //TODO: use value from device if applicable;
47   - private LwM2MFirmwareUpdateStrategy strategy;
48   - private UpdateStateFw updateState;
49   - private UpdateResultFw updateResult;
  48 + private LwM2MFirmwareUpdateStrategy fwStrategy;
  49 + private LwM2MSoftwareUpdateStrategy swStrategy;
  50 + private FirmwareUpdateState updateState;
  51 + private FirmwareUpdateResult updateResult;
50 52
51 53 private String failedPackageId;
52 54 private int retryAttempts;
... ... @@ -54,7 +56,7 @@ public class LwM2MClientOtaInfo {
54 56 public LwM2MClientOtaInfo(String endpoint, OtaPackageType type, Integer strategyCode, String baseUrl) {
55 57 this.endpoint = endpoint;
56 58 this.type = type;
57   - this.strategy = LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(strategyCode);
  59 + this.fwStrategy = LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(strategyCode);
58 60 this.baseUrl = baseUrl;
59 61 }
60 62
... ... @@ -88,7 +90,7 @@ public class LwM2MClientOtaInfo {
88 90 return StringUtils.isNotEmpty(currentName) || StringUtils.isNotEmpty(currentVersion5) || StringUtils.isNotEmpty(currentVersion3);
89 91 }
90 92
91   - public void setUpdateResult(UpdateResultFw updateResult) {
  93 + public void setUpdateResult(FirmwareUpdateResult updateResult) {
92 94 this.updateResult = updateResult;
93 95 switch (updateResult) {
94 96 case INITIAL:
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.transport.lwm2m.server.ota;
17 17
  18 +import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration;
18 19 import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
19 20
20 21 import java.util.Optional;
... ... @@ -31,6 +32,10 @@ public interface LwM2MOtaUpdateService {
31 32
32 33 void onCurrentFirmwareNameUpdate(LwM2mClient client, String name);
33 34
  35 + void onCurrentFirmwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration);
  36 +
  37 + void onCurrentSoftwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration);
  38 +
34 39 void onCurrentFirmwareVersion3Update(LwM2mClient client, String version);
35 40
36 41 void onCurrentFirmwareVersion5Update(LwM2mClient client, String version);
... ...
  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.ota.firmware;
  17 +
  18 +public enum FirmwareDeliveryMethod {
  19 + PULL(0, "Pull only"),
  20 + PUSH(1, "Push only"),
  21 + BOTH(2, "Push or Push");
  22 +
  23 + public int code;
  24 + public String type;
  25 +
  26 + FirmwareDeliveryMethod(int code, String type) {
  27 + this.code = code;
  28 + this.type = type;
  29 + }
  30 +
  31 + public static FirmwareDeliveryMethod fromStateFwByType(String type) {
  32 + for (FirmwareDeliveryMethod to : FirmwareDeliveryMethod.values()) {
  33 + if (to.type.equals(type)) {
  34 + return to;
  35 + }
  36 + }
  37 + throw new IllegalArgumentException(String.format("Unsupported FW delivery type : %s", type));
  38 + }
  39 +
  40 + public static FirmwareDeliveryMethod fromStateFwByCode(int code) {
  41 + for (FirmwareDeliveryMethod to : FirmwareDeliveryMethod.values()) {
  42 + if (to.code == code) {
  43 + return to;
  44 + }
  45 + }
  46 + throw new IllegalArgumentException(String.format("Unsupported FW delivery code : %s", code));
  47 + }
  48 +}
... ...
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateResult.java renamed from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateResultFw.java
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.transport.lwm2m.server;
  16 +package org.thingsboard.server.transport.lwm2m.server.ota.firmware;
17 17
18 18 import lombok.Getter;
19 19
... ... @@ -30,7 +30,7 @@ import lombok.Getter;
30 30 * 8: Firmware update failed.
31 31 * 9: Unsupported protocol.
32 32 */
33   -public enum UpdateResultFw {
  33 +public enum FirmwareUpdateResult {
34 34 INITIAL(0, "Initial value", false),
35 35 UPDATE_SUCCESSFULLY(1, "Firmware updated successfully", false),
36 36 NOT_ENOUGH(2, "Not enough flash memory for the new firmware package", false),
... ... @@ -49,14 +49,14 @@ public enum UpdateResultFw {
49 49 @Getter
50 50 private boolean again;
51 51
52   - UpdateResultFw(int code, String type, boolean isAgain) {
  52 + FirmwareUpdateResult(int code, String type, boolean isAgain) {
53 53 this.code = code;
54 54 this.type = type;
55 55 this.again = isAgain;
56 56 }
57 57
58   - public static UpdateResultFw fromUpdateResultFwByType(String type) {
59   - for (UpdateResultFw to : UpdateResultFw.values()) {
  58 + public static FirmwareUpdateResult fromUpdateResultFwByType(String type) {
  59 + for (FirmwareUpdateResult to : FirmwareUpdateResult.values()) {
60 60 if (to.type.equals(type)) {
61 61 return to;
62 62 }
... ... @@ -64,8 +64,8 @@ public enum UpdateResultFw {
64 64 throw new IllegalArgumentException(String.format("Unsupported FW Update Result type : %s", type));
65 65 }
66 66
67   - public static UpdateResultFw fromUpdateResultFwByCode(int code) {
68   - for (UpdateResultFw to : UpdateResultFw.values()) {
  67 + public static FirmwareUpdateResult fromUpdateResultFwByCode(int code) {
  68 + for (FirmwareUpdateResult to : FirmwareUpdateResult.values()) {
69 69 if (to.code == code) {
70 70 return to;
71 71 }
... ...
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateState.java renamed from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateStateFw.java
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.transport.lwm2m.server;
  16 +package org.thingsboard.server.transport.lwm2m.server.ota.firmware;
17 17
18 18 /**
19 19 * /** State R
... ... @@ -22,7 +22,7 @@ package org.thingsboard.server.transport.lwm2m.server;
22 22 * 2: Downloaded
23 23 * 3: Updating
24 24 */
25   -public enum UpdateStateFw {
  25 +public enum FirmwareUpdateState {
26 26 IDLE(0, "Idle"),
27 27 DOWNLOADING(1, "Downloading"),
28 28 DOWNLOADED(2, "Downloaded"),
... ... @@ -31,13 +31,13 @@ public enum UpdateStateFw {
31 31 public int code;
32 32 public String type;
33 33
34   - UpdateStateFw(int code, String type) {
  34 + FirmwareUpdateState(int code, String type) {
35 35 this.code = code;
36 36 this.type = type;
37 37 }
38 38
39   - public static UpdateStateFw fromStateFwByType(String type) {
40   - for (UpdateStateFw to : UpdateStateFw.values()) {
  39 + public static FirmwareUpdateState fromStateFwByType(String type) {
  40 + for (FirmwareUpdateState to : FirmwareUpdateState.values()) {
41 41 if (to.type.equals(type)) {
42 42 return to;
43 43 }
... ... @@ -45,8 +45,8 @@ public enum UpdateStateFw {
45 45 throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type));
46 46 }
47 47
48   - public static UpdateStateFw fromStateFwByCode(int code) {
49   - for (UpdateStateFw to : UpdateStateFw.values()) {
  48 + public static FirmwareUpdateState fromStateFwByCode(int code) {
  49 + for (FirmwareUpdateState to : FirmwareUpdateState.values()) {
50 50 if (to.code == code) {
51 51 return to;
52 52 }
... ...
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/LwM2MFirmwareUpdateStrategy.java renamed from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MFirmwareUpdateStrategy.java
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.transport.lwm2m.server;
  16 +package org.thingsboard.server.transport.lwm2m.server.ota.firmware;
17 17
18 18 public enum LwM2MFirmwareUpdateStrategy {
19 19 OBJ_5_BINARY(1, "ObjectId 5, Binary"),
... ...
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MSoftwareUpdateStrategy.java renamed from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSoftwareUpdateStrategy.java
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.transport.lwm2m.server;
  16 +package org.thingsboard.server.transport.lwm2m.server.ota.software;
17 17
18 18 public enum LwM2MSoftwareUpdateStrategy {
19 19 BINARY(1, "ObjectId 9, Binary"),
... ...
  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.ota.software;
  17 +
  18 +/**
  19 + * SW Update Result
  20 + * Contains the result of downloading or installing/uninstalling the software
  21 + * 0: Initial value.
  22 + * - Prior to download any new package in the Device, Update Result MUST be reset to this initial value.
  23 + * - One side effect of executing the Uninstall resource is to reset Update Result to this initial value "0".
  24 + * 1: Downloading.
  25 + * - The package downloading process is on-going.
  26 + * 2: Software successfully installed.
  27 + * 3: Successfully Downloaded and package integrity verified
  28 + * (( 4-49, for expansion, of other scenarios))
  29 + * ** Failed
  30 + * 50: Not enough storage for the new software package.
  31 + * 51: Out of memory during downloading process.
  32 + * 52: Connection lost during downloading process.
  33 + * 53: Package integrity check failure.
  34 + * 54: Unsupported package type.
  35 + * 56: Invalid URI
  36 + * 57: Device defined update error
  37 + * 58: Software installation failure
  38 + * 59: Uninstallation Failure during forUpdate(arg=0)
  39 + * 60-200 : (for expansion, selection to be in blocks depending on new introduction of features)
  40 + * This Resource MAY be reported by sending Observe operation.
  41 + */
  42 +public enum SoftwareUpdateResult {
  43 + INITIAL(0, "Initial value", false),
  44 + DOWNLOADING(1, "Downloading", false),
  45 + SUCCESSFULLY_INSTALLED(2, "Software successfully installed", false),
  46 + SUCCESSFULLY_DOWNLOADED_VERIFIED(3, "Successfully Downloaded and package integrity verified", false),
  47 + NOT_ENOUGH_STORAGE(50, "Not enough storage for the new software package", true),
  48 + OUT_OFF_MEMORY(51, "Out of memory during downloading process", true),
  49 + CONNECTION_LOST(52, "Connection lost during downloading process", false),
  50 + PACKAGE_CHECK_FAILURE(53, "Package integrity check failure.", false),
  51 + UNSUPPORTED_PACKAGE_TYPE(54, "Unsupported package type", false),
  52 + INVALID_URI(56, "Invalid URI", true),
  53 + UPDATE_ERROR(57, "Device defined update error", true),
  54 + INSTALL_FAILURE(58, "Software installation failure", true),
  55 + UN_INSTALL_FAILURE(59, "Uninstallation Failure during forUpdate(arg=0)", true);
  56 +
  57 + public int code;
  58 + public String type;
  59 + public boolean isAgain;
  60 +
  61 + SoftwareUpdateResult(int code, String type, boolean isAgain) {
  62 + this.code = code;
  63 + this.type = type;
  64 + this.isAgain = isAgain;
  65 + }
  66 +
  67 + public static SoftwareUpdateResult fromUpdateResultSwByType(String type) {
  68 + for (SoftwareUpdateResult to : SoftwareUpdateResult.values()) {
  69 + if (to.type.equals(type)) {
  70 + return to;
  71 + }
  72 + }
  73 + throw new IllegalArgumentException(String.format("Unsupported SW Update Result type : %s", type));
  74 + }
  75 +
  76 + public static SoftwareUpdateResult fromUpdateResultSwByCode(int code) {
  77 + for (SoftwareUpdateResult to : SoftwareUpdateResult.values()) {
  78 + if (to.code == code) {
  79 + return to;
  80 + }
  81 + }
  82 + throw new IllegalArgumentException(String.format("Unsupported SW Update Result code : %s", code));
  83 + }
  84 +}
... ...
  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.ota.software;
  17 +
  18 +/**
  19 + * SW Update State R
  20 + * 0: INITIAL Before downloading. (see 5.1.2.1)
  21 + * 1: DOWNLOAD STARTED The downloading process has started and is on-going. (see 5.1.2.2)
  22 + * 2: DOWNLOADED The package has been completely downloaded (see 5.1.2.3)
  23 + * 3: DELIVERED In that state, the package has been correctly downloaded and is ready to be installed. (see 5.1.2.4)
  24 + * If executing the Install Resource failed, the state remains at DELIVERED.
  25 + * If executing the Install Resource was successful, the state changes from DELIVERED to INSTALLED.
  26 + * After executing the UnInstall Resource, the state changes to INITIAL.
  27 + * 4: INSTALLED
  28 + */
  29 +public enum SoftwareUpdateState {
  30 + INITIAL(0, "Initial"),
  31 + DOWNLOAD_STARTED(1, "DownloadStarted"),
  32 + DOWNLOADED(2, "Downloaded"),
  33 + DELIVERED(3, "Delivered"),
  34 + INSTALLED(4, "Installed");
  35 +
  36 + public int code;
  37 + public String type;
  38 +
  39 + SoftwareUpdateState(int code, String type) {
  40 + this.code = code;
  41 + this.type = type;
  42 + }
  43 +
  44 + public static SoftwareUpdateState fromUpdateStateSwByType(String type) {
  45 + for (SoftwareUpdateState to : SoftwareUpdateState.values()) {
  46 + if (to.type.equals(type)) {
  47 + return to;
  48 + }
  49 + }
  50 + throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", type));
  51 + }
  52 +
  53 + public static SoftwareUpdateState fromUpdateStateSwByCode(int code) {
  54 + for (SoftwareUpdateState to : SoftwareUpdateState.values()) {
  55 + if (to.code == code) {
  56 + return to;
  57 + }
  58 + }
  59 + throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", code));
  60 + }
  61 +}
  62 +
... ...
... ... @@ -16,26 +16,28 @@
16 16 package org.thingsboard.server.transport.lwm2m.server.store;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19   -import org.eclipse.leshan.server.security.EditableSecurityStore;
20 19 import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
21 20 import org.eclipse.leshan.server.security.SecurityInfo;
22   -import org.eclipse.leshan.server.security.SecurityStore;
23   -import org.eclipse.leshan.server.security.SecurityStoreListener;
24 21 import org.jetbrains.annotations.Nullable;
25   -import org.springframework.stereotype.Component;
26   -import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
27 22 import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
28 23 import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
29   -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
30   -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
31 24
32   -import java.util.Collection;
  25 +import java.util.HashSet;
  26 +import java.util.Map;
  27 +import java.util.Set;
  28 +import java.util.concurrent.ConcurrentHashMap;
  29 +import java.util.concurrent.ConcurrentMap;
  30 +import java.util.concurrent.locks.Lock;
  31 +import java.util.concurrent.locks.ReentrantLock;
  32 +
  33 +import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
33 34
34 35 @Slf4j
35   -public class TbLwM2mSecurityStore implements TbEditableSecurityStore {
  36 +public class TbLwM2mSecurityStore implements TbMainSecurityStore {
36 37
37 38 private final TbEditableSecurityStore securityStore;
38 39 private final LwM2mCredentialsSecurityInfoValidator validator;
  40 + private final ConcurrentMap<String, Set<String>> endpointRegistrations = new ConcurrentHashMap<>();
39 41
40 42 public TbLwM2mSecurityStore(TbEditableSecurityStore securityStore, LwM2mCredentialsSecurityInfoValidator validator) {
41 43 this.securityStore = securityStore;
... ... @@ -67,25 +69,43 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore {
67 69
68 70 @Nullable
69 71 public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) {
70   - TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, LwM2mTransportUtil.LwM2mTypeServer.CLIENT);
71   - try {
72   - if (securityInfo != null) {
  72 + TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, CLIENT);
  73 + doPut(securityInfo);
  74 + return securityInfo != null ? securityInfo.getSecurityInfo() : null;
  75 + }
  76 +
  77 + private void doPut(TbLwM2MSecurityInfo securityInfo) {
  78 + if (securityInfo != null) {
  79 + try {
73 80 securityStore.put(securityInfo);
  81 + } catch (NonUniqueSecurityInfoException e) {
  82 + log.trace("Failed to add security info: {}", securityInfo, e);
74 83 }
75   - } catch (NonUniqueSecurityInfoException e) {
76   - log.trace("Failed to add security info: {}", securityInfo, e);
77 84 }
78   - return securityInfo != null ? securityInfo.getSecurityInfo() : null;
79 85 }
80 86
81 87 @Override
82   - public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException {
83   - securityStore.put(tbSecurityInfo);
  88 + public void putX509(TbLwM2MSecurityInfo securityInfo) throws NonUniqueSecurityInfoException {
  89 + securityStore.put(securityInfo);
84 90 }
85 91
86 92 @Override
87   - public void remove(String endpoint) {
88   - //TODO: Make sure we delay removal of security store from endpoint due to reg/unreg race condition.
89   -// securityStore.remove(endpoint);
  93 + public void registerX509(String endpoint, String registrationId) {
  94 + endpointRegistrations.computeIfAbsent(endpoint, ep -> new HashSet<>()).add(registrationId);
  95 + }
  96 +
  97 + @Override
  98 + public void remove(String endpoint, String registrationId) {
  99 + Set<String> epRegistrationIds = endpointRegistrations.get(endpoint);
  100 + boolean shouldRemove;
  101 + if (epRegistrationIds == null) {
  102 + shouldRemove = true;
  103 + } else {
  104 + epRegistrationIds.remove(registrationId);
  105 + shouldRemove = epRegistrationIds.isEmpty();
  106 + }
  107 + if (shouldRemove) {
  108 + securityStore.remove(endpoint);
  109 + }
90 110 }
91 111 }
... ...
... ... @@ -51,7 +51,7 @@ public class TbLwM2mStoreFactory {
51 51 }
52 52
53 53 @Bean
54   - private TbEditableSecurityStore securityStore() {
  54 + private TbMainSecurityStore securityStore() {
55 55 return new TbLwM2mSecurityStore(redisConfiguration.isPresent() && useRedis ?
56 56 new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new TbInMemorySecurityStore(), validator);
57 57 }
... ...
  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.store;
  17 +
  18 +import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
  19 +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
  20 +
  21 +public interface TbMainSecurityStore extends TbSecurityStore {
  22 +
  23 + void putX509(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException;
  24 +
  25 + void registerX509(String endpoint, String registrationId);
  26 +
  27 + void remove(String endpoint, String registrationId);
  28 +
  29 +}
... ...
... ... @@ -37,11 +37,12 @@ import org.eclipse.leshan.server.registration.Registration;
37 37 import org.springframework.context.annotation.Lazy;
38 38 import org.springframework.stereotype.Service;
39 39 import org.thingsboard.common.util.DonAsynchron;
40   -import org.thingsboard.common.util.ThingsBoardExecutors;
41 40 import org.thingsboard.server.cache.ota.OtaPackageDataCache;
42 41 import org.thingsboard.server.common.data.Device;
43 42 import org.thingsboard.server.common.data.DeviceProfile;
  43 +import org.thingsboard.server.common.data.StringUtils;
44 44 import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes;
  45 +import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration;
45 46 import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration;
46 47 import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
47 48 import org.thingsboard.server.common.data.ota.OtaPackageUtil;
... ... @@ -82,6 +83,8 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttrib
82 83 import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesRequest;
83 84 import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
84 85 import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService;
  86 +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy;
  87 +import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy;
85 88 import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcRequestHandler;
86 89 import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
87 90 import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
... ... @@ -100,23 +103,22 @@ import java.util.Set;
100 103 import java.util.UUID;
101 104 import java.util.concurrent.ConcurrentHashMap;
102 105 import java.util.concurrent.CountDownLatch;
103   -import java.util.concurrent.ExecutorService;
104 106 import java.util.concurrent.TimeUnit;
105 107 import java.util.stream.Collectors;
106 108
107 109 import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
108   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_3_VER_ID;
109   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_VER_ID;
110   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_DELIVERY_METHOD;
111   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID;
112   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID;
113   -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID;
114 110 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR;
115 111 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
116 112 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN;
117 113 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId;
118 114 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString;
119 115 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
  116 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID;
  117 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_5_VER_ID;
  118 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD;
  119 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_NAME_ID;
  120 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID;
  121 +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID;
120 122
121 123
122 124 @Slf4j
... ... @@ -130,16 +132,13 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
130 132 private final LwM2mTransportContext context;
131 133 private final LwM2MAttributesService attributesService;
132 134 private final LwM2MOtaUpdateService otaService;
133   - public final LwM2MTransportServerConfig config;
  135 + private final LwM2MTransportServerConfig config;
134 136 private final LwM2MTelemetryLogService logService;
135   - public final OtaPackageDataCache otaPackageDataCache;
136   - public final LwM2mTransportServerHelper helper;
  137 + private final LwM2mTransportServerHelper helper;
137 138 private final TbLwM2MDtlsSessionStore sessionStore;
138   - public final LwM2mClientContext clientContext;
  139 + private final LwM2mClientContext clientContext;
139 140 private final LwM2MRpcRequestHandler rpcHandler;
140   - public final LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler;
141   -
142   - public final Map<String, Integer> firmwareUpdateState;
  141 + private final LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler;
143 142
144 143 public DefaultLwM2MUplinkMsgHandler(TransportService transportService,
145 144 LwM2MTransportServerConfig config,
... ... @@ -150,7 +149,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
150 149 @Lazy LwM2MAttributesService attributesService,
151 150 @Lazy LwM2MRpcRequestHandler rpcHandler,
152 151 @Lazy LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler,
153   - OtaPackageDataCache otaPackageDataCache,
154 152 LwM2mTransportContext context, TbLwM2MDtlsSessionStore sessionStore) {
155 153 this.transportService = transportService;
156 154 this.attributesService = attributesService;
... ... @@ -161,9 +159,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
161 159 this.logService = logService;
162 160 this.rpcHandler = rpcHandler;
163 161 this.defaultLwM2MDownlinkMsgHandler = defaultLwM2MDownlinkMsgHandler;
164   - this.otaPackageDataCache = otaPackageDataCache;
165 162 this.context = context;
166   - this.firmwareUpdateState = new ConcurrentHashMap<>();
167 163 this.sessionStore = sessionStore;
168 164 }
169 165
... ... @@ -216,7 +212,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
216 212 }
217 213 logService.log(lwM2MClient, LOG_LWM2M_INFO + ": Client registered with registration id: " + registration.getId());
218 214 SessionInfoProto sessionInfo = lwM2MClient.getSession();
219   - transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, attributesService, rpcHandler, sessionInfo));
  215 + transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, attributesService, rpcHandler, sessionInfo, transportService));
220 216 log.warn("40) sessionId [{}] Registering rpc subscription after Registration client", new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()));
221 217 TransportProtos.TransportToDeviceActorMsg msg = TransportProtos.TransportToDeviceActorMsg.newBuilder()
222 218 .setSessionInfo(sessionInfo)
... ... @@ -780,6 +776,21 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
780 776 clients.forEach(client -> sendCancelObserveRequest(targetId, client));
781 777 }
782 778 }
  779 +
  780 + // update value in fwInfo
  781 + OtherConfiguration newLwM2mSettings = newProfile.getClientLwM2mSettings();
  782 + OtherConfiguration oldLwM2mSettings = oldProfile.getClientLwM2mSettings();
  783 + if (!newLwM2mSettings.getFwUpdateStrategy().equals(oldLwM2mSettings.getFwUpdateStrategy())
  784 + || (StringUtils.isNotEmpty(newLwM2mSettings.getFwUpdateRecourse()) &&
  785 + !newLwM2mSettings.getFwUpdateRecourse().equals(oldLwM2mSettings.getFwUpdateRecourse()))) {
  786 + clients.forEach(lwM2MClient -> otaService.onCurrentFirmwareStrategyUpdate(lwM2MClient, newLwM2mSettings));
  787 + }
  788 +
  789 + if (!newLwM2mSettings.getSwUpdateStrategy().equals(oldLwM2mSettings.getSwUpdateStrategy())
  790 + || (StringUtils.isNotEmpty(newLwM2mSettings.getSwUpdateRecourse()) &&
  791 + !newLwM2mSettings.getSwUpdateRecourse().equals(oldLwM2mSettings.getSwUpdateRecourse()))) {
  792 + clients.forEach(lwM2MClient -> otaService.onCurrentSoftwareStrategyUpdate(lwM2MClient, newLwM2mSettings));
  793 + }
783 794 }
784 795 }
785 796
... ... @@ -877,7 +888,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
877 888 */
878 889 private void reportActivityAndRegister(SessionInfoProto sessionInfo) {
879 890 if (sessionInfo != null && transportService.reportActivity(sessionInfo) == null) {
880   - transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, attributesService, rpcHandler, sessionInfo));
  891 + transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, attributesService, rpcHandler, sessionInfo, transportService));
881 892 this.reportActivitySubscription(sessionInfo);
882 893 }
883 894 }
... ...
  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.uplink;
  17 +
  18 +public enum LwM2mTypeServer {
  19 + BOOTSTRAP(0, "bootstrap"),
  20 + CLIENT(1, "client");
  21 +
  22 + public int code;
  23 + public String type;
  24 +
  25 + LwM2mTypeServer(int code, String type) {
  26 + this.code = code;
  27 + this.type = type;
  28 + }
  29 +
  30 + public static LwM2mTypeServer fromLwM2mTypeServer(String type) {
  31 + for (LwM2mTypeServer sm : LwM2mTypeServer.values()) {
  32 + if (sm.type.equals(type)) {
  33 + return sm;
  34 + }
  35 + }
  36 + throw new IllegalArgumentException(String.format("Unsupported typeServer type : %d", type));
  37 + }
  38 +}
  39 +
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import com.google.gson.JsonParseException;
  20 +import io.netty.channel.ChannelFuture;
20 21 import io.netty.channel.ChannelHandlerContext;
21 22 import io.netty.channel.ChannelInboundHandlerAdapter;
22 23 import io.netty.handler.codec.mqtt.MqttConnAckMessage;
... ... @@ -47,8 +48,9 @@ import org.thingsboard.server.common.data.DeviceProfile;
47 48 import org.thingsboard.server.common.data.DeviceTransportType;
48 49 import org.thingsboard.server.common.data.TransportPayloadType;
49 50 import org.thingsboard.server.common.data.device.profile.MqttTopics;
50   -import org.thingsboard.server.common.data.ota.OtaPackageType;
51 51 import org.thingsboard.server.common.data.id.OtaPackageId;
  52 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  53 +import org.thingsboard.server.common.data.rpc.RpcStatus;
52 54 import org.thingsboard.server.common.msg.EncryptionUtil;
53 55 import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
54 56 import org.thingsboard.server.common.transport.SessionMsgListener;
... ... @@ -813,7 +815,31 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
813 815 public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
814 816 log.trace("[{}] Received RPC command to device", sessionId);
815 817 try {
816   - deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, rpcRequest).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  818 + deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, rpcRequest)
  819 + .ifPresent(payload -> {
  820 + ChannelFuture channelFuture = deviceSessionCtx.getChannel().writeAndFlush(payload);
  821 + if (rpcRequest.getPersisted()) {
  822 + channelFuture.addListener(future -> {
  823 + RpcStatus status;
  824 + Throwable t = future.cause();
  825 + if (t != null) {
  826 + log.error("Failed delivering RPC command to device!", t);
  827 + status = RpcStatus.FAILED;
  828 + } else if (rpcRequest.getOneway()) {
  829 + status = RpcStatus.SUCCESSFUL;
  830 + } else {
  831 + status = RpcStatus.DELIVERED;
  832 + }
  833 + TransportProtos.ToDevicePersistedRpcResponseMsg msg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder()
  834 + .setRequestId(rpcRequest.getRequestId())
  835 + .setRequestIdLSB(rpcRequest.getRequestIdLSB())
  836 + .setRequestIdMSB(rpcRequest.getRequestIdMSB())
  837 + .setStatus(status.name())
  838 + .build();
  839 + transportService.process(deviceSessionCtx.getSessionInfo(), msg, TransportServiceCallback.EMPTY);
  840 + });
  841 + }
  842 + });
817 843 } catch (Exception e) {
818 844 log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e);
819 845 }
... ...
... ... @@ -15,9 +15,13 @@
15 15 */
16 16 package org.thingsboard.server.transport.mqtt.session;
17 17
  18 +import io.netty.channel.ChannelFuture;
18 19 import lombok.extern.slf4j.Slf4j;
19 20 import org.thingsboard.server.common.data.DeviceProfile;
  21 +import org.thingsboard.server.common.data.rpc.RpcStatus;
20 22 import org.thingsboard.server.common.transport.SessionMsgListener;
  23 +import org.thingsboard.server.common.transport.TransportService;
  24 +import org.thingsboard.server.common.transport.TransportServiceCallback;
21 25 import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
22 26 import org.thingsboard.server.gen.transport.TransportProtos;
23 27 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
... ... @@ -32,9 +36,11 @@ import java.util.concurrent.ConcurrentMap;
32 36 public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext implements SessionMsgListener {
33 37
34 38 private final GatewaySessionHandler parent;
  39 + private final TransportService transportService;
35 40
36 41 public GatewayDeviceSessionCtx(GatewaySessionHandler parent, TransportDeviceInfo deviceInfo,
37   - DeviceProfile deviceProfile, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap) {
  42 + DeviceProfile deviceProfile, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap,
  43 + TransportService transportService) {
38 44 super(UUID.randomUUID(), mqttQoSMap);
39 45 this.parent = parent;
40 46 setSessionInfo(SessionInfoProto.newBuilder()
... ... @@ -56,6 +62,7 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple
56 62 .build());
57 63 setDeviceInfo(deviceInfo);
58 64 setDeviceProfile(deviceProfile);
  65 + this.transportService = transportService;
59 66 }
60 67
61 68 @Override
... ... @@ -89,7 +96,32 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple
89 96 @Override
90 97 public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg request) {
91 98 try {
92   - parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent(parent::writeAndFlush);
  99 + parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent(
  100 + payload -> {
  101 + ChannelFuture channelFuture = parent.writeAndFlush(payload);
  102 + if (request.getPersisted()) {
  103 + channelFuture.addListener(future -> {
  104 + RpcStatus status;
  105 + Throwable t = future.cause();
  106 + if (t != null) {
  107 + log.error("Failed delivering RPC command to device!", t);
  108 + status = RpcStatus.FAILED;
  109 + } else if (request.getOneway()) {
  110 + status = RpcStatus.SUCCESSFUL;
  111 + } else {
  112 + status = RpcStatus.DELIVERED;
  113 + }
  114 + TransportProtos.ToDevicePersistedRpcResponseMsg msg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder()
  115 + .setRequestId(request.getRequestId())
  116 + .setRequestIdLSB(request.getRequestIdLSB())
  117 + .setRequestIdMSB(request.getRequestIdMSB())
  118 + .setStatus(status.name())
  119 + .build();
  120 + transportService.process(getSessionInfo(), msg, TransportServiceCallback.EMPTY);
  121 + });
  122 + }
  123 + }
  124 + );
93 125 } catch (Exception e) {
94 126 log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
95 127 }
... ...
... ... @@ -28,6 +28,7 @@ import com.google.gson.JsonSyntaxException;
28 28 import com.google.protobuf.InvalidProtocolBufferException;
29 29 import com.google.protobuf.ProtocolStringList;
30 30 import io.netty.buffer.ByteBuf;
  31 +import io.netty.channel.ChannelFuture;
31 32 import io.netty.channel.ChannelHandlerContext;
32 33 import io.netty.handler.codec.mqtt.MqttMessage;
33 34 import io.netty.handler.codec.mqtt.MqttPublishMessage;
... ... @@ -188,8 +189,8 @@ public class GatewaySessionHandler {
188 189 }
189 190 }
190 191
191   - void writeAndFlush(MqttMessage mqttMessage) {
192   - channel.writeAndFlush(mqttMessage);
  192 + ChannelFuture writeAndFlush(MqttMessage mqttMessage) {
  193 + return channel.writeAndFlush(mqttMessage);
193 194 }
194 195
195 196 int nextMsgId() {
... ... @@ -251,7 +252,7 @@ public class GatewaySessionHandler {
251 252 new TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse>() {
252 253 @Override
253 254 public void onSuccess(GetOrCreateDeviceFromGatewayResponse msg) {
254   - GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), msg.getDeviceProfile(), mqttQoSMap);
  255 + GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), msg.getDeviceProfile(), mqttQoSMap, transportService);
255 256 if (devices.putIfAbsent(deviceName, deviceSessionCtx) == null) {
256 257 log.trace("[{}] First got or created device [{}], type [{}] for the gateway session", sessionId, deviceName, deviceType);
257 258 SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo();
... ...
... ... @@ -26,7 +26,9 @@ import org.thingsboard.server.common.data.DeviceProfile;
26 26 import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
27 27 import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration;
28 28 import org.thingsboard.server.common.data.id.DeviceId;
  29 +import org.thingsboard.server.common.data.rpc.RpcStatus;
29 30 import org.thingsboard.server.common.transport.SessionMsgListener;
  31 +import org.thingsboard.server.common.transport.TransportServiceCallback;
30 32 import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
31 33 import org.thingsboard.server.gen.transport.TransportProtos;
32 34 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
... ... @@ -139,6 +141,21 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S
139 141 @Override
140 142 public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) {
141 143 snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest);
  144 + if (toDeviceRequest.getPersisted()) {
  145 + RpcStatus status;
  146 + if (toDeviceRequest.getOneway()) {
  147 + status = RpcStatus.SUCCESSFUL;
  148 + } else {
  149 + status = RpcStatus.DELIVERED;
  150 + }
  151 + TransportProtos.ToDevicePersistedRpcResponseMsg responseMsg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder()
  152 + .setRequestId(toDeviceRequest.getRequestId())
  153 + .setRequestIdLSB(toDeviceRequest.getRequestIdLSB())
  154 + .setRequestIdMSB(toDeviceRequest.getRequestIdMSB())
  155 + .setStatus(status.name())
  156 + .build();
  157 + snmpTransportContext.getTransportService().process(getSessionInfo(), responseMsg, TransportServiceCallback.EMPTY);
  158 + }
142 159 }
143 160
144 161 @Override
... ...
... ... @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
20 20 import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
21 21 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
22 22 import org.thingsboard.server.common.transport.service.SessionMetaData;
  23 +import org.thingsboard.server.gen.transport.TransportProtos;
23 24 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
24 25 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
25 26 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
... ... @@ -109,6 +110,8 @@ public interface TransportService {
109 110
110 111 void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback);
111 112
  113 + void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDevicePersistedRpcResponseMsg msg, TransportServiceCallback<Void> callback);
  114 +
112 115 void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback<Void> callback);
113 116
114 117 void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback);
... ...
... ... @@ -557,6 +557,15 @@ public class DefaultTransportService implements TransportService {
557 557 }
558 558 }
559 559
  560 + @Override
  561 + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDevicePersistedRpcResponseMsg msg, TransportServiceCallback<Void> callback) {
  562 + if (checkLimits(sessionInfo, msg, callback)) {
  563 + reportActivityInternal(sessionInfo);
  564 + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPersistedRpcResponseMsg(msg).build(),
  565 + new ApiStatsProxyCallback<>(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, callback));
  566 + }
  567 + }
  568 +
560 569 private void processTimeout(String requestId) {
561 570 RpcRequestMetadata data = toServerRpcPendingMap.remove(requestId);
562 571 if (data != null) {
... ...
... ... @@ -507,6 +507,17 @@ public class ModelConstants {
507 507 public static final String OTA_PACKAGE_ADDITIONAL_INFO_COLUMN = ADDITIONAL_INFO_PROPERTY;
508 508
509 509 /**
  510 + * Persisted RPC constants.
  511 + */
  512 + public static final String RPC_TABLE_NAME = "rpc";
  513 + public static final String RPC_TENANT_ID_COLUMN = TENANT_ID_COLUMN;
  514 + public static final String RPC_DEVICE_ID = "device_id";
  515 + public static final String RPC_EXPIRATION_TIME = "expiration_time";
  516 + public static final String RPC_REQUEST = "request";
  517 + public static final String RPC_RESPONSE = "response";
  518 + public static final String RPC_STATUS = "status";
  519 +
  520 + /**
510 521 * Edge constants.
511 522 */
512 523 public static final String EDGE_COLUMN_FAMILY_NAME = "edge";
... ...
  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.dao.model.sql;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import org.hibernate.annotations.Type;
  22 +import org.hibernate.annotations.TypeDef;
  23 +import org.thingsboard.server.common.data.id.DeviceId;
  24 +import org.thingsboard.server.common.data.id.RpcId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.rpc.Rpc;
  27 +import org.thingsboard.server.common.data.rpc.RpcStatus;
  28 +import org.thingsboard.server.dao.model.BaseEntity;
  29 +import org.thingsboard.server.dao.model.BaseSqlEntity;
  30 +import org.thingsboard.server.dao.util.mapping.JsonStringType;
  31 +
  32 +import javax.persistence.Column;
  33 +import javax.persistence.Entity;
  34 +import javax.persistence.EnumType;
  35 +import javax.persistence.Enumerated;
  36 +import javax.persistence.Table;
  37 +import java.util.UUID;
  38 +
  39 +import static org.thingsboard.server.dao.model.ModelConstants.RPC_DEVICE_ID;
  40 +import static org.thingsboard.server.dao.model.ModelConstants.RPC_EXPIRATION_TIME;
  41 +import static org.thingsboard.server.dao.model.ModelConstants.RPC_REQUEST;
  42 +import static org.thingsboard.server.dao.model.ModelConstants.RPC_RESPONSE;
  43 +import static org.thingsboard.server.dao.model.ModelConstants.RPC_STATUS;
  44 +import static org.thingsboard.server.dao.model.ModelConstants.RPC_TABLE_NAME;
  45 +import static org.thingsboard.server.dao.model.ModelConstants.RPC_TENANT_ID_COLUMN;
  46 +
  47 +@Data
  48 +@EqualsAndHashCode(callSuper = true)
  49 +@Entity
  50 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  51 +@Table(name = RPC_TABLE_NAME)
  52 +public class RpcEntity extends BaseSqlEntity<Rpc> implements BaseEntity<Rpc> {
  53 +
  54 + @Column(name = RPC_TENANT_ID_COLUMN)
  55 + private UUID tenantId;
  56 +
  57 + @Column(name = RPC_DEVICE_ID)
  58 + private UUID deviceId;
  59 +
  60 + @Column(name = RPC_EXPIRATION_TIME)
  61 + private long expirationTime;
  62 +
  63 + @Type(type = "json")
  64 + @Column(name = RPC_REQUEST)
  65 + private JsonNode request;
  66 +
  67 + @Type(type = "json")
  68 + @Column(name = RPC_RESPONSE)
  69 + private JsonNode response;
  70 +
  71 + @Enumerated(EnumType.STRING)
  72 + @Column(name = RPC_STATUS)
  73 + private RpcStatus status;
  74 +
  75 + public RpcEntity() {
  76 + super();
  77 + }
  78 +
  79 + public RpcEntity(Rpc rpc) {
  80 + this.setUuid(rpc.getUuidId());
  81 + this.createdTime = rpc.getCreatedTime();
  82 + this.tenantId = rpc.getTenantId().getId();
  83 + this.deviceId = rpc.getDeviceId().getId();
  84 + this.expirationTime = rpc.getExpirationTime();
  85 + this.request = rpc.getRequest();
  86 + this.response = rpc.getResponse();
  87 + this.status = rpc.getStatus();
  88 + }
  89 +
  90 + @Override
  91 + public Rpc toData() {
  92 + Rpc rpc = new Rpc(new RpcId(id));
  93 + rpc.setCreatedTime(createdTime);
  94 + rpc.setTenantId(new TenantId(tenantId));
  95 + rpc.setDeviceId(new DeviceId(deviceId));
  96 + rpc.setExpirationTime(expirationTime);
  97 + rpc.setRequest(request);
  98 + rpc.setResponse(response);
  99 + rpc.setStatus(status);
  100 + return rpc;
  101 + }
  102 +}
... ...
  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.dao.rpc;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import lombok.RequiredArgsConstructor;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.springframework.stereotype.Service;
  22 +import org.thingsboard.server.common.data.id.DeviceId;
  23 +import org.thingsboard.server.common.data.id.RpcId;
  24 +import org.thingsboard.server.common.data.id.TenantId;
  25 +import org.thingsboard.server.common.data.page.PageData;
  26 +import org.thingsboard.server.common.data.page.PageLink;
  27 +import org.thingsboard.server.common.data.rpc.Rpc;
  28 +import org.thingsboard.server.common.data.rpc.RpcStatus;
  29 +import org.thingsboard.server.dao.service.PaginatedRemover;
  30 +
  31 +import static org.thingsboard.server.dao.service.Validator.validateId;
  32 +import static org.thingsboard.server.dao.service.Validator.validatePageLink;
  33 +
  34 +@Service
  35 +@Slf4j
  36 +@RequiredArgsConstructor
  37 +public class BaseRpcService implements RpcService {
  38 + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
  39 + public static final String INCORRECT_RPC_ID = "Incorrect rpcId ";
  40 +
  41 + private final RpcDao rpcDao;
  42 +
  43 + @Override
  44 + public Rpc save(Rpc rpc) {
  45 + log.trace("Executing save, [{}]", rpc);
  46 + return rpcDao.save(rpc.getTenantId(), rpc);
  47 + }
  48 +
  49 + @Override
  50 + public void deleteRpc(TenantId tenantId, RpcId rpcId) {
  51 + log.trace("Executing deleteRpc, tenantId [{}], rpcId [{}]", tenantId, rpcId);
  52 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  53 + validateId(rpcId, INCORRECT_RPC_ID + rpcId);
  54 + rpcDao.removeById(tenantId, rpcId.getId());
  55 + }
  56 +
  57 + @Override
  58 + public void deleteAllRpcByTenantId(TenantId tenantId) {
  59 + log.trace("Executing deleteAllRpcByTenantId, tenantId [{}]", tenantId);
  60 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  61 + tenantRpcRemover.removeEntities(tenantId, tenantId);
  62 + }
  63 +
  64 + @Override
  65 + public Rpc findById(TenantId tenantId, RpcId rpcId) {
  66 + log.trace("Executing findById, tenantId [{}], rpcId [{}]", tenantId, rpcId);
  67 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  68 + validateId(rpcId, INCORRECT_RPC_ID + rpcId);
  69 + return rpcDao.findById(tenantId, rpcId.getId());
  70 + }
  71 +
  72 + @Override
  73 + public ListenableFuture<Rpc> findRpcByIdAsync(TenantId tenantId, RpcId rpcId) {
  74 + log.trace("Executing findRpcByIdAsync, tenantId [{}], rpcId: [{}]", tenantId, rpcId);
  75 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  76 + validateId(rpcId, INCORRECT_RPC_ID + rpcId);
  77 + return rpcDao.findByIdAsync(tenantId, rpcId.getId());
  78 + }
  79 +
  80 + @Override
  81 + public PageData<Rpc> findAllByDeviceIdAndStatus(TenantId tenantId, DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) {
  82 + log.trace("Executing findAllByDeviceIdAndStatus, tenantId [{}], deviceId [{}], rpcStatus [{}], pageLink [{}]", tenantId, deviceId, rpcStatus, pageLink);
  83 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  84 + validatePageLink(pageLink);
  85 + return rpcDao.findAllByDeviceId(tenantId, deviceId, rpcStatus, pageLink);
  86 + }
  87 +
  88 + private PaginatedRemover<TenantId, Rpc> tenantRpcRemover =
  89 + new PaginatedRemover<>() {
  90 + @Override
  91 + protected PageData<Rpc> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
  92 + return rpcDao.findAllRpcByTenantId(id, pageLink);
  93 + }
  94 +
  95 + @Override
  96 + protected void removeEntity(TenantId tenantId, Rpc entity) {
  97 + deleteRpc(tenantId, entity.getId());
  98 + }
  99 + };
  100 +}
... ...
  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.dao.rpc;
  17 +
  18 +import org.thingsboard.server.common.data.id.DeviceId;
  19 +import org.thingsboard.server.common.data.id.TenantId;
  20 +import org.thingsboard.server.common.data.page.PageData;
  21 +import org.thingsboard.server.common.data.page.PageLink;
  22 +import org.thingsboard.server.common.data.rpc.Rpc;
  23 +import org.thingsboard.server.common.data.rpc.RpcStatus;
  24 +import org.thingsboard.server.dao.Dao;
  25 +
  26 +public interface RpcDao extends Dao<Rpc> {
  27 + PageData<Rpc> findAllByDeviceId(TenantId tenantId, DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink);
  28 +
  29 + PageData<Rpc> findAllRpcByTenantId(TenantId tenantId, PageLink pageLink);
  30 +
  31 + Long deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime);
  32 +}
... ...
  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.dao.sql.rpc;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.data.repository.CrudRepository;
  21 +import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.common.data.id.DeviceId;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.page.PageData;
  25 +import org.thingsboard.server.common.data.page.PageLink;
  26 +import org.thingsboard.server.common.data.rpc.Rpc;
  27 +import org.thingsboard.server.common.data.rpc.RpcStatus;
  28 +import org.thingsboard.server.dao.DaoUtil;
  29 +import org.thingsboard.server.dao.model.sql.RpcEntity;
  30 +import org.thingsboard.server.dao.rpc.RpcDao;
  31 +import org.thingsboard.server.dao.sql.JpaAbstractDao;
  32 +
  33 +import java.util.UUID;
  34 +
  35 +@Slf4j
  36 +@Component
  37 +@AllArgsConstructor
  38 +public class JpaRpcDao extends JpaAbstractDao<RpcEntity, Rpc> implements RpcDao {
  39 +
  40 + private final RpcRepository rpcRepository;
  41 +
  42 + @Override
  43 + protected Class<RpcEntity> getEntityClass() {
  44 + return RpcEntity.class;
  45 + }
  46 +
  47 + @Override
  48 + protected CrudRepository<RpcEntity, UUID> getCrudRepository() {
  49 + return rpcRepository;
  50 + }
  51 +
  52 + @Override
  53 + public PageData<Rpc> findAllByDeviceId(TenantId tenantId, DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) {
  54 + return DaoUtil.toPageData(rpcRepository.findAllByTenantIdAndDeviceIdAndStatus(tenantId.getId(), deviceId.getId(), rpcStatus, DaoUtil.toPageable(pageLink)));
  55 + }
  56 +
  57 + @Override
  58 + public PageData<Rpc> findAllRpcByTenantId(TenantId tenantId, PageLink pageLink) {
  59 + return DaoUtil.toPageData(rpcRepository.findAllByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
  60 + }
  61 +
  62 + @Override
  63 + public Long deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime) {
  64 + return rpcRepository.deleteOutdatedRpcByTenantId(tenantId.getId(), expirationTime);
  65 + }
  66 +}
... ...
  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.dao.sql.rpc;
  17 +
  18 +import org.springframework.data.domain.Page;
  19 +import org.springframework.data.domain.Pageable;
  20 +import org.springframework.data.jpa.repository.Query;
  21 +import org.springframework.data.repository.CrudRepository;
  22 +import org.springframework.data.repository.query.Param;
  23 +import org.thingsboard.server.common.data.rpc.RpcStatus;
  24 +import org.thingsboard.server.dao.model.sql.RpcEntity;
  25 +
  26 +import java.util.UUID;
  27 +
  28 +public interface RpcRepository extends CrudRepository<RpcEntity, UUID> {
  29 + Page<RpcEntity> findAllByTenantIdAndDeviceIdAndStatus(UUID tenantId, UUID deviceId, RpcStatus status, Pageable pageable);
  30 +
  31 + Page<RpcEntity> findAllByTenantId(UUID tenantId, Pageable pageable);
  32 +
  33 + @Query(value = "WITH deleted AS (DELETE FROM rpc WHERE (tenant_id = :tenantId AND created_time < :expirationTime) IS TRUE RETURNING *) SELECT count(*) FROM deleted",
  34 + nativeQuery = true)
  35 + Long deleteOutdatedRpcByTenantId(@Param("tenantId") UUID tenantId, @Param("expirationTime") Long expirationTime);
  36 +}
... ...
... ... @@ -37,6 +37,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService;
37 37 import org.thingsboard.server.dao.exception.DataValidationException;
38 38 import org.thingsboard.server.dao.ota.OtaPackageService;
39 39 import org.thingsboard.server.dao.resource.ResourceService;
  40 +import org.thingsboard.server.dao.rpc.RpcService;
40 41 import org.thingsboard.server.dao.rule.RuleChainService;
41 42 import org.thingsboard.server.dao.service.DataValidator;
42 43 import org.thingsboard.server.dao.service.PaginatedRemover;
... ... @@ -96,6 +97,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
96 97 @Autowired
97 98 private OtaPackageService otaPackageService;
98 99
  100 + @Autowired
  101 + private RpcService rpcService;
  102 +
99 103 @Override
100 104 public Tenant findTenantById(TenantId tenantId) {
101 105 log.trace("Executing findTenantById [{}]", tenantId);
... ... @@ -151,6 +155,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
151 155 apiUsageStateService.deleteApiUsageStateByTenantId(tenantId);
152 156 resourceService.deleteResourcesByTenantId(tenantId);
153 157 otaPackageService.deleteOtaPackagesByTenantId(tenantId);
  158 + rpcService.deleteAllRpcByTenantId(tenantId);
154 159 tenantDao.removeById(tenantId, tenantId.getId());
155 160 deleteEntityRelations(tenantId, tenantId);
156 161 }
... ...
... ... @@ -567,3 +567,14 @@ CREATE TABLE IF NOT EXISTS edge_event (
567 567 tenant_id uuid,
568 568 ts bigint NOT NULL
569 569 );
  570 +
  571 +CREATE TABLE IF NOT EXISTS rpc (
  572 + id uuid NOT NULL CONSTRAINT rpc_pkey PRIMARY KEY,
  573 + created_time bigint NOT NULL,
  574 + tenant_id uuid NOT NULL,
  575 + device_id uuid NOT NULL,
  576 + expiration_time bigint NOT NULL,
  577 + request varchar(10000000) NOT NULL,
  578 + response varchar(10000000),
  579 + status varchar(255) NOT NULL
  580 +);
... ...
... ... @@ -46,3 +46,4 @@ CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribu
46 46
47 47 CREATE INDEX IF NOT EXISTS idx_audit_log_tenant_id_and_created_time ON audit_log(tenant_id, created_time);
48 48
  49 +CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id);
... ...
... ... @@ -605,6 +605,17 @@ CREATE TABLE IF NOT EXISTS edge_event (
605 605 ts bigint NOT NULL
606 606 );
607 607
  608 +CREATE TABLE IF NOT EXISTS rpc (
  609 + id uuid NOT NULL CONSTRAINT rpc_pkey PRIMARY KEY,
  610 + created_time bigint NOT NULL,
  611 + tenant_id uuid NOT NULL,
  612 + device_id uuid NOT NULL,
  613 + expiration_time bigint NOT NULL,
  614 + request varchar(10000000) NOT NULL,
  615 + response varchar(10000000),
  616 + status varchar(255) NOT NULL
  617 +);
  618 +
608 619 CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint)
609 620 LANGUAGE plpgsql AS
610 621 $$
... ...
... ... @@ -36,4 +36,5 @@ DROP TABLE IF EXISTS resource;
36 36 DROP TABLE IF EXISTS ota_package;
37 37 DROP TABLE IF EXISTS edge;
38 38 DROP TABLE IF EXISTS edge_event;
  39 +DROP TABLE IF EXISTS rpc;
39 40 DROP FUNCTION IF EXISTS to_uuid;
... ...
... ... @@ -37,3 +37,4 @@ DROP TABLE IF EXISTS resource;
37 37 DROP TABLE IF EXISTS firmware;
38 38 DROP TABLE IF EXISTS edge;
39 39 DROP TABLE IF EXISTS edge_event;
  40 +DROP TABLE IF EXISTS rpc;
... ...
... ... @@ -35,6 +35,7 @@ public final class RuleEngineDeviceRpcRequest {
35 35 private final UUID requestUUID;
36 36 private final String originServiceId;
37 37 private final boolean oneway;
  38 + private final boolean persisted;
38 39 private final String method;
39 40 private final String body;
40 41 private final long expirationTime;
... ...
... ... @@ -33,8 +33,8 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
33 33 type = ComponentType.FILTER,
34 34 name = "message type switch",
35 35 configClazz = EmptyNodeConfiguration.class,
36   - relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "Activity Event", "Inactivity Event",
37   - "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned",
  36 + relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "RPC Queued", "RPC Delivered", "RPC Successful", "RPC Timeout", "RPC Failed",
  37 + "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned",
38 38 "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant",
39 39 "Timeseries Updated", "Timeseries Deleted"},
40 40 nodeDescription = "Route incoming messages by Message Type",
... ... @@ -95,6 +95,16 @@ public class TbMsgTypeSwitchNode implements TbNode {
95 95 relationType = "Timeseries Updated";
96 96 } else if (msg.getType().equals(DataConstants.TIMESERIES_DELETED)) {
97 97 relationType = "Timeseries Deleted";
  98 + } else if (msg.getType().equals(DataConstants.RPC_QUEUED)) {
  99 + relationType = "RPC Queued";
  100 + } else if (msg.getType().equals(DataConstants.RPC_DELIVERED)) {
  101 + relationType = "RPC Delivered";
  102 + } else if (msg.getType().equals(DataConstants.RPC_SUCCESSFUL)) {
  103 + relationType = "RPC Successful";
  104 + } else if (msg.getType().equals(DataConstants.RPC_TIMEOUT)) {
  105 + relationType = "RPC Timeout";
  106 + } else if (msg.getType().equals(DataConstants.RPC_FAILED)) {
  107 + relationType = "RPC Failed";
98 108 } else {
99 109 relationType = "Other";
100 110 }
... ...
... ... @@ -81,6 +81,9 @@ public class TbSendRPCRequestNode implements TbNode {
81 81 tmp = msg.getMetaData().getValue("oneway");
82 82 boolean oneway = !StringUtils.isEmpty(tmp) && Boolean.parseBoolean(tmp);
83 83
  84 + tmp = msg.getMetaData().getValue("persisted");
  85 + boolean persisted = !StringUtils.isEmpty(tmp) && Boolean.parseBoolean(tmp);
  86 +
84 87 tmp = msg.getMetaData().getValue("requestUUID");
85 88 UUID requestUUID = !StringUtils.isEmpty(tmp) ? UUID.fromString(tmp) : Uuids.timeBased();
86 89 tmp = msg.getMetaData().getValue("originServiceId");
... ... @@ -108,6 +111,7 @@ public class TbSendRPCRequestNode implements TbNode {
108 111 .originServiceId(originServiceId)
109 112 .expirationTime(expirationTime)
110 113 .restApiCall(restApiCall)
  114 + .persisted(persisted)
111 115 .build();
112 116
113 117 ctx.getRpcService().sendRpcRequestToDevice(request, ruleEngineDeviceRpcResponse -> {
... ...
... ... @@ -160,9 +160,9 @@
160 160 <!-- <div fxLayout="column">-->
161 161 <!-- <mat-form-field class="mat-block">-->
162 162 <!-- <mat-label>{{ 'device-profile.lwm2m.client-strategy-label' | translate }}</mat-label>-->
163   -<!-- <mat-select formControlName="clientStrategy"-->
  163 +<!-- <mat-select formControlName="clientOnlyObserveAfterConnect"-->
164 164 <!-- matTooltip="{{ 'device-profile.lwm2m.client-strategy-tip' | translate:-->
165   -<!-- { count: +lwm2mDeviceProfileFormGroup.get('clientStrategy').value } }}"-->
  165 +<!-- { count: +lwm2mDeviceProfileFormGroup.get('clientOnlyObserveAfterConnect').value } }}"-->
166 166 <!-- matTooltipPosition="above">-->
167 167 <!-- <mat-option value=1>{{ 'device-profile.lwm2m.client-strategy-connect' | translate:-->
168 168 <!-- {count: 1} }}</mat-option>-->
... ...
... ... @@ -97,7 +97,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
97 97 binding: [],
98 98 bootstrapServer: [null, Validators.required],
99 99 lwm2mServer: [null, Validators.required],
100   - clientStrategy: [1, []],
  100 + clientOnlyObserveAfterConnect: [1, []],
101 101 fwUpdateStrategy: [1, []],
102 102 swUpdateStrategy: [1, []],
103 103 fwUpdateRecourse: [{value: '', disabled: true}, []],
... ... @@ -216,7 +216,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
216 216 binding: this.configurationValue.bootstrap.servers.binding,
217 217 bootstrapServer: this.configurationValue.bootstrap.bootstrapServer,
218 218 lwm2mServer: this.configurationValue.bootstrap.lwm2mServer,
219   - clientStrategy: this.configurationValue.clientLwM2mSettings.clientStrategy,
  219 + clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect,
220 220 fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy || 1,
221 221 swUpdateStrategy: this.configurationValue.clientLwM2mSettings.swUpdateStrategy || 1,
222 222 fwUpdateRecourse: fwResource,
... ... @@ -257,7 +257,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
257 257 bootstrapServers.defaultMinPeriod = config.defaultMinPeriod;
258 258 bootstrapServers.notifIfDisabled = config.notifIfDisabled;
259 259 bootstrapServers.binding = config.binding;
260   - this.configurationValue.clientLwM2mSettings.clientStrategy = config.clientStrategy;
  260 + this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect = config.clientOnlyObserveAfterConnect;
261 261 this.configurationValue.clientLwM2mSettings.fwUpdateStrategy = config.fwUpdateStrategy;
262 262 this.configurationValue.clientLwM2mSettings.swUpdateStrategy = config.swUpdateStrategy;
263 263 this.configurationValue.clientLwM2mSettings.fwUpdateRecourse = config.fwUpdateRecourse;
... ...
... ... @@ -168,7 +168,7 @@ export interface Lwm2mProfileConfigModels {
168 168 }
169 169
170 170 export interface ClientLwM2mSettings {
171   - clientStrategy: string;
  171 + clientOnlyObserveAfterConnect: number;
172 172 fwUpdateStrategy: number;
173 173 swUpdateStrategy: number;
174 174 fwUpdateRecourse: string;
... ... @@ -240,7 +240,7 @@ export function getDefaultProfileConfig(hostname?: any): Lwm2mProfileConfigModel
240 240
241 241 function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings {
242 242 return {
243   - clientStrategy: '1',
  243 + clientOnlyObserveAfterConnect: 1,
244 244 fwUpdateStrategy: 1,
245 245 swUpdateStrategy: 1,
246 246 fwUpdateRecourse: DEFAULT_FW_UPDATE_RESOURCE,
... ...
... ... @@ -197,6 +197,18 @@
197 197 </mat-error>
198 198 </mat-form-field>
199 199 <mat-form-field class="mat-block">
  200 + <mat-label translate>tenant-profile.rpc-ttl-days</mat-label>
  201 + <input matInput required min="0" step="1"
  202 + formControlName="rpcTtlDays"
  203 + type="number">
  204 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('rpcTtlDays').hasError('required')">
  205 + {{ 'tenant-profile.rpc-ttl-days-required' | translate}}
  206 + </mat-error>
  207 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('rpcTtlDays').hasError('min')">
  208 + {{ 'tenant-profile.rpc-ttl-days-days-range' | translate}}
  209 + </mat-error>
  210 + </mat-form-field>
  211 + <mat-form-field class="mat-block">
200 212 <mat-label translate>tenant-profile.max-rule-node-executions-per-message</mat-label>
201 213 <input matInput required min="0" step="1"
202 214 formControlName="maxRuleNodeExecutionsPerMessage"
... ...
... ... @@ -77,7 +77,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
77 77 maxSms: [null, [Validators.required, Validators.min(0)]],
78 78 maxCreatedAlarms: [null, [Validators.required, Validators.min(0)]],
79 79 defaultStorageTtlDays: [null, [Validators.required, Validators.min(0)]],
80   - alarmsTtlDays: [null, [Validators.required, Validators.min(0)]]
  80 + alarmsTtlDays: [null, [Validators.required, Validators.min(0)]],
  81 + rpcTtlDays: [null, [Validators.required, Validators.min(0)]]
81 82 });
82 83 this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => {
83 84 this.updateModel();
... ...
... ... @@ -352,7 +352,12 @@ export enum MessageType {
352 352 ATTRIBUTES_UPDATED = 'ATTRIBUTES_UPDATED',
353 353 ATTRIBUTES_DELETED = 'ATTRIBUTES_DELETED',
354 354 TIMESERIES_UPDATED = 'TIMESERIES_UPDATED',
355   - TIMESERIES_DELETED = 'TIMESERIES_DELETED'
  355 + TIMESERIES_DELETED = 'TIMESERIES_DELETED',
  356 + RPC_QUEUED = 'RPC_QUEUED',
  357 + RPC_DELIVERED = 'RPC_DELIVERED',
  358 + RPC_SUCCESSFUL = 'RPC_SUCCESSFUL',
  359 + RPC_TIMEOUT = 'RPC_TIMEOUT',
  360 + RPC_FAILED = 'RPC_FAILED'
356 361 }
357 362
358 363 export const messageTypeNames = new Map<MessageType, string>(
... ... @@ -373,7 +378,12 @@ export const messageTypeNames = new Map<MessageType, string>(
373 378 [MessageType.ATTRIBUTES_UPDATED, 'Attributes Updated'],
374 379 [MessageType.ATTRIBUTES_DELETED, 'Attributes Deleted'],
375 380 [MessageType.TIMESERIES_UPDATED, 'Timeseries Updated'],
376   - [MessageType.TIMESERIES_DELETED, 'Timeseries Deleted']
  381 + [MessageType.TIMESERIES_DELETED, 'Timeseries Deleted'],
  382 + [MessageType.RPC_QUEUED, 'RPC Queued'],
  383 + [MessageType.RPC_DELIVERED, 'RPC Delivered'],
  384 + [MessageType.RPC_SUCCESSFUL, 'RPC Successful'],
  385 + [MessageType.RPC_TIMEOUT, 'RPC Timeout'],
  386 + [MessageType.RPC_FAILED, 'RPC Failed']
377 387 ]
378 388 );
379 389
... ...
... ... @@ -53,6 +53,7 @@ export interface DefaultTenantProfileConfiguration {
53 53
54 54 defaultStorageTtlDays: number;
55 55 alarmsTtlDays: number;
  56 + rpcTtlDays: number;
56 57 }
57 58
58 59 export type TenantProfileConfigurations = DefaultTenantProfileConfiguration;
... ... @@ -85,7 +86,8 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan
85 86 maxSms: 0,
86 87 maxCreatedAlarms: 0,
87 88 defaultStorageTtlDays: 0,
88   - alarmsTtlDays: 0
  89 + alarmsTtlDays: 0,
  90 + rpcTtlDays: 0
89 91 };
90 92 configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT};
91 93 break;
... ...
... ... @@ -2638,6 +2638,9 @@
2638 2638 "alarms-ttl-days": "Alarms TTL days (0 - unlimited)",
2639 2639 "alarms-ttl-days-required": "Alarms TTL days required",
2640 2640 "alarms-ttl-days-days-range": "Alarms TTL days can't be negative",
  2641 + "rpc-ttl-days": "RPC TTL days (0 - unlimited)",
  2642 + "rpc-ttl-days-required": "RPC TTL days required",
  2643 + "rpc-ttl-days-days-range": "RPC TTL days can't be negative",
2641 2644 "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)",
2642 2645 "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.",
2643 2646 "max-rule-node-executions-per-message-range": "Maximum number of rule node executions per message can't be negative",
... ...