From f2b58fd939044ef40966a8c2ad36f60a6793afce Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Fri, 27 Aug 2021 11:44:09 +0300 Subject: [PATCH] Lwm2m: tests5 --- .../lwm2m/client/LwM2MTestClient.java | 9 +- .../LwM2MTransportBootstrapService.java | 16 +- .../secure/LwM2MBootstrapConfig.java | 2 + .../secure/LwM2MBootstrapSecurityStore.java | 19 +- .../LwM2MInMemoryBootstrapConfigStore.java | 11 +- .../secure/LwM2MServerBootstrap.java | 2 - .../LwM2mDefaultBootstrapSessionManager.java | 178 +++++++- .../config/LwM2MTransportServerConfig.java | 7 - .../AbstractLwM2mTransportResource.java | 3 - .../server/DefaultLwM2mTransportService.java | 8 +- .../lwm2m/server/LwM2mOperationType.java | 15 +- .../lwm2m/server/LwM2mServerListener.java | 2 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 1 - .../server/LwM2mTransportServerHelper.java | 4 +- .../lwm2m/server/LwM2mTransportUtil.java | 72 +++ .../DefaultLwM2MAttributesService.java | 113 ++++- .../attributes/LwM2MAttributesService.java | 2 +- .../lwm2m/server/client/LwM2mClient.java | 121 +++-- .../server/client/LwM2mClientContext.java | 5 +- .../server/client/LwM2mClientContextImpl.java | 34 +- .../lwm2m/server/client/ResourceValue.java | 44 +- ...urce.java => TbLwM2mMultipleResource.java} | 6 +- .../common/LwM2MExecutorAwareService.java | 1 - .../DefaultLwM2mDownlinkMsgHandler.java | 423 ++++++++++++------ .../server/downlink/HasContentFormat.java | 4 +- .../downlink/LwM2mDownlinkMsgHandler.java | 8 +- .../TbLwM2MCancelObserveCallback.java | 4 +- .../server/downlink/TbLwM2MCreateRequest.java | 50 +++ .../TbLwM2MCreateResponseCallback.java | 36 ++ .../downlink/TbLwM2MDeleteCallback.java | 1 - .../downlink/TbLwM2MDiscoverCallback.java | 3 +- .../downlink/TbLwM2MExecuteCallback.java | 3 +- .../downlink/TbLwM2MObserveCallback.java | 2 +- .../downlink/TbLwM2MObserveRequest.java | 13 +- .../server/downlink/TbLwM2MReadRequest.java | 13 +- .../downlink/TbLwM2MTargetedCallback.java | 27 +- .../TbLwM2MWriteAttributesCallback.java | 1 - .../TbLwM2MReadCompositeRequest.java | 13 +- .../log/DefaultLwM2MTelemetryLogService.java | 1 - .../ota/DefaultLwM2MOtaUpdateService.java | 3 +- .../ota/software/LwM2MClientSwOtaInfo.java | 4 - .../rpc/DefaultLwM2MRpcRequestHandler.java | 103 ++++- ...equest.java => LwM2MRpcRequestHeader.java} | 3 +- .../rpc/RpcCancelAllObserveCallback.java | 1 - .../server/rpc/RpcCancelObserveCallback.java | 7 - .../rpc/RpcCreateRequest.java} | 18 +- .../server/rpc/RpcCreateResponseCallback.java | 38 ++ .../lwm2m/server/rpc/RpcDiscoverCallback.java | 6 - .../lwm2m/server/rpc/RpcLinkSetCallback.java | 5 - .../server/rpc/RpcWriteAttributesRequest.java | 2 +- .../server/rpc/RpcWriteReplaceRequest.java | 2 +- .../server/rpc/RpcWriteUpdateRequest.java | 3 +- .../composite/RpcReadCompositeRequest.java | 4 +- .../store/TbLwM2mRedisClientOtaInfoStore.java | 8 - .../store/TbLwM2mRedisRegistrationStore.java | 2 +- .../uplink/DefaultLwM2MUplinkMsgHandler.java | 60 ++- .../server/uplink/LwM2mUplinkMsgHandler.java | 3 + .../lwm2m/utils/LwM2mValueConverterImpl.java | 12 +- pom.xml | 2 +- 59 files changed, 1145 insertions(+), 418 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/{TbLwM2MMultipleResource.java => TbLwM2mMultipleResource.java} (82%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateResponseCallback.java rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/{IdOrKeyRequest.java => LwM2MRpcRequestHeader.java} (92%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/{bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java => server/rpc/RpcCreateRequest.java} (59%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateResponseCallback.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index c4f22ff5bb..72f9b014b7 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -39,12 +39,13 @@ import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.StaticModel; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.DeregisterRequest; import org.eclipse.leshan.core.request.RegisterRequest; import org.eclipse.leshan.core.request.UpdateRequest; +import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.io.IOException; import java.net.InetSocketAddress; @@ -130,8 +131,8 @@ public class LwM2MTestClient { builder.setRegistrationEngineFactory(engineFactory); builder.setEndpointFactory(endpointFactory); builder.setSharedExecutor(executor); - builder.setDecoder(new DefaultLwM2mNodeDecoder(true)); - builder.setEncoder(new DefaultLwM2mNodeEncoder(true)); + builder.setDecoder(new DefaultLwM2mDecoder(false)); + builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false)); client = builder.build(); LwM2mClientObserver observer = new LwM2mClientObserver() { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 16f9443547..0026392ece 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -19,17 +19,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; -import org.eclipse.leshan.core.model.ObjectLoader; -import org.eclipse.leshan.core.model.ObjectModel; -import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; -import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigurationAdapter; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; @@ -42,7 +39,6 @@ import java.security.KeyStoreException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; -import java.util.List; import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig; @@ -57,7 +53,7 @@ public class LwM2MTransportBootstrapService { private final LwM2MTransportBootstrapConfig bootstrapConfig; private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore; private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; - + private final TransportService transportService; private LeshanBootstrapServer server; @PostConstruct @@ -83,15 +79,11 @@ public class LwM2MTransportBootstrapService { /* Create CoAP Config */ builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); - /* Define model provider (Create Models )*/ - List models = ObjectLoader.loadDefault(); - builder.setModel(new StaticModel(models)); - /* Create credentials */ this.setServerWithCredentials(builder); /* Set securityStore with new ConfigStore */ - builder.setConfigStore(new LwM2MInMemoryBootstrapConfigurationAdapter(lwM2MInMemoryBootstrapConfigStore)); + builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); /* SecurityStore */ builder.setSecurityStore(lwM2MBootstrapSecurityStore); @@ -106,7 +98,7 @@ public class LwM2MTransportBootstrapService { /* Set DTLS Config */ builder.setDtlsConfig(dtlsConfig); - BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore); + BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore, lwM2MInMemoryBootstrapConfigStore, transportService); builder.setSessionManager(sessionManager); /* Create BootstrapServer */ diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java index 7dca87458b..ab96228600 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.request.BindingMode; @@ -59,6 +60,7 @@ public class LwM2MBootstrapConfig implements Serializable { LwM2MServerBootstrap lwm2mServer; + @JsonIgnore public BootstrapConfig getLwM2MBootstrapConfig() { BootstrapConfig configBs = new BootstrapConfig(); /* Delete old security objects */ diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index c7ec42d879..fb6149a796 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -39,7 +39,9 @@ import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Collections; import java.util.Iterator; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; @@ -58,6 +60,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private final LwM2mTransportContext context; private final LwM2mTransportServerHelper helper; + private final Map bsSessions = new ConcurrentHashMap<>(); public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContext context, LwM2mTransportServerHelper helper) { this.bootstrapConfigStore = bootstrapConfigStore; @@ -82,7 +85,12 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } bootstrapConfigStore.add(endPoint, bsConfigNew); } catch (InvalidConfigurationException e) { - log.error("", e); + if (e.getMessage().contains("Psk identity") && e.getMessage().contains("already used for this bootstrap server")) { + log.trace("", e); + } + else { + log.error("", e); + } } return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator(); } @@ -155,6 +163,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class); UUID sessionUUiD = UUID.randomUUID(); TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); + bsSessions.put(store.getEndpoint(), sessionInfo); context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, null, null, sessionInfo, context.getTransportService())); if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); @@ -189,4 +198,12 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) && lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.getSecurityMode())); } + + public TransportProtos.SessionInfoProto getSessionByEndpoint(String endpoint) { + return bsSessions.get(endpoint); + } + + public TransportProtos.SessionInfoProto removeSessionByEndpoint(String endpoint) { + return bsSessions.remove(endpoint); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java index bc44922fcf..ba3880c6bc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java @@ -16,7 +16,9 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; +import org.eclipse.leshan.server.bootstrap.BootstrapSession; import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -35,6 +37,11 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt private final Lock readLock = readWriteLock.readLock(); private final Lock writeLock = readWriteLock.writeLock(); + @Override + public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) { + return bootstrapByEndpoint.get(endpoint); + } + @Override public Map getAll() { readLock.lock(); @@ -56,10 +63,10 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt } @Override - public BootstrapConfig remove(String enpoint) { + public BootstrapConfig remove(String endpoint) { writeLock.lock(); try { - return super.remove(enpoint); + return super.remove(endpoint); } finally { writeLock.unlock(); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java index c8a004f52c..0e425f2a05 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java @@ -42,8 +42,6 @@ public class LwM2MServerBootstrap { public LwM2MServerBootstrap() { } - ; - public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) { this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId(); this.clientSecretKey = bootstrapFromCredential.getClientSecretKey(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java index 4e34c3dc6d..2e41d17635 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java @@ -16,23 +16,39 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; +import org.eclipse.leshan.core.request.BootstrapFinishRequest; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; +import org.eclipse.leshan.server.bootstrap.BootstrapConfigStoreTaskProvider; +import org.eclipse.leshan.server.bootstrap.BootstrapFailureCause; import org.eclipse.leshan.server.bootstrap.BootstrapSession; +import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager; +import org.eclipse.leshan.server.model.LwM2mBootstrapModelProvider; +import org.eclipse.leshan.server.model.StandardBootstrapModelProvider; import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityInfo; +import org.thingsboard.server.common.transport.TransportService; -import java.util.Collections; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; + +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; @Slf4j public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager { private BootstrapSecurityStore bsSecurityStore; private SecurityChecker securityChecker; + private BootstrapTaskProvider tasksProvider; + private LwM2mBootstrapModelProvider modelProvider; + private TransportService transportService; /** * Create a {@link DefaultBootstrapSessionManager} using a default {@link SecurityChecker} to accept or refuse new @@ -40,29 +56,169 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession * * @param bsSecurityStore the {@link BootstrapSecurityStore} used by default {@link SecurityChecker}. */ - public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore) { - this(bsSecurityStore, new SecurityChecker()); + public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore, TransportService transportService) { + this(bsSecurityStore, new SecurityChecker(), new BootstrapConfigStoreTaskProvider(configStore), + new StandardBootstrapModelProvider()); + this.transportService = transportService; } - public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker) { - super(bsSecurityStore); + /** + * Create a {@link DefaultBootstrapSessionManager}. + * + * @param bsSecurityStore the {@link BootstrapSecurityStore} used by {@link SecurityChecker}. + * @param securityChecker used to accept or refuse new {@link BootstrapSession}. + */ + public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker, + BootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) { + super(bsSecurityStore, securityChecker, tasksProvider, modelProvider); this.bsSecurityStore = bsSecurityStore; this.securityChecker = securityChecker; + this.tasksProvider = tasksProvider; + this.modelProvider = modelProvider; } - @SuppressWarnings("deprecation") + @Override public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { boolean authorized; - if (bsSecurityStore != null) { - Iterator securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? - Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())).iterator() : bsSecurityStore.getAllByEndpoint(request.getEndpointName()); - log.info("Bootstrap session started securityInfos: [{}]", securityInfos); + Iterator securityInfos; + if (bsSecurityStore != null && securityChecker != null) { + if (clientIdentity.isSecure() && clientIdentity.isPSK()) { + securityInfos = bsSecurityStore.getAllByEndpoint(clientIdentity.getPskIdentity()); + } else { + securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName()); + } authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos); } else { authorized = true; } DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized); - log.info("Bootstrap session started : {}", session); + if (authorized) { + this.sendLogs (request.getEndpointName(), + String.format("%s: Bootstrap session started endpoint: %s, session: %s ", LOG_LWM2M_INFO, request.getEndpointName(), + session.toString())); + } return session; } + + @Override + public boolean hasConfigFor(BootstrapSession session) { + BootstrapTaskProvider.Tasks firstTasks = tasksProvider.getTasks(session, null); + if (firstTasks == null) return false; + initTasks(session, firstTasks); + return true; + } + + protected void initTasks(BootstrapSession bssession, BootstrapTaskProvider.Tasks tasks) { + DefaultBootstrapSession session = (DefaultBootstrapSession) bssession; + // set models + if (tasks.supportedObjects != null) + session.setModel(modelProvider.getObjectModel(session, tasks.supportedObjects)); + + // set Requests to Send + session.setRequests(tasks.requestsToSend); + + // prepare list where we will store Responses + session.setResponses(new ArrayList(tasks.requestsToSend.size())); + + // is last Tasks ? + session.setMoreTasks(!tasks.last); + } + + @Override + public BootstrapDownlinkRequest getFirstRequest(BootstrapSession bsSession) { + return nextRequest(bsSession); + } + + protected BootstrapDownlinkRequest nextRequest(BootstrapSession bsSession) { + DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession; + List> requestsToSend = session.getRequests(); + + if (!requestsToSend.isEmpty()) { + // get next requests + return requestsToSend.remove(0); + } else { + if (session.hasMoreTasks()) { + BootstrapTaskProvider.Tasks nextTasks = tasksProvider.getTasks(session, session.getResponses()); + if (nextTasks == null) { + session.setMoreTasks(false); + return new BootstrapFinishRequest(); + } + + initTasks(session, nextTasks); + return nextRequest(bsSession); + } else { + return new BootstrapFinishRequest(); + } + } + } + + @Override + public BootstrapPolicy onResponseSuccess(BootstrapSession bsSession, + BootstrapDownlinkRequest request, LwM2mResponse response) { + if (!(request instanceof BootstrapFinishRequest)) { + // store response + DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession; + session.getResponses().add(response); + this.sendLogs (bsSession.getEndpoint(), + String.format("%s: %s %s receives success response %s for %s : %s", LOG_LWM2M_INFO, + request.getClass().getSimpleName(), request.getPath().toString(), response.toString(), bsSession.toString(), request.toString())); + // on success for NOT bootstrap finish request we send next request + return BootstrapPolicy.continueWith(nextRequest(bsSession)); + } else { + // on success for bootstrap finish request we stop the session + this.sendLogs (bsSession.getEndpoint(), + String.format("%s: %s receives success response for bootstrap finish request and stop the session: %s", LOG_LWM2M_INFO, + request.getClass().getSimpleName(), bsSession.toString())); + return BootstrapPolicy.finished(); + } + } + + @Override + public BootstrapPolicy onResponseError(BootstrapSession bsSession, + BootstrapDownlinkRequest request, LwM2mResponse response) { + if (!(request instanceof BootstrapFinishRequest)) { + // store response + DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession; + session.getResponses().add(response); + this.sendLogs (bsSession.getEndpoint(), + String.format("%s: %s %s receives error response %s for %s : %s", LOG_LWM2M_INFO, + request.getClass().getSimpleName(), + request.getPath().toString(), response.toString(), bsSession.toString(), request.toString())); + // on response error for NOT bootstrap finish request we continue any sending next request + return BootstrapPolicy.continueWith(nextRequest(bsSession)); + } else { + // on response error for bootstrap finish request we stop the session + this.sendLogs (bsSession.getEndpoint(), + String.format("%s: %s %s error response %s for request %s bootstrap finish. Stop the session: %s", LOG_LWM2M_INFO, + request.getClass().getSimpleName(), + request.getPath().toString(), response.toString(), request.toString(), bsSession.toString())); + return BootstrapPolicy.failed(); + } + } + + @Override + public BootstrapPolicy onRequestFailure(BootstrapSession bsSession, + BootstrapDownlinkRequest request, Throwable cause) { + this.sendLogs (bsSession.getEndpoint(), + String.format("%s: %s %s failed because of %s for %s : %s", LOG_LWM2M_INFO, + request.getClass().getSimpleName(), request.getPath().toString(), cause.toString(), bsSession.toString(), request.toString())); + return BootstrapPolicy.failed(); + } + + @Override + public void end(BootstrapSession bsSession) { + this.sendLogs (bsSession.getEndpoint(), + String.format("%s: Bootstrap session finished : %s", LOG_LWM2M_INFO, bsSession.toString())); + } + + @Override + public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) { + this.sendLogs (bsSession.getEndpoint(), + String.format("%s: Bootstrap session failed by %s: %s", LOG_LWM2M_INFO, cause.toString(), bsSession.toString())); + } + + private void sendLogs (String endpointName, String logMsg) { + log.trace(logMsg); + transportService.log(((LwM2MBootstrapSecurityStore) bsSecurityStore).getSessionByEndpoint(endpointName), logMsg); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java index b0cb237e42..6cd1ce05b2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java @@ -15,24 +15,17 @@ */ package org.thingsboard.server.transport.lwm2m.config; -import com.google.common.io.Resources; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.server.model.LwM2mModelProvider; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.ResourceUtils; import javax.annotation.PostConstruct; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; import java.security.KeyStore; @Slf4j diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java index f23b84488b..5aed494934 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java @@ -16,11 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.leshan.core.californium.LwM2mCoapResource; -import org.thingsboard.server.common.transport.TransportServiceCallback; @Slf4j public abstract class AbstractLwM2mTransportResource extends LwM2mCoapResource { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 356548b2bd..c099d7ad17 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -20,8 +20,8 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; @@ -110,9 +110,9 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { LeshanServerBuilder builder = new LeshanServerBuilder(); builder.setLocalAddress(config.getHost(), config.getPort()); builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort()); - builder.setDecoder(new DefaultLwM2mNodeDecoder()); + builder.setDecoder(new DefaultLwM2mDecoder()); /* Use a magic converter to support bad type send by the UI. */ - builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); + builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance())); /* Create CoAP Config */ builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java index 6cd204299a..7ac959605a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java @@ -52,12 +52,15 @@ public enum LwM2mOperationType { DELETE(16, "Delete", true), // only for RPC - FW_UPDATE(17, "FirmwareUpdate", false); + FW_UPDATE(17, "FirmwareUpdate", false), + // FW_READ_INFO(18, "FirmwareReadInfo", false), + SW_UPDATE(19, "SoftwareUpdate", false), + // SW_READ_INFO(20, "SoftwareReadInfo", false), + SW_UNINSTALL(21, "SoftwareUninstall", false), + CREATE(11, "Create", true); + + -// FW_READ_INFO(18, "FirmwareReadInfo"), -// SW_READ_INFO(19, "SoftwareReadInfo"), -// SW_UPDATE(20, "SoftwareUpdate"), -// SW_UNINSTALL(21, "SoftwareUninstall"); @Getter private final int code; @@ -78,7 +81,7 @@ public enum LwM2mOperationType { this.type = type; this.hasObjectId = hasObjectId; this.composite = composite; - if(hasObjectId && composite){ + if (hasObjectId && composite) { throw new IllegalArgumentException("Can't set both Composite and hasObjectId for the same operation!"); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index 2ece617666..898a1ad8b2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -98,7 +98,7 @@ public class LwM2mServerListener { @Override public void onError(Observation observation, Registration registration, Exception error) { - log.error("Unable to handle notification of [{}:{}]", observation.getRegistrationId(), observation.getPath(), error); + log.error("Unable to handle notification of [{}:{}] [{}]", observation.getRegistrationId(), observation.getPath(), error.getMessage()); } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java index e9b0391250..463823e583 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java @@ -25,7 +25,6 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java index f91aea84f7..cd12e2129d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import com.google.gson.JsonParser; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.DDFFileParser; @@ -50,6 +51,7 @@ import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType. public class LwM2mTransportServerHelper { private final LwM2mTransportContext context; + private final static JsonParser JSON_PARSER = new JsonParser();; public void sendParametersOnThingsboardAttribute(List result, SessionInfoProto sessionInfo) { PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder(); @@ -229,7 +231,7 @@ public class LwM2mTransportServerHelper { case STRING_V: return kv.getStringV(); case JSON_V: - return kv.getJsonV(); + return JSON_PARSER.parse(kv.getJsonV()); } return null; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java index 6fb5fd195e..8b65e55ad5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java @@ -16,6 +16,9 @@ package org.thingsboard.server.transport.lwm2m.server; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.leshan.core.attributes.Attribute; @@ -26,6 +29,7 @@ import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.request.SimpleDownlinkRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; @@ -39,18 +43,23 @@ import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTrans import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; +import org.thingsboard.server.transport.lwm2m.server.downlink.HasVersionedId; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; @@ -302,6 +311,12 @@ public class LwM2mTransportUtil { return attributeLists.toArray(Attribute[]::new); } + /** + * "UNSIGNED_INTEGER": // Number -> Integer Example: + * Alarm Timestamp [32-bit unsigned integer] + * Short Server ID, Object ID, Object Instance ID, Resource ID, Resource Instance ID + * "CORELINK": // String used in Attribute + */ public static ResourceModel.Type equalsResourceTypeGetSimpleName(Object value) { switch (value.getClass().getSimpleName()) { case "Double": @@ -323,6 +338,63 @@ public class LwM2mTransportUtil { } } + public static void validateVersionedId(LwM2mClient client, HasVersionedId request) { + String msgExceptionStr = ""; + if (request.getObjectId() == null) { + msgExceptionStr = "Specified object id is null!"; + } else { + msgExceptionStr = client.isValidObjectVersion(request.getVersionedId()); + } + if (!msgExceptionStr.isEmpty() ) { + throw new IllegalArgumentException(msgExceptionStr); + } + } + + public static String getMsgException(String keyName, Set msgException) { + if (msgException.size() == 1) { + msgException.add(" is not configured in the device profile!"); + } + msgException.remove(""); + return String.format("%s %s", keyName, String.join(",", msgException)).trim(); + } + + public static Map convertMultiResourceValuesFromRpcBody(LinkedHashMap value, ResourceModel.Type type, String versionedId) { + Gson gson = new Gson(); + JsonParser JSON_PARSER = new JsonParser(); + String json = gson.toJson(value, LinkedHashMap.class); + return convertMultiResourceValuesFromJson((JsonObject) JSON_PARSER.parse(json), type, versionedId); + } + + public static Map convertMultiResourceValuesFromJson(JsonObject newValProto, ResourceModel.Type type, String versionedId) { + Map newValues = equalsMultiResourceValuesResourceType(type); + newValProto.getAsJsonObject().entrySet().forEach((obj) -> { + newValues.put(Integer.valueOf(obj.getKey()), LwM2mValueConverterImpl.getInstance().convertValue(obj.getValue().getAsString(), + STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId)))); + }); + return newValues; + } + + public static Map equalsMultiResourceValuesResourceType(ResourceModel.Type type) { + switch (type) { + case FLOAT: + return new HashMap(); + case INTEGER: + return new HashMap(); + case STRING: + return new HashMap(); + case BOOLEAN: + return new HashMap(); + case OPAQUE: + return new HashMap(); + case TIME: + return new HashMap(); + case OBJLNK: + return new HashMap(); + default: + return null; + } + } + public static Object convertWriteAttributes(String type, Object value, DefaultLwM2MUplinkMsgHandler serviceImpl, String target) { switch (type) { /** Integer [0:255]; */ diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java index 8cba658859..3d27621c00 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java @@ -17,11 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.attributes; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.node.LwM2mResourceInstance; import org.springframework.stereotype.Service; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; @@ -43,14 +46,19 @@ import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandle import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertMultiResourceValuesFromJson; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; @Slf4j @@ -145,7 +153,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { newSoftwareTag = getStrValue(tsKvProto); } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) { newSoftwareUrl = getStrValue(tsKvProto); - }else { + } else { otherAttributes.add(tsKvProto); } } @@ -156,7 +164,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl), Optional.ofNullable(newSoftwareTag)); } if (!otherAttributes.isEmpty()) { - onAttributesUpdate(lwM2MClient, otherAttributes); + onAttributesUpdate(lwM2MClient, otherAttributes, true); } } else if (lwM2MClient == null) { log.error("OnAttributeUpdate, lwM2MClient is null"); @@ -169,42 +177,97 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { * => send to client Request Update of value (new value from shared attribute) * and LwM2MClient.delayedRequests.add(path) * #2.1 if there is not a difference in values between the current resource values and the shared attribute values - * */ @Override - public void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos) { + public void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos, boolean logFailedUpdateOfNonChangedValue) { log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos); + Map attributesUpdate = new ConcurrentHashMap<>(); tsKvProtos.forEach(tsKvProto -> { - String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey()); - if (pathIdVer != null) { - // #1.1 - if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) { - if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) { + try { + String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey(), false); + if (pathIdVer != null) { + // #1.1 + if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) { + if (tsKvProto.getTs() >= lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) { + lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); + attributesUpdate.put(pathIdVer, tsKvProto); + } + } else { lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); + attributesUpdate.put(pathIdVer, tsKvProto); } - } else { - lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); } + } catch (IllegalArgumentException e){ + log.error("Failed update resource [{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), e.getMessage()); + String logMsg = String.format("%s: Failed update resource onAttributesUpdate %s.", + LOG_LWM2M_ERROR, e.getMessage()); + logService.log(lwM2MClient, logMsg); } }); clientContext.update(lwM2MClient); // #2.1 - lwM2MClient.getSharedAttributes().forEach((pathIdVer, tsKvProto) -> { - this.pushUpdateToClientIfNeeded(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), - getValueFromKvProto(tsKvProto.getKv()), pathIdVer); + attributesUpdate.forEach((pathIdVer, tsKvProto) -> { + ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config.getModelProvider()); + Object newValProto = getValueFromKvProto(tsKvProto.getKv()); + Object oldResourceValue = this.getResourceValueFormatKv(lwM2MClient, pathIdVer); + if (!resourceModel.multiple || !(newValProto instanceof JsonElement)) { + this.pushUpdateToClientIfNeeded(lwM2MClient, oldResourceValue, newValProto, pathIdVer, logFailedUpdateOfNonChangedValue); + } else { + pushUpdateMultiToClientIfNeeded(lwM2MClient, resourceModel, (JsonElement) newValProto, + (Map) oldResourceValue, pathIdVer); + } }); } - private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) { - if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { + private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, + String versionedId, boolean logFailedUpdateOfNonChangedValue) { + if (newValue == null) { + String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. New value is bad", + LOG_LWM2M_ERROR, versionedId, "null"); + logService.log(lwM2MClient, logMsg); + log.error("Failed update resource [{}] [{}]", versionedId, newValue); + } else if ((valueOld != null && newValue.toString().equals(valueOld.toString()))){ + if (logFailedUpdateOfNonChangedValue) { + String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed", + LOG_LWM2M_INFO, versionedId, newValue); + logService.log(lwM2MClient, logMsg); + log.info("Failed update resource [{}] [{}]. Value is not changed", versionedId, newValue); + } + } else { TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(clientContext.getRequestTimeout(lwM2MClient)).build(); downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId)); + } + } + + private void pushUpdateMultiToClientIfNeeded(LwM2mClient client, ResourceModel resourceModel, JsonElement newValProto, + Map valueOld, String versionedId) { + Map newValues = convertMultiResourceValuesFromJson((JsonObject)newValProto, resourceModel.type, versionedId); + if (newValues.size() > 0) { + if (valueOld != null && valueOld.size() > 0) { + valueOld.values().stream().forEach((v) -> { + if (newValues.containsKey(v.getId())) { + boolean isValueEquals; + if (OPAQUE.equals(v.getType())) { + isValueEquals = Arrays.equals((byte [])newValues.get(v.getId()), (byte[]) v.getValue()); + } else { + isValueEquals = newValues.get(v.getId()).toString().equals(v.getValue().toString()); + } + if (isValueEquals) { + newValues.remove(v.getId()); + } + } + }); + } + } + + if (newValues.size() > 0) { + TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValues).timeout(this.config.getTimeout()).build(); + downlinkHandler.sendWriteReplaceRequest(client, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId)); } else { - log.error("Failed update resource [{}] [{}]", versionedId, newValue); + log.error("Failed update resource [{}] [{}]", versionedId, newValProto); String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad", - LOG_LWM2M_ERROR, versionedId, newValue); - logService.log(lwM2MClient, logMsg); - log.info("Failed update resource [{}] [{}]", versionedId, newValue); + LOG_LWM2M_ERROR, versionedId, newValProto); + logService.log(client, logMsg); } } @@ -217,8 +280,14 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { if (resourceValue != null) { ResourceModel.Type currentType = resourceValue.getType(); ResourceModel.Type expectedType = helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); - return LwM2mValueConverterImpl.getInstance().convertValue(resourceValue.getValue(), currentType, expectedType, - new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); + if (!resourceValue.isMultiInstances()) { + return LwM2mValueConverterImpl.getInstance().convertValue(resourceValue.getValue(), currentType, expectedType, + new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); + } else if (resourceValue.getInstances().size() > 0) { + return resourceValue.getInstances(); + } else { + return null; + } } else { return null; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java index b9d7fa9e21..cce7a29c88 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java @@ -30,5 +30,5 @@ public interface LwM2MAttributesService { void onAttributesUpdate(TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification, TransportProtos.SessionInfoProto sessionInfo); - void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos); + void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos, boolean logFailedUpdateOfNonChangedValue); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 97a3d72c33..ff2757ba3b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -27,7 +27,7 @@ import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.codec.LwM2mValueConverter; import org.eclipse.leshan.core.request.ContentFormat; -import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.core.request.WriteRequest.Mode; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.Device; @@ -43,8 +43,10 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -57,10 +59,11 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import java.util.stream.Stream; -import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_OBJECT_VERSION_DEFAULT; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertMultiResourceValuesFromRpcBody; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; @@ -101,11 +104,11 @@ public class LwM2mClient implements Serializable { @Getter private Long psmActivityTimer; @Getter - private Long edrxCycle; - @Getter private Long pagingTransmissionWindow; @Getter @Setter + private Long edrxCycle; + @Getter private Registration registration; @Getter @Setter @@ -118,6 +121,10 @@ public class LwM2mClient implements Serializable { private boolean firstEdrxDownlink = true; + @Getter + Set clientSupportContentFormats; + @Getter + ContentFormat defaultContentFormat; @Getter private final AtomicInteger retryAttempts; @@ -147,6 +154,12 @@ public class LwM2mClient implements Serializable { this.pagingTransmissionWindow = credentials.getDeviceInfo().getPagingTransmissionWindow(); } + public void setRegistration(Registration registration) { + this.registration = registration; + this.clientSupportContentFormats = clientSupportContentFormat(registration); + this.defaultContentFormat = calculateDefaultContentFormat(registration); + } + public void lock() { lock.lock(); } @@ -210,15 +223,16 @@ public class LwM2mClient implements Serializable { .build(); } - public boolean saveResourceValue(String pathRezIdVer, LwM2mResource rez, LwM2mModelProvider modelProvider) { - if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null) { - this.resources.get(pathRezIdVer).setLwM2mResource(rez); + public boolean saveResourceValue(String pathRezIdVer, LwM2mResource resource, LwM2mModelProvider modelProvider, Mode mode) { + if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null && + resourceEqualsModel(resource, this.resources.get(pathRezIdVer).getResourceModel())) { + this.resources.get(pathRezIdVer).updateLwM2mResource(resource, mode); return true; } else { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); - if (resourceModel != null) { - this.resources.put(pathRezIdVer, new ResourceValue(rez, resourceModel)); + if (resourceModel != null && resourceEqualsModel(resource, resourceModel)) { + this.resources.put(pathRezIdVer, new ResourceValue(resource, resourceModel)); return true; } else { return false; @@ -226,6 +240,11 @@ public class LwM2mClient implements Serializable { } } + private boolean resourceEqualsModel(LwM2mResource resource, ResourceModel resourceModel) { + return ((!resourceModel.multiple && resource instanceof LwM2mSingleResource) || + (resourceModel.multiple && resource instanceof LwM2mMultipleResource)); + } + public Object getResourceValue(String pathRezIdVer, String pathRezId) { String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer; if (this.resources.get(pathRez) != null) { @@ -282,7 +301,6 @@ public class LwM2mClient implements Serializable { } - public Collection getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, LwM2mValueConverter converter) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); @@ -299,31 +317,61 @@ public class LwM2mClient implements Serializable { return resources; } + /** + * The instance must have all the resources that have the property + * Mandatory + */ public Collection getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, LwM2mValueConverter converter) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); Collection resources = ConcurrentHashMap.newKeySet(); Map resourceModels = modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()).resources; - resourceModels.forEach((resId, resourceModel) -> { - if (((Map) params).containsKey(String.valueOf(resId))) { - Object value = ((Map) params).get((String.valueOf(resId))); - resources.add(LwM2mSingleResource.newResource(resId, - converter.convertValue(value, equalsResourceTypeGetSimpleName(value), resourceModel.type, pathIds), resourceModel.type)); - - } - }); + if (params != null) { + resourceModels.forEach((resourceId, resourceModel) -> { + if (((Map) params).containsKey(String.valueOf(resourceId))) { + Object value = ((Map) params).get((String.valueOf(resourceId))); + LwM2mResource resource = null; + if (resourceModel.multiple) { + if (value instanceof LinkedHashMap) { + Map values = convertMultiResourceValuesFromRpcBody((LinkedHashMap) value, resourceModel.type, pathRezIdVer); + resource = LwM2mMultipleResource.newResource(resourceId, (Map) values, resourceModel.type); + } + } else { + Object valueRez = value.getClass().getSimpleName().equals("Integer") ? ((Integer) value).longValue() : value; + resource = LwM2mSingleResource.newResource(resourceId, + converter.convertValue(valueRez, equalsResourceTypeGetSimpleName(value), resourceModel.type, pathIds), resourceModel.type); + } + if (resource != null) { + resources.add(resource); + } else if (resourceModel.operations.isWritable() && resourceModel.mandatory) { + throw new IllegalArgumentException("Resource id=" + resourceId + " is mandatory. The value of this resource must not be null."); + } + } + else if (resourceModel.operations.isWritable() && resourceModel.mandatory) { + throw new IllegalArgumentException("Resource id=" + resourceId + " is mandatory. The value of this resource must not be null."); + } + }); + } + else { + throw new IllegalArgumentException("The value of this resource must not be null."); + } return resources; } - public void isValidObjectVersion(String path) { + public String isValidObjectVersion(String path) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); - String verRez = getVerFromPathIdVerOrId(path); - if ((verRez != null && !verRez.equals(verSupportedObject)) || - (verRez == null && !LWM2M_OBJECT_VERSION_DEFAULT.equals(verSupportedObject))) { - throw new IllegalArgumentException(String.format("Specified resource id %s is not valid version! Must be version: %s", path, verSupportedObject)); + if (verSupportedObject == null) { + return String.format("Specified resource id %s is missing from the client!", path); + } else { + String verRez = getVerFromPathIdVerOrId(path); + if ((verRez != null && !verRez.equals(verSupportedObject)) || + (verRez == null && !LWM2M_OBJECT_VERSION_DEFAULT.equals(verSupportedObject))) { + return String.format("Specified resource id %s is not valid version! Must be version: %s", path, verSupportedObject); + } } + return ""; } /** @@ -365,12 +413,33 @@ public class LwM2mClient implements Serializable { .collect(Collectors.toSet()); } - public ContentFormat getDefaultContentFormat() { + private ContentFormat calculateDefaultContentFormat(Registration registration) { if (registration == null) { return ContentFormat.DEFAULT; + } else{ + return LwM2mVersion.fromVersion(registration.getLwM2mVersion()).getContentFormat(); + } + } + + private Set clientSupportContentFormat(Registration registration) { + Set contentFormats = new HashSet<>(); + if (registration == null) { + contentFormats.add(ContentFormat.DEFAULT); } else { - return LwM2mVersion.fromVersionStr(registration.getLwM2mVersion()).getContentFormat(); + String code = Arrays.stream(registration.getObjectLinks()).filter(link -> link.getUrl().equals("/")). + findFirst().get().getAttributes().get("ct"); + if (code != null) { + Set codes = Stream.of(code.replaceAll("\"", "").split(" ", -1)) + .map(String::trim) + .map(Integer::parseInt) + .map(ContentFormat::fromCode) + .collect(Collectors.toSet()); + contentFormats.addAll(codes); + } else { + contentFormats.add(ContentFormat.DEFAULT); + } } + return contentFormats; } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java index c05148017c..5b720a8b66 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.client; +import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; @@ -51,7 +52,7 @@ public interface LwM2mClientContext { LwM2mClient getClientByDeviceId(UUID deviceId); - String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName); + String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName, boolean isCompositeOperation); void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials); @@ -61,7 +62,7 @@ public interface LwM2mClientContext { void sendMsgsAfterSleeping(LwM2mClient lwM2MClient); - boolean isComposite(LwM2mClient client); + ContentFormat getContentFormatComposite(LwM2mClient client); void onUplink(LwM2mClient client); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index e29dcdd1dd..ebf9c11155 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -18,8 +18,8 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; -import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.server.registration.Registration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; @@ -36,7 +36,6 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -59,8 +58,7 @@ import java.util.function.Predicate; import static org.eclipse.leshan.core.SecurityMode.NO_SEC; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getMsgException; @Slf4j @Service @@ -291,12 +289,13 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } @Override - public String getObjectIdByKeyNameFromProfile(LwM2mClient client, String keyName) { + public String getObjectIdByKeyNameFromProfile(LwM2mClient client, String keyName, boolean isCompositeOperation) { Lwm2mDeviceProfileTransportConfiguration profile = getProfile(client.getProfileId()); - + Set msgException = ConcurrentHashMap.newKeySet(); + msgException.add(""); return profile.getObserveAttr().getKeyName().entrySet().stream() - .filter(e -> e.getValue().equals(keyName) && validateResourceInModel(client, e.getKey(), false)).findFirst().orElseThrow( - () -> new IllegalArgumentException(keyName + " is not configured in the device profile!") + .filter(e -> e.getValue().equals(keyName) && (isCompositeOperation || !msgException.add(client.isValidObjectVersion(e.getKey())))).findFirst().orElseThrow( + () -> new IllegalArgumentException(getMsgException (keyName, msgException)) ).getKey(); } @@ -336,7 +335,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { if (LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) { PowerMode powerMode = getPowerMode(lwM2MClient); if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) { - defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient); + defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient, false); TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg .newBuilder() .setSessionInfo(lwM2MClient.getSession()) @@ -412,9 +411,9 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } @Override - public boolean isComposite(LwM2mClient client) { - return LwM2mVersion.fromVersionStr(client.getRegistration().getLwM2mVersion()).isComposite() & - getProfile(client.getProfileId()).getClientLwM2mSettings().isCompositeOperationsSupport(); + public ContentFormat getContentFormatComposite(LwM2mClient client) { + return client.getClientSupportContentFormats().contains(ContentFormat.SENML_JSON) ? ContentFormat.SENML_JSON : + client.getClientSupportContentFormats().contains(ContentFormat.SENML_CBOR) ? ContentFormat.SENML_CBOR : null; } @Override @@ -541,15 +540,4 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } return timeout; } - - private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { - ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config - .getModelProvider()); - Integer objectId = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)).getObjectId(); - String objectVer = validateObjectVerFromKey(pathIdVer); - return resourceModel != null && (isWritableNotOptional ? - objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() : - objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId))); - } - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java index 7f61caf58a..700c589202 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java @@ -16,13 +16,20 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResourceInstance; +import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.request.WriteRequest.Mode; import java.io.Serializable; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; +@Slf4j @Data public class ResourceValue implements Serializable { @@ -32,32 +39,35 @@ public class ResourceValue implements Serializable { private TbResourceModel resourceModel; public ResourceValue(LwM2mResource lwM2mResource, ResourceModel resourceModel) { - this.lwM2mResource = toTbLwM2MResource(lwM2mResource); this.resourceModel = toTbResourceModel(resourceModel); + updateLwM2mResource(lwM2mResource, Mode.UPDATE); } - public void setLwM2mResource(LwM2mResource lwM2mResource) { - this.lwM2mResource = toTbLwM2MResource(lwM2mResource); + public void updateLwM2mResource(LwM2mResource lwM2mResource, Mode mode) { + if (lwM2mResource instanceof LwM2mSingleResource) { + this.lwM2mResource = new TbLwM2MSingleResource(lwM2mResource.getId(), lwM2mResource.getValue(), lwM2mResource.getType()); + + } else if (lwM2mResource instanceof LwM2mMultipleResource) { + if (lwM2mResource.getInstances().values().size() > 0) { + Set instancesSet = lwM2mResource.getInstances().values().stream().map(ResourceValue::toTbLwM2MResourceInstance).collect(Collectors.toSet()); + if (Mode.REPLACE.equals(mode) && this.lwM2mResource != null) { + Map oldInstances = this.lwM2mResource.getInstances(); + oldInstances.values().forEach(v -> { + if (instancesSet.stream().filter(vIns -> v.getId() == vIns.getId()).collect(Collectors.toList()).size() == 0){ + instancesSet.add(toTbLwM2MResourceInstance(v)); + } + }); + } + TbLwM2MResourceInstance[] instances = instancesSet.toArray(new TbLwM2MResourceInstance[0]); + this.lwM2mResource = new TbLwM2mMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), instances); + } + } } public void setResourceModel(ResourceModel resourceModel) { this.resourceModel = toTbResourceModel(resourceModel); } - private static TbLwM2MResource toTbLwM2MResource(LwM2mResource lwM2mResource) { - if (lwM2mResource.isMultiInstances()) { - if ( lwM2mResource.getInstances().values().size() > 0) { - TbLwM2MResourceInstance [] instances = lwM2mResource.getInstances().values().stream().map(ResourceValue::toTbLwM2MResourceInstance).collect(Collectors.toSet()).toArray(new TbLwM2MResourceInstance[0]); - return new TbLwM2MMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), instances); - } - else { - return new TbLwM2MMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), new TbLwM2MResourceInstance[0]); - } - } else { - return new TbLwM2MSingleResource(lwM2mResource.getId(), lwM2mResource.getValue(), lwM2mResource.getType()); - } - } - private static TbLwM2MResourceInstance toTbLwM2MResourceInstance(LwM2mResourceInstance instance) { return new TbLwM2MResourceInstance(instance.getId(), instance.getValue(), instance.getType()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MMultipleResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java similarity index 82% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MMultipleResource.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java index ad5431b383..dce642f336 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MMultipleResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java @@ -17,16 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.client; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mMultipleResource; -import org.eclipse.leshan.core.node.LwM2mResourceInstance; import java.io.Serializable; -import java.util.Collection; -public class TbLwM2MMultipleResource extends LwM2mMultipleResource implements TbLwM2MResource, Serializable { +public class TbLwM2mMultipleResource extends LwM2mMultipleResource implements TbLwM2MResource, Serializable { private static final long serialVersionUID = 4658477128628087186L; - public TbLwM2MMultipleResource(int id, ResourceModel.Type type, TbLwM2MResourceInstance... instances) { + public TbLwM2mMultipleResource(int id, ResourceModel.Type type, TbLwM2MResourceInstance... instances) { super(id, type, instances); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java index 1f1dff784f..52dd7221d1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.server.common; import org.thingsboard.common.util.ThingsBoardExecutors; -import javax.annotation.PreDestroy; import java.util.concurrent.ExecutorService; public abstract class LwM2MExecutorAwareService { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index 82ea0c5fbe..5a6ac6b466 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -18,16 +18,19 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.LwM2m; import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.AttributeSet; +import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.ObjectLink; -import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.request.CompositeDownlinkRequest; import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DownlinkRequest; @@ -40,7 +43,9 @@ import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.exception.ClientSleepingException; +import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.eclipse.leshan.core.request.exception.TimeoutException; +import org.eclipse.leshan.core.response.CreateResponse; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ExecuteResponse; @@ -55,7 +60,6 @@ import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; @@ -73,9 +77,12 @@ import javax.annotation.PreDestroy; import java.util.Arrays; import java.util.Collection; import java.util.Date; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -85,7 +92,11 @@ import static org.eclipse.leshan.core.attributes.Attribute.LESSER_THAN; import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD; import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; import static org.eclipse.leshan.core.attributes.Attribute.STEP; +import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK; +import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertMultiResourceValuesFromRpcBody; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateVersionedId; @Slf4j @Service @@ -123,40 +134,49 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback) { - validateVersionedId(client, request); - ReadRequest downlink = new ReadRequest(getRequestContentFormat(client, request, this.config.getModelProvider()), request.getObjectId()); - sendSimpleRequest(client, downlink, request.getTimeout(), callback); + try { + validateVersionedId(client, request); + ReadRequest downlink = new ReadRequest(getReadRequestContentFormat(client, request, this.config.getModelProvider()), request.getObjectId()); + sendSimpleRequest(client, downlink, request.getTimeout(), callback); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); + } } @Override - public void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback callback) { - validateVersionedIds(client, request); - ContentFormat requestContentFormat = ContentFormat.SENML_JSON; - ContentFormat responseContentFormat = ContentFormat.SENML_JSON; - - ReadCompositeRequest downlink = new ReadCompositeRequest(requestContentFormat, responseContentFormat, request.getObjectIds()); - sendCompositeRequest(client, downlink, request.getTimeout(), callback); + public void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, + DownlinkRequestCallback callback, ContentFormat contentFormatComposite) { + try { + ReadCompositeRequest downlink = new ReadCompositeRequest(contentFormatComposite, contentFormatComposite, request.getObjectIds()); + sendCompositeRequest(client, downlink, this.config.getTimeout(), callback); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); + } } @Override public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback) { - validateVersionedId(client, request); - LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); - Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); - if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { - ObserveRequest downlink; - ContentFormat contentFormat = getRequestContentFormat(client, request, this.config.getModelProvider()); - if (resultIds.isResource()) { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); - } else if (resultIds.isObjectInstance()) { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); + try { + validateVersionedId(client, request); + LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); + Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); + if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { + ObserveRequest downlink; + ContentFormat contentFormat = getReadRequestContentFormat(client, request, this.config.getModelProvider()); + if (resultIds.isResource()) { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); + } else if (resultIds.isObjectInstance()) { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); + } else { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); + } + log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); + sendSimpleRequest(client, downlink, request.getTimeout(), callback); } else { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); + callback.onValidationError(resultIds.toString(), "Observation is already registered!"); } - log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); - sendSimpleRequest(client, downlink, request.getTimeout(), callback); - } else { - callback.onValidationError(resultIds.toString(), "Observation is already registered!"); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); } } @@ -174,25 +194,36 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback callback) { - ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); - if (resourceModelExecute != null) { - ExecuteRequest downlink; - if (request.getParams() != null && !resourceModelExecute.multiple) { - downlink = new ExecuteRequest(request.getVersionedId(), (String) this.converter.convertValue(request.getParams(), resourceModelExecute.type, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId()))); - } else { - downlink = new ExecuteRequest(request.getVersionedId()); + try { + ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); + if (resourceModelExecute != null) { + validateVersionedId(client, request); + ExecuteRequest downlink; + if (request.getParams() != null && !resourceModelExecute.multiple) { + downlink = new ExecuteRequest(request.getObjectId(), (String) this.converter.convertValue(request.getParams(), resourceModelExecute.type, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId()))); + } else { + downlink = new ExecuteRequest(request.getObjectId()); + } + sendSimpleRequest(client, downlink, request.getTimeout(), callback); } - sendSimpleRequest(client, downlink, request.getTimeout(), callback); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); } } @Override public void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback callback) { - sendSimpleRequest(client, new DeleteRequest(request.getObjectId()), request.getTimeout(), callback); + try { + validateVersionedId(client, request); + sendSimpleRequest(client, new DeleteRequest(request.getObjectId()), request.getTimeout(), callback); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); + } } @Override public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback callback) { + validateVersionedId(client, request); int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId()); callback.onSuccess(request, observeCancelCnt); } @@ -209,51 +240,86 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im sendSimpleRequest(client, new DiscoverRequest(request.getObjectId()), request.getTimeout(), callback); } + /** + * Example # 1: + * AttributeSet attributes = new AttributeSet(new Attribute(Attribute.MINIMUM_PERIOD, 10L), + * new Attribute(Attribute.MAXIMUM_PERIOD, 100L)); + * WriteAttributesRequest requestTest = new WriteAttributesRequest(3, 0, 14, attributes); + * sendSimpleRequest(client, requestTest, request.getTimeout(), callback); + *

+ * Example # 2 + * Dimension and Object version are read only attributes. + * addAttribute(attributes, DIMENSION, params.getDim(), dim -> dim >= 0 && dim <= 255); + * addAttribute(attributes, OBJECT_VERSION, params.getVer(), StringUtils::isNotEmpty, Function.identity()); + */ @Override public void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback) { - validateVersionedId(client, request); - if (request.getAttributes() == null) { - throw new IllegalArgumentException("Attributes to write are not specified!"); + try { + validateVersionedId(client, request); + if (request.getAttributes() == null) { + throw new IllegalArgumentException("Attributes to write are not specified!"); + } + ObjectAttributes params = request.getAttributes(); + List attributes = new LinkedList<>(); + addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); + addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); + addAttribute(attributes, GREATER_THAN, params.getGt()); + addAttribute(attributes, LESSER_THAN, params.getLt()); + addAttribute(attributes, STEP, params.getSt()); + AttributeSet attributeSet = new AttributeSet(attributes); + sendSimpleRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); } - ObjectAttributes params = request.getAttributes(); - List attributes = new LinkedList<>(); -// Dimension and Object version are read only attributes. -// addAttribute(attributes, DIMENSION, params.getDim(), dim -> dim >= 0 && dim <= 255); -// addAttribute(attributes, OBJECT_VERSION, params.getVer(), StringUtils::isNotEmpty, Function.identity()); - addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); - addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); - addAttribute(attributes, GREATER_THAN, params.getGt()); - addAttribute(attributes, LESSER_THAN, params.getLt()); - addAttribute(attributes, STEP, params.getSt()); - AttributeSet attributeSet = new AttributeSet(attributes); - sendSimpleRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); } @Override public void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback) { - ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); - if (resourceModelWrite != null) { - ContentFormat contentFormat = convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); - try { - LwM2mPath path = new LwM2mPath(request.getObjectId()); - WriteRequest downlink = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat, - path.getObjectId(), path.getObjectInstanceId(), path.getResourceId(), request.getValue()); - sendSimpleRequest(client, downlink, request.getTimeout(), callback); - } catch (Exception e) { - callback.onError(toString(request), e); + LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); + if (resultIds.isResource() || resultIds.isResourceInstance()) { + validateVersionedId(client, request); + ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); + if (resourceModelWrite != null) { + ContentFormat contentFormat = getWriteRequestContentFormat(client, request, this.config.getModelProvider()); + try { + WriteRequest downlink = null; + if (resourceModelWrite.multiple) { + if (request.getValue() instanceof Map && ((Map) request.getValue()).size() > 0) { + downlink = new WriteRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), + (Map) request.getValue(), resourceModelWrite.type); + } else { + callback.onValidationError(toString(request), "Resource value is: " + request.getValue().getClass().getSimpleName() + ". Value of Multi-Instance Resource must be in Json format!"); + } + } else { + downlink = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat, + resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), request.getValue()); + } + if (downlink != null) { + sendSimpleRequest(client, downlink, request.getTimeout(), callback); + } else { + callback.onValidationError(toString(request), "WriteRequest is null."); + } + } catch (Exception e) { + callback.onError(toString(request), e); + } + + } else { + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!"); } } else { - callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!"); + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + ". This operation can only be used for Resource or ResourceInstance!"); } } @Override - public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest, DownlinkRequestCallback callback) { - ContentFormat contentFormat = ContentFormat.SENML_JSON; + public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest, + DownlinkRequestCallback callback, ContentFormat contentFormatComposite) { try { - WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormat, rpcWriteCompositeRequest.getNodes()); + WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormatComposite, rpcWriteCompositeRequest.getNodes()); //TODO: replace config.getTimeout(); - sendWriteCompositeRequest(client, downlink, config.getTimeout(), callback); + sendWriteCompositeRequest(client, downlink, this.config.getTimeout(), callback); + } catch (InvalidRequestException e) { + callback.onValidationError(rpcWriteCompositeRequest.toString(), e.getMessage()); } catch (Exception e) { callback.onError(toString(rpcWriteCompositeRequest), e); } @@ -261,38 +327,90 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback) { + try { + validateVersionedId(client, request); + WriteRequest downlink = null; + LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); + ContentFormat contentFormat = getWriteRequestContentFormat(client, request, this.config.getModelProvider()); + if (resultIds.isObjectInstance()) { + /* + * params = "{\"id\":0,\"value\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" + * int rscId = resultIds.getObjectInstanceId(); + * contentFormat – Format of the payload (TLV or JSON). + */ + Collection resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); + if (resources.size() > 0) { + downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); + } else { + callback.onValidationError(toString(request), "No resources to update!"); + } + } else if (resultIds.isResource()) { + ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); + if (resourceModelWrite.multiple) { + if (request.getValue() instanceof Map && ((Map) request.getValue()).size() > 0) { + Map value = convertMultiResourceValuesFromRpcBody((LinkedHashMap) request.getValue(), resourceModelWrite.type, request.getObjectId()); + downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), + value, resourceModelWrite.type); + } else { + callback.onValidationError(toString(request), "Resource value is bad. Format: " + request.getValue().getClass().getSimpleName() + ". Value of Multi-Instance Resource must be in Json format!"); + } + } + } + if (downlink != null) { + sendSimpleRequest(client, downlink, request.getTimeout(), callback); + } else { + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + ". This operation can only be used for ObjectInstance or Multi-Instance Resource !"); + } + } catch (Exception e) { + callback.onValidationError(toString(request), e.getMessage()); + } + } + + public void sendCreateRequest(LwM2mClient client, TbLwM2MCreateRequest request, DownlinkRequestCallback callback) { + validateVersionedId(client, request); + CreateRequest downlink = null; LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); - if (resultIds.isResource()) { - /* - * send request: path = '/3/0' node == wM2mObjectInstance - * with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} - **/ - Collection resources = client.getNewResourceForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); - ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); - ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); - WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), - resultIds.getObjectInstanceId(), resources); - sendSimpleRequest(client, downlink, request.getTimeout(), callback); - } else if (resultIds.isObjectInstance()) { - /* - * params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" - * int rscId = resultIds.getObjectInstanceId(); - * contentFormat – Format of the payload (TLV or JSON). - */ - Collection resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); - if (resources.size() > 0) { - ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : ContentFormat.DEFAULT; - WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); + ObjectModel objectModel = client.getObjectModel(request.getObjectId(), this.config.getModelProvider()); + // POST /{Object ID}/{Object Instance ID} && Resources is Mandatory + if (objectModel.multiple) { + // LwM2M CBOR, SenML CBOR, SenML JSON, or TLV (see [LwM2M-CORE]) + ContentFormat contentFormat = getWriteRequestContentFormat(client, request, this.config.getModelProvider()); + if (resultIds.isObject() || resultIds.isObjectInstance()) { + Collection resources; + if (resultIds.isObject()) { +// contentFormat = ContentFormat.TLV; + if (request.getValue() != null) { + resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); + downlink = new CreateRequest(contentFormat, resultIds.getObjectId(), resources); + } else if (request.getNodes() != null && request.getNodes().size() > 0) { + Set instances = ConcurrentHashMap.newKeySet(); + request.getNodes().forEach((key, value) -> { + Collection resourcesForInstance = client.getNewResourcesForInstance(request.getVersionedId(), value, this.config.getModelProvider(), this.converter); + LwM2mObjectInstance instance = new LwM2mObjectInstance(Integer.parseInt(key), resourcesForInstance); + instances.add(instance); + }); + LwM2mObjectInstance[] instanceArrays = instances.toArray(new LwM2mObjectInstance[instances.size()]); + downlink = new CreateRequest(contentFormat, resultIds.getObjectId(), instanceArrays); + } + + } else { + resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); + LwM2mObjectInstance instance = new LwM2mObjectInstance(resultIds.getObjectInstanceId(), resources); + downlink = new CreateRequest(contentFormat, resultIds.getObjectId(), instance); + } + } + if (downlink != null) { sendSimpleRequest(client, downlink, request.getTimeout(), callback); } else { - callback.onValidationError(toString(request), "No resources to update!"); + callback.onValidationError(toString(request), "Path " + request.getVersionedId() + + ". This operation can only be used for created new ObjectInstance !"); } } else { - callback.onValidationError(toString(request), "Update of the root level object is not supported yet!"); + throw new IllegalArgumentException("Path " + request.getVersionedId() + + ". Object must be Multiple !"); } } - private , T extends LwM2mResponse> void sendSimpleRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback callback) { sendRequest(client, request, timeoutInMs, callback, r -> request.getPath().toString()); } @@ -301,6 +419,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im sendRequest(client, request, timeoutInMs, callback, r -> request.getPaths().toString()); } + private , T extends LwM2mResponse> void sendRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback callback, Function pathToStringFunction) { if (!clientContext.isDownlinkAllowed(client)) { log.trace("[{}] ignore downlink request cause client is sleeping.", client.getEndpoint()); @@ -319,7 +438,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im clientContext.awake(client); } }); - },e -> handleDownlinkError(client, request, callback, e)); + }, e -> handleDownlinkError(client, request, callback, e)); } catch (Exception e) { handleDownlinkError(client, request, callback, e); } @@ -366,6 +485,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im }); } + private WriteRequest getWriteRequestSingleResource(ResourceModel.Type type, ContentFormat contentFormat, int objectId, int instanceId, int resourceId, Object value) { switch (type) { case STRING: // String @@ -395,24 +515,6 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } - private void validateVersionedId(LwM2mClient client, HasVersionedId request) { - client.isValidObjectVersion(request.getVersionedId()); - if (request.getObjectId() == null) { - throw new IllegalArgumentException("Specified object id is null!"); - } - } - - private void validateVersionedIds(LwM2mClient client, HasVersionedIds request) { - for (String versionedId : request.getVersionedIds()) { - client.isValidObjectVersion(versionedId); - } - for (String objectId : request.getObjectIds()) { - if (objectId == null) { - throw new IllegalArgumentException("Specified object id is null!"); - } - } - } - private static void addAttribute(List attributes, String attributeName, T value) { addAttribute(attributes, attributeName, value, null, null); } @@ -427,52 +529,91 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } - private static ContentFormat convertResourceModelTypeToContentFormat(LwM2mClient client, ResourceModel.Type type) { - switch (type) { - case BOOLEAN: - case STRING: - case TIME: - case INTEGER: - case FLOAT: - return client.getDefaultContentFormat(); - case OPAQUE: - return ContentFormat.OPAQUE; - case OBJLNK: - return ContentFormat.LINK; - default: + private static ContentFormat getReadRequestContentFormat(LwM2mClient client, T request, LwM2mModelProvider modelProvider) { + if (request.getRequestContentFormat().isPresent()) { + return request.getRequestContentFormat().get(); + } else { + return getRequestContentFormat(client, request.getVersionedId(), modelProvider); } - throw new CodecException("Invalid ResourceModel_Type for %s ContentFormat.", type); } - private static ContentFormat getRequestContentFormat(LwM2mClient client, HasContentFormat request, LwM2mModelProvider modelProvider) { - if (request.getRequestContentFormat() != null) { - return request.getRequestContentFormat(); + private static ContentFormat getWriteRequestContentFormat(LwM2mClient client, TbLwM2MDownlinkRequest request, LwM2mModelProvider modelProvider) { + if (request instanceof TbLwM2MWriteReplaceRequest && ((TbLwM2MWriteReplaceRequest) request).getContentFormat() != null) { + return ((TbLwM2MWriteReplaceRequest) request).getContentFormat(); + } else if (request instanceof TbLwM2MWriteUpdateRequest && ((TbLwM2MWriteUpdateRequest) request).getObjectContentFormat() != null) { + return ((TbLwM2MWriteUpdateRequest) request).getObjectContentFormat(); } else { String versionedId = null; - if (request instanceof TbLwM2MReadRequest) { - versionedId = ((TbLwM2MReadRequest) request).getVersionedId(); - } else if (request instanceof TbLwM2MObserveRequest) { - versionedId = ((TbLwM2MObserveRequest) request).getVersionedId(); + if (request instanceof TbLwM2MWriteReplaceRequest) { + versionedId = ((TbLwM2MWriteReplaceRequest) request).getVersionedId(); + } else if (request instanceof TbLwM2MWriteUpdateRequest) { + versionedId = ((TbLwM2MWriteUpdateRequest) request).getVersionedId(); + } else if (request instanceof TbLwM2MCreateRequest) { + versionedId = ((TbLwM2MCreateRequest) request).getVersionedId(); + } + return getRequestContentFormat(client, versionedId, modelProvider); + } + } + + private static ContentFormat getRequestContentFormat(LwM2mClient client, String versionedId, LwM2mModelProvider modelProvider) { + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(versionedId)); + if (pathIds.isResourceInstance()) { + ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); + if (OBJLNK.equals(resourceModel.type)) { + return ContentFormat.LINK; + } else if (OPAQUE.equals(resourceModel.type)) { + return ContentFormat.OPAQUE; + } else { + return findFirst(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON); + } + } else if (pathIds.isResource()) { + ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); + if (!resourceModel.multiple) { + if (OBJLNK.equals(resourceModel.type)) { + return ContentFormat.LINK; + } else if (OPAQUE.equals(resourceModel.type)) { + return ContentFormat.OPAQUE; + } else { + return findFirst(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON); + } + } else { + return getContentFormatForComplex(client); } - String id = fromVersionedIdToObjectId(versionedId); - if (id != null && new LwM2mPath(id).isResource() && !client.isResourceMultiInstances(versionedId, modelProvider)) { - return client.getDefaultContentFormat(); + } else { + return getContentFormatForComplex(client); + } + } + + private static ContentFormat getContentFormatForComplex(LwM2mClient client) { + if (LwM2m.Version.V1_0.equals(client.getRegistration().getLwM2mVersion())) { + return ContentFormat.TLV; + } else if (LwM2m.Version.V1_1.equals(client.getRegistration().getLwM2mVersion())) { + ContentFormat result = findFirst(client.getClientSupportContentFormats(), null, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON, ContentFormat.TLV, ContentFormat.JSON); + if (result != null) { + return result; } else { - return ContentFormat.DEFAULT; + throw new RuntimeException("The client does not support any of SenML CBOR, SenML JSON, TLV or JSON formats. Can't send complex requests. Try using singe-instance requests."); + } + } else { + throw new RuntimeException("The version " + client.getRegistration().getLwM2mVersion() + " is not supported!"); + } + } + + private static ContentFormat findFirst(Set supported, ContentFormat defaultValue, ContentFormat... desiredFormats) { + for (ContentFormat contentFormat : desiredFormats) { + if (supported.contains(contentFormat)) { + return contentFormat; } } + return defaultValue; } private String toString(R request) { try { - try { - return JacksonUtil.toString(request); - } catch (Exception e) { - return request.toString(); - } + return request != null ? request.toString() : ""; } catch (Exception e) { - log.warn("Failed to convert request to string", e); - return request != null ? request.getClass().getSimpleName() : ""; + log.trace("Failed to convert request to string", e); + return request.getClass().getSimpleName(); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java index f2618224e7..833ff76cab 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java @@ -17,9 +17,11 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.ContentFormat; +import java.util.Optional; + public interface HasContentFormat { - ContentFormat getRequestContentFormat(); + Optional getRequestContentFormat(); default ContentFormat getResponseContentFormat() { return null; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java index 871a59a63b..a4537af703 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java @@ -16,6 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.ExecuteRequest; @@ -25,6 +27,7 @@ import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.response.CreateResponse; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ExecuteResponse; @@ -45,7 +48,7 @@ public interface LwM2mDownlinkMsgHandler { void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback); - void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback callback); + void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback callback, ContentFormat contentFormatComposite); void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback); @@ -67,9 +70,10 @@ public interface LwM2mDownlinkMsgHandler { void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback); - void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback callback); + void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback callback, ContentFormat contentFormatComposite); void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback); + void sendCreateRequest(LwM2mClient client, TbLwM2MCreateRequest request, DownlinkRequestCallback callback); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java index 311be03462..b663215078 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java @@ -16,12 +16,10 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL; @Slf4j public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateRequest.java new file mode 100644 index 0000000000..9eed9381a8 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateRequest.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.downlink; + +import lombok.Builder; +import lombok.Getter; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.response.CreateResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; + +import java.util.Map; + +public class TbLwM2MCreateRequest extends AbstractTbLwM2MTargetedDownlinkRequest { + + @Getter + private final Object value; + @Getter + private final ContentFormat objectContentFormat; + @Getter + private final Map nodes;; + + @Builder + private TbLwM2MCreateRequest(String versionedId, long timeout, Object value, ContentFormat objectContentFormat, Map nodes) { + super(versionedId, timeout); + this.value = value; + this.objectContentFormat = objectContentFormat; + this.nodes = nodes; + } + + @Override + public LwM2mOperationType getType() { + return LwM2mOperationType.CREATE; + } + + + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateResponseCallback.java new file mode 100644 index 0000000000..316bbd4636 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateResponseCallback.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.downlink; + +import org.eclipse.leshan.core.request.CreateRequest; +import org.eclipse.leshan.core.response.CreateResponse; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; + +public class TbLwM2MCreateResponseCallback extends TbLwM2MUplinkTargetedCallback { + + public TbLwM2MCreateResponseCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { + super(handler, logService, client, targetId); + } + + @Override + public void onSuccess(CreateRequest request, CreateResponse response) { + super.onSuccess(request, response); + handler.onCreateResponseOk(client, versionedId, request); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java index 4c0a758572..2b3c544dae 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java @@ -19,7 +19,6 @@ import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.response.DeleteResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; public class TbLwM2MDeleteCallback extends TbLwM2MTargetedCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java index 41172f0599..cc855fd3a8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java @@ -17,9 +17,8 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.response.DiscoverResponse; -import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; public class TbLwM2MDiscoverCallback extends TbLwM2MTargetedCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java index 992cf6d616..9315e9ded6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java @@ -17,9 +17,8 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.response.ExecuteResponse; -import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; public class TbLwM2MExecuteCallback extends TbLwM2MTargetedCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java index dde49c870d..97be9fbff3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java @@ -18,9 +18,9 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.response.ObserveResponse; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; @Slf4j public class TbLwM2MObserveCallback extends TbLwM2MUplinkTargetedCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java index 0d13b4a1ff..849d0dd2bf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java @@ -16,20 +16,20 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; -import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.ObserveResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import java.util.Optional; + public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest implements HasContentFormat { - @Getter - private final ContentFormat requestContentFormat; + private final Optional requestContentFormat; @Builder private TbLwM2MObserveRequest(String versionedId, long timeout, ContentFormat requestContentFormat) { super(versionedId, timeout); - this.requestContentFormat = requestContentFormat; + this.requestContentFormat = Optional.ofNullable(requestContentFormat); } @Override @@ -38,5 +38,8 @@ public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkReques } - + @Override + public Optional getRequestContentFormat() { + return this.requestContentFormat; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java index 6f388237c2..39c4b0fb4b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java @@ -16,20 +16,20 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; -import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import java.util.Optional; + public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest implements HasContentFormat { - @Getter - private final ContentFormat requestContentFormat; + private final Optional requestContentFormat; @Builder private TbLwM2MReadRequest(String versionedId, long timeout, ContentFormat requestContentFormat) { super(versionedId, timeout); - this.requestContentFormat = requestContentFormat; + this.requestContentFormat = Optional.ofNullable(requestContentFormat); } @Override @@ -38,5 +38,8 @@ public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest getRequestContentFormat() { + return this.requestContentFormat; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java index 6b013ef9b5..92992d299c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java @@ -16,6 +16,9 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.util.Hex; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; @@ -45,8 +48,28 @@ public abstract class TbLwM2MTargetedCallback extends AbstractTbLwM2MReque public void onSuccess(R request, T response) { //TODO convert camelCase to "camel case" using .split("(? 0) { + int len = ((byte[])((LwM2mSingleResource) response.getContent()).getValue()).length; + String valueReplace = len + "Bytes"; + String valueStr = Hex.encodeHexString((byte[]) (((LwM2mSingleResource) response.getContent()).getValue())); + return response.toString().replace(valueReplace, valueStr); + } + } + return response.toString(); + } + else { + return response.toString(); + } + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java index e346ad965a..3e4847ce1e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java @@ -19,7 +19,6 @@ import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; public class TbLwM2MWriteAttributesCallback extends TbLwM2MTargetedCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java index 43b55f371f..086a3d7049 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java @@ -22,10 +22,12 @@ import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; import org.thingsboard.server.transport.lwm2m.server.downlink.HasContentFormat; +import java.util.Optional; + public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlinkCompositeRequest implements HasContentFormat { - @Getter - private final ContentFormat requestContentFormat; + + private final Optional requestContentFormatOpt; @Getter private final ContentFormat responseContentFormat; @@ -33,7 +35,7 @@ public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlink @Builder private TbLwM2MReadCompositeRequest(String [] versionedIds, long timeout, ContentFormat requestContentFormat, ContentFormat responseContentFormat) { super(versionedIds, timeout); - this.requestContentFormat = requestContentFormat; + this.requestContentFormatOpt = Optional.ofNullable(requestContentFormat); this.responseContentFormat = responseContentFormat; } @@ -41,4 +43,9 @@ public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlink public LwM2mOperationType getType() { return LwM2mOperationType.READ_COMPOSITE; } + + @Override + public Optional getRequestContentFormat() { + return this.requestContentFormatOpt; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java index eb5dae8f4a..4e59eb5b44 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java @@ -21,7 +21,6 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 2776b64fef..8335057caa 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -180,10 +180,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } var clientSettings = clientContext.getProfile(client.getProfileId()).getClientLwM2mSettings(); - onFirmwareStrategyUpdate(client, clientSettings); onCurrentSoftwareStrategyUpdate(client, clientSettings); - + if (!attributesToFetch.isEmpty()) { var future = attributesService.getSharedAttributes(client, attributesToFetch); DonAsynchron.withCallback(future, attrs -> { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java index 547d713251..e4b8336f92 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java @@ -20,12 +20,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy; @Data @EqualsAndHashCode(callSuper = true) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 8135b5aae0..285f9796f9 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -18,6 +18,9 @@ package org.thingsboard.server.transport.lwm2m.server.rpc; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.request.ContentFormat; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; @@ -35,6 +38,8 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllOb import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCreateRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCreateResponseCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverAllRequest; @@ -61,11 +66,14 @@ import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcReadRespon import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcWriteCompositeRequest; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; + +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertMultiResourceValuesFromRpcBody; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; @Slf4j @Service @@ -95,7 +103,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { } try { if (operationType.isHasObjectId()) { - String objectId = getIdFromParameters(client, rpcRequest); + LwM2MRpcRequestHeader header = JacksonUtil.fromString(rpcRequest.getParams(), LwM2MRpcRequestHeader.class); + String objectId = getIdFromParameters(client, header); + ContentFormat contentFormat = null; + if (StringUtils.isNotEmpty(header.getContentFormat())) { + contentFormat = ContentFormat.fromName(header.getContentFormat()); + } switch (operationType) { case READ: sendReadRequest(client, rpcRequest, objectId); @@ -124,17 +137,21 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { case WRITE_REPLACE: sendWriteReplaceRequest(client, rpcRequest, objectId); break; + case CREATE: + sendCreateRequest(client, rpcRequest, objectId); + break; default: throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } } else if (operationType.isComposite()) { - if (clientContext.isComposite(client)) { + ContentFormat contentFormatComposite = clientContext.getContentFormatComposite(client); + if (contentFormatComposite != null) { switch (operationType) { case READ_COMPOSITE: - sendReadCompositeRequest(client, rpcRequest); + sendReadCompositeRequest(client, rpcRequest, contentFormatComposite); break; case WRITE_COMPOSITE: - sendWriteCompositeRequest(client, rpcRequest); + sendWriteCompositeRequest(client, rpcRequest, contentFormatComposite); break; default: throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); @@ -173,12 +190,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { downlinkHandler.sendReadRequest(client, request, rpcCallback); } - private void sendReadCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { + private void sendReadCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, ContentFormat contentFormatComposite) { String[] versionedIds = getIdsFromParameters(client, requestMsg); TbLwM2MReadCompositeRequest request = TbLwM2MReadCompositeRequest.builder().versionedIds(versionedIds).timeout(clientContext.getRequestTimeout(client)).build(); var mainCallback = new TbLwM2MReadCompositeCallback(uplinkHandler, logService, client, versionedIds); var rpcCallback = new RpcReadResponseCompositeCallback(transportService, client, requestMsg, mainCallback); - downlinkHandler.sendReadCompositeRequest(client, request, rpcCallback); + downlinkHandler.sendReadCompositeRequest(client, request, rpcCallback, contentFormatComposite); } private void sendObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { @@ -231,8 +248,25 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { downlinkHandler.sendWriteUpdateRequest(client, builder.build(), rpcCallback); } + private void sendCreateRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + RpcCreateRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcCreateRequest.class); + TbLwM2MCreateRequest.TbLwM2MCreateRequestBuilder builder = TbLwM2MCreateRequest.builder().versionedId(versionedId); + builder.value(requestBody.getValue()).nodes(requestBody.getNodes()).timeout(clientContext.getRequestTimeout(client)); + var mainCallback = new TbLwM2MCreateResponseCallback(uplinkHandler, logService, client, versionedId); + var rpcCallback = new RpcCreateResponseCallback<>(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendCreateRequest(client, builder.build(), rpcCallback); + } + private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { RpcWriteReplaceRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteReplaceRequest.class); + LwM2mPath path = new LwM2mPath(fromVersionedIdToObjectId(versionedId)); + if (path.isResource() && requestBody.getValue() instanceof LinkedHashMap) { + ResourceModel resourceModel = client.getResourceModel(versionedId, this.config.getModelProvider()); + if (resourceModel != null && resourceModel.multiple) { + Map value = convertMultiResourceValuesFromRpcBody((LinkedHashMap) requestBody.getValue(), resourceModel.type, versionedId); + requestBody.setValue(value); + } + } TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) .value(requestBody.getValue()) .timeout(clientContext.getRequestTimeout(client)).build(); @@ -249,11 +283,39 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { * nodes.put("/1/0/2", 100); * nodes.put("/5/0/1", "coap://localhost:5685"); */ - private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { + private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, ContentFormat contentFormatComposite) { RpcWriteCompositeRequest rpcWriteCompositeRequest = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteCompositeRequest.class); - var mainCallback = new TbLwM2MWriteResponseCompositeCallback(uplinkHandler, logService, client, null); - var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); - downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback); + Map validNodes = validateNodes(client, rpcWriteCompositeRequest.getNodes()); + if (validNodes.size() > 0) { + rpcWriteCompositeRequest.setNodes(validNodes); + var mainCallback = new TbLwM2MWriteResponseCompositeCallback(uplinkHandler, logService, client, null); + var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback, contentFormatComposite); + } else { + throw new IllegalArgumentException(String.format("nodes: %s is not validate value", rpcWriteCompositeRequest.getNodes().toString())); + } + } + + private Map validateNodes(LwM2mClient client, Map nodes) { + Map newNodes = new LinkedHashMap(); + nodes.forEach((key, value) -> { + String versionedId; + try { + // validate key.toString() + new LwM2mPath(fromVersionedIdToObjectId(key.toString())); + versionedId = key.toString(); + } catch (Exception e) { + versionedId = clientContext.getObjectIdByKeyNameFromProfile(client, key.toString(), true); + } + // validate value. Must be only primitive, not Json + if (value instanceof LinkedHashMap) { + throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + + "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); + } else { + newNodes.put(fromVersionedIdToObjectId(versionedId), value); + } + }); + return newNodes; } private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { @@ -277,13 +339,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback); } - private String getIdFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { - IdOrKeyRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), IdOrKeyRequest.class); + private String getIdFromParameters(LwM2mClient client, LwM2MRpcRequestHeader header) { String targetId; - if (StringUtils.isNotEmpty(requestParams.getKey())) { - targetId = clientContext.getObjectIdByKeyNameFromProfile(client, requestParams.getKey()); - } else if (StringUtils.isNotEmpty(requestParams.getId())) { - targetId = requestParams.getId(); + if (StringUtils.isNotEmpty(header.getKey())) { + targetId = clientContext.getObjectIdByKeyNameFromProfile(client, header.getKey(), false); + } else if (StringUtils.isNotEmpty(header.getId())) { + targetId = header.getId(); } else { throw new IllegalArgumentException("Can't find 'key' or 'id' in the requestParams parameters!"); } @@ -293,14 +354,14 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private String[] getIdsFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { RpcReadCompositeRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), RpcReadCompositeRequest.class); if (requestParams.getKeys() != null && requestParams.getKeys().length > 0) { - Set targetIds = ConcurrentHashMap.newKeySet(); + Set targetIds = new HashSet<>(); for (String key : requestParams.getKeys()) { - String targetId = clientContext.getObjectIdByKeyNameFromProfile(client, key); + String targetId = clientContext.getObjectIdByKeyNameFromProfile(client, key, true); if (targetId != null) { targetIds.add(targetId); } } - return (String[]) targetIds.toArray(String[]::new); + return targetIds.toArray(String[]::new); } else if (requestParams.getIds() != null && requestParams.getIds().length > 0) { return requestParams.getIds(); } else { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHeader.java similarity index 92% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHeader.java index bef4fa37e8..996f248ea6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHeader.java @@ -20,8 +20,9 @@ import lombok.Data; @Data @JsonIgnoreProperties(ignoreUnknown = true) -public class IdOrKeyRequest { +public class LwM2MRpcRequestHeader { private String key; private String id; + private String contentFormat; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java index d2b7ffb834..c40d21cc4b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java @@ -21,7 +21,6 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; public class RpcCancelAllObserveCallback extends RpcDownlinkRequestCallbackProxy { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java index 1d1f1de230..f7971783ac 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java @@ -16,19 +16,12 @@ package org.thingsboard.server.transport.lwm2m.server.rpc; import org.eclipse.leshan.core.ResponseCode; -import org.eclipse.leshan.core.node.LwM2mObject; -import org.eclipse.leshan.core.node.LwM2mObjectInstance; -import org.eclipse.leshan.core.node.LwM2mResource; -import org.eclipse.leshan.core.response.ReadResponse; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; -import java.util.Optional; - public class RpcCancelObserveCallback extends RpcDownlinkRequestCallbackProxy { public RpcCancelObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateRequest.java similarity index 59% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateRequest.java index 12325f8c22..febd0f8e40 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateRequest.java @@ -13,15 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.bootstrap.secure; +package org.thingsboard.server.transport.lwm2m.server.rpc; -import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; -import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter; +import lombok.Data; +import lombok.EqualsAndHashCode; -public class LwM2MInMemoryBootstrapConfigurationAdapter extends BootstrapConfigurationStoreAdapter { +import java.util.Map; - public LwM2MInMemoryBootstrapConfigurationAdapter(BootstrapConfigStore store) { - super(store); - } +@Data +@EqualsAndHashCode(callSuper = true) +public class RpcCreateRequest extends LwM2MRpcRequestHeader { + + private Object value; + private String contentFormat; + private Map nodes; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateResponseCallback.java new file mode 100644 index 0000000000..f27d8e89d7 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateResponseCallback.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.rpc; + +import org.eclipse.leshan.core.request.LwM2mRequest; +import org.eclipse.leshan.core.response.CreateResponse; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; + +import java.util.Optional; + +public class RpcCreateResponseCallback, T extends CreateResponse> extends RpcLwM2MDownlinkCallback { + + public RpcCreateResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + super(transportService, client, requestMsg, callback); + } + + @Override + protected Optional serializeSuccessfulResponse(T response) { + String value = response.getLocation() != null ? "location=" + response.getLocation() : ""; + return Optional.of(value); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java index ba8a634de0..936d177b2b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java @@ -16,14 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server.rpc; import org.eclipse.leshan.core.Link; -import org.eclipse.leshan.core.node.LwM2mObject; -import org.eclipse.leshan.core.node.LwM2mObjectInstance; -import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.response.DiscoverResponse; -import org.eclipse.leshan.core.response.ReadResponse; -import org.jetbrains.annotations.NotNull; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java index 82d880c206..115b3df90d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java @@ -15,18 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.server.rpc; -import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.ResponseCode; -import org.eclipse.leshan.core.response.DiscoverResponse; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; -import java.util.Optional; -import java.util.Set; - public class RpcLinkSetCallback extends RpcDownlinkRequestCallbackProxy { public RpcLinkSetCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java index 67642078c6..bfe3b3cb90 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; @Data @EqualsAndHashCode(callSuper = true) -public class RpcWriteAttributesRequest extends IdOrKeyRequest { +public class RpcWriteAttributesRequest extends LwM2MRpcRequestHeader { private ObjectAttributes attributes; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java index 55fb2b49aa..36ad1be345 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java @@ -20,7 +20,7 @@ import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) -public class RpcWriteReplaceRequest extends IdOrKeyRequest { +public class RpcWriteReplaceRequest extends LwM2MRpcRequestHeader { private Object value; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java index b56fea7eb6..a38e7fb6c1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java @@ -17,11 +17,10 @@ package org.thingsboard.server.transport.lwm2m.server.rpc; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; @Data @EqualsAndHashCode(callSuper = true) -public class RpcWriteUpdateRequest extends IdOrKeyRequest { +public class RpcWriteUpdateRequest extends LwM2MRpcRequestHeader { private Object value; private String contentFormat; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java index ccd4767fee..f4620e2bcf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java @@ -22,7 +22,7 @@ import lombok.Data; @JsonIgnoreProperties(ignoreUnknown = true) public class RpcReadCompositeRequest { - private String [] keys; - private String [] ids; + private String[] keys; + private String[] ids; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java index fcc588473a..f3b6629e8d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java @@ -15,21 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; -import org.eclipse.leshan.server.security.SecurityInfo; -import org.jetbrains.annotations.Nullable; -import org.nustaq.serialization.FSTConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.integration.redis.util.RedisLockRegistry; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo; -import java.util.concurrent.locks.Lock; - public class TbLwM2mRedisClientOtaInfoStore implements TbLwM2MClientOtaInfoStore { private static final String OTA_EP = "OTA#EP#"; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index 1493123bc2..b8da8467e2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -726,7 +726,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto public synchronized void start() { if (!started) { started = true; - cleanerTask = schedExecutor.scheduleAtFixedRate(new TbLwM2mRedisRegistrationStore.Cleaner(), cleanPeriod, cleanPeriod, TimeUnit.SECONDS); + cleanerTask = schedExecutor.scheduleAtFixedRate(new Cleaner(), cleanPeriod, cleanPeriod, TimeUnit.SECONDS); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index daa1152728..2c4f911db6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -23,15 +23,20 @@ import com.google.gson.reflect.TypeToken; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.node.LwM2mResourceInstance; +import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.request.WriteRequest.Mode; import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.eclipse.leshan.core.response.ReadResponse; @@ -69,8 +74,6 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCal import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MLatchCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; @@ -217,7 +220,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl + registration.getLwM2mVersion() + " and modes: " + registration.getQueueMode() + ", " + registration.getBindingMode()); sessionManager.register(lwM2MClient.getSession()); this.initClientTelemetry(lwM2MClient); - this.initAttributes(lwM2MClient); + this.initAttributes(lwM2MClient, true); otaService.init(lwM2MClient); lwM2MClient.getRetryAttempts().set(0); } catch (LwM2MClientStateException stateException) { @@ -319,7 +322,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path); } else if (response.getContent() instanceof LwM2mResource) { LwM2mResource lwM2mResource = (LwM2mResource) response.getContent(); - this.updateResourcesValue(lwM2MClient, lwM2mResource, path); + this.updateResourcesValue(lwM2MClient, lwM2mResource, path, Mode.UPDATE); } } if (clientContext.awake(lwM2MClient)) { @@ -342,7 +345,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl } else if (v instanceof LwM2mObjectInstance) { this.updateObjectInstanceResourceValue(lwM2MClient, (LwM2mObjectInstance) v, k.toString()); } else if (v instanceof LwM2mResource) { - this.updateResourcesValue(lwM2MClient, (LwM2mResource) v, k.toString()); + this.updateResourcesValue(lwM2MClient, (LwM2mResource) v, k.toString(), Mode.UPDATE); } } }); @@ -482,11 +485,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl } } - private void sendDiscoverRequest(LwM2mClient lwM2MClient, String targetId) { - TbLwM2MDiscoverRequest request = TbLwM2MDiscoverRequest.builder().versionedId(targetId).timeout(clientContext.getRequestTimeout(lwM2MClient)).build(); - defaultLwM2MDownlinkMsgHandler.sendDiscoverRequest(lwM2MClient, request, new TbLwM2MDiscoverCallback(logService, lwM2MClient, targetId)); - } - private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId) { sendReadRequest(lwM2MClient, versionedId, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId)); } @@ -527,7 +525,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { String pathRez = pathIds.toString() + "/" + resourceId; - this.updateResourcesValue(client, resource, pathRez); + this.updateResourcesValue(client, resource, pathRez, Mode.UPDATE); }); } @@ -537,14 +535,14 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl * #2 Update new Resources (replace old Resource Value on new Resource Value) * #3 If fr_update -> UpdateFirmware * #4 updateAttrTelemetry - * - * @param lwM2MClient - Registration LwM2M Client + * @param lwM2MClient - Registration LwM2M Client * @param lwM2mResource - LwM2mSingleResource response.getContent() * @param path - resource + * @param mode - Replace, Update */ - private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path) { + private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path, Mode mode) { Registration registration = lwM2MClient.getRegistration(); - if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) { + if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider(), mode)) { if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, registration))) { otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); } else if (path.equals(convertObjectIdToVersionedId(FW_3_VER_ID, registration))) { @@ -570,7 +568,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl } this.updateAttrTelemetry(registration, Collections.singleton(path)); } else { - log.error("Fail update Resource [{}]", lwM2mResource); + log.error("Fail update path [{}] Resource [{}]", path, lwM2mResource); } } @@ -686,7 +684,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl Gson gson = new GsonBuilder().create(); ResourceModel.Type finalCurrentType = currentType; resourceValue.getInstances().forEach((k, v) -> { - Object val = this.converter.convertValue(v, finalCurrentType, expectedType, + Object val = this.converter.convertValue(v.getValue(), finalCurrentType, expectedType, new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); JsonElement element = gson.toJsonTree(val, val.getClass()); ((JsonObject) finalvalueKvProto).add(String.valueOf(k), element); @@ -714,21 +712,37 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl @Override public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request) { if (request.getNode() instanceof LwM2mResource) { - this.updateResourcesValue(client, ((LwM2mResource) request.getNode()), path); + this.updateResourcesValue(client, ((LwM2mResource) request.getNode()), path, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE); clientContext.update(client); } else if (request.getNode() instanceof LwM2mObjectInstance) { ((LwM2mObjectInstance) request.getNode()).getResources().forEach((resId, resource) -> { - this.updateResourcesValue(client, resource, path + "/" + resId); + this.updateResourcesValue(client, resource, path + "/" + resId, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE); }); clientContext.update(client); } } + @Override + public void onCreateResponseOk(LwM2mClient client, String path, CreateRequest request) { + if (request.getObjectInstances() != null && request.getObjectInstances().size() > 0) { + request.getObjectInstances().forEach( instance -> + instance.getResources() + ); +// this.updateResourcesValue(client, ((LwM2mResource) request.getNode()), path, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE); + clientContext.update(client); + } + } @Override public void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request) { log.trace("ReadCompositeResponse: [{}]", request.getNodes()); request.getNodes().forEach((k, v) -> { - this.updateResourcesValue(client, (LwM2mResource) v, k.toString()); + if (v instanceof LwM2mSingleResource) { + this.updateResourcesValue(client, (LwM2mResource) v, k.toString(), Mode.REPLACE); + } else { + LwM2mResourceInstance resourceInstance = (LwM2mResourceInstance) v; + LwM2mMultipleResource multipleResource = new LwM2mMultipleResource(v.getId(), resourceInstance.getType(), resourceInstance); + this.updateResourcesValue(client, multipleResource, k.toString(), Mode.REPLACE); + } }); } @@ -928,14 +942,14 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl * * @param lwM2MClient - LwM2M Client */ - public void initAttributes(LwM2mClient lwM2MClient) { + public void initAttributes(LwM2mClient lwM2MClient, boolean logFailedUpdateOfNonChangedValue) { Map keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient); if (!keyNamesMap.isEmpty()) { Set keysToFetch = new HashSet<>(keyNamesMap.values()); keysToFetch.removeAll(OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS); keysToFetch.removeAll(OtaPackageUtil.ALL_SW_ATTRIBUTE_KEYS); DonAsynchron.withCallback(attributesService.getSharedAttributes(lwM2MClient, keysToFetch), - v -> attributesService.onAttributesUpdate(lwM2MClient, v), + v -> attributesService.onAttributesUpdate(lwM2MClient, v, logFailedUpdateOfNonChangedValue), t -> log.error("[{}] Failed to get attributes", lwM2MClient.getEndpoint(), t), executor); } @@ -950,7 +964,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl return this.config; } - private void reportActivitySubscription(TransportProtos.SessionInfoProto sessionInfo) { + private void reportActivitySubscription(SessionInfoProto sessionInfo) { transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() .setAttributeSubscription(true) .setRpcSubscription(true) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java index b6fdf56a33..0749ab34f6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.uplink; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadCompositeResponse; @@ -56,6 +57,8 @@ public interface LwM2mUplinkMsgHandler { void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request); + void onCreateResponseOk(LwM2mClient client, String path, CreateRequest request); + void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request); void onToTransportUpdateCredentials(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToTransportUpdateCredentialsProto updateCredentials); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index 12a256ebed..0427caa3a6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.utils; +import com.google.api.client.util.Base64; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mPath; @@ -166,13 +167,16 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { case OPAQUE: if (currentType == Type.STRING) { /** let's assume we received an hexadecimal string */ - log.debug("Trying to convert hexadecimal string [{}] to byte array", value); - // TODO check if we shouldn't instead assume that the string contains Base64 encoded data + log.debug("Trying to convert hexadecimal/base64 string [{}] to byte array", value); try { return Hex.decodeHex(((String)value).toCharArray()); } catch (IllegalArgumentException e) { - throw new CodecException("Unable to convert hexastring [%s] to byte array for resource %s", value, - resourcePath); + try { + return Base64.decodeBase64(((String) value).getBytes()); + } catch (IllegalArgumentException ea) { + throw new CodecException("Unable to convert hexastring or base64 [%s] to byte array for resource %s", + value, resourcePath); + } } } break; diff --git a/pom.xml b/pom.xml index 687b6031fd..e3d4664923 100755 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ 2.12.1 2.2.6 2.6.1 - 2.0.0-M3 + 2.0.0-M4 2.6.2 2.3.30 1.6.2