Browse Source

Lwm2m: tests5

pull/5152/head
nickAS21 5 years ago
parent
commit
f2b58fd939
  1. 9
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  2. 16
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  3. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java
  4. 19
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  5. 11
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java
  6. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java
  7. 178
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java
  8. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  9. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java
  10. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  11. 15
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java
  12. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  13. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java
  14. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  15. 72
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java
  16. 113
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java
  17. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java
  18. 121
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  19. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java
  20. 34
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  21. 44
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java
  22. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java
  23. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java
  24. 423
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java
  25. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java
  26. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java
  27. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java
  28. 50
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateRequest.java
  29. 36
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateResponseCallback.java
  30. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java
  31. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java
  32. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java
  33. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java
  34. 13
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java
  35. 13
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java
  36. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java
  37. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java
  38. 13
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java
  39. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java
  40. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java
  41. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java
  42. 103
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java
  43. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHeader.java
  44. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java
  45. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java
  46. 18
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateRequest.java
  47. 38
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateResponseCallback.java
  48. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java
  49. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java
  50. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java
  51. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java
  52. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java
  53. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java
  54. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java
  55. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java
  56. 60
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java
  57. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java
  58. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java
  59. 2
      pom.xml

9
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.ObjectLoader;
import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.model.StaticModel;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.BootstrapRequest;
import org.eclipse.leshan.core.request.DeregisterRequest; import org.eclipse.leshan.core.request.DeregisterRequest;
import org.eclipse.leshan.core.request.RegisterRequest; import org.eclipse.leshan.core.request.RegisterRequest;
import org.eclipse.leshan.core.request.UpdateRequest; import org.eclipse.leshan.core.request.UpdateRequest;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -130,8 +131,8 @@ public class LwM2MTestClient {
builder.setRegistrationEngineFactory(engineFactory); builder.setRegistrationEngineFactory(engineFactory);
builder.setEndpointFactory(endpointFactory); builder.setEndpointFactory(endpointFactory);
builder.setSharedExecutor(executor); builder.setSharedExecutor(executor);
builder.setDecoder(new DefaultLwM2mNodeDecoder(true)); builder.setDecoder(new DefaultLwM2mDecoder(false));
builder.setEncoder(new DefaultLwM2mNodeEncoder(true)); builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false));
client = builder.build(); client = builder.build();
LwM2mClientObserver observer = new LwM2mClientObserver() { LwM2mClientObserver observer = new LwM2mClientObserver() {

16
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 lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; 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.bootstrap.BootstrapSessionManager;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; 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.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; 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.bootstrap.secure.LwM2mDefaultBootstrapSessionManager;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
@ -42,7 +39,6 @@ import java.security.KeyStoreException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.List;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig; import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig;
@ -57,7 +53,7 @@ public class LwM2MTransportBootstrapService {
private final LwM2MTransportBootstrapConfig bootstrapConfig; private final LwM2MTransportBootstrapConfig bootstrapConfig;
private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore; private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore;
private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore;
private final TransportService transportService;
private LeshanBootstrapServer server; private LeshanBootstrapServer server;
@PostConstruct @PostConstruct
@ -83,15 +79,11 @@ public class LwM2MTransportBootstrapService {
/* Create CoAP Config */ /* Create CoAP Config */
builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig));
/* Define model provider (Create Models )*/
List<ObjectModel> models = ObjectLoader.loadDefault();
builder.setModel(new StaticModel(models));
/* Create credentials */ /* Create credentials */
this.setServerWithCredentials(builder); this.setServerWithCredentials(builder);
/* Set securityStore with new ConfigStore */ /* Set securityStore with new ConfigStore */
builder.setConfigStore(new LwM2MInMemoryBootstrapConfigurationAdapter(lwM2MInMemoryBootstrapConfigStore)); builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
/* SecurityStore */ /* SecurityStore */
builder.setSecurityStore(lwM2MBootstrapSecurityStore); builder.setSecurityStore(lwM2MBootstrapSecurityStore);
@ -106,7 +98,7 @@ public class LwM2MTransportBootstrapService {
/* Set DTLS Config */ /* Set DTLS Config */
builder.setDtlsConfig(dtlsConfig); builder.setDtlsConfig(dtlsConfig);
BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore); BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore, lwM2MInMemoryBootstrapConfigStore, transportService);
builder.setSessionManager(sessionManager); builder.setSessionManager(sessionManager);
/* Create BootstrapServer */ /* Create BootstrapServer */

2
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; package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.BindingMode;
@ -59,6 +60,7 @@ public class LwM2MBootstrapConfig implements Serializable {
LwM2MServerBootstrap lwm2mServer; LwM2MServerBootstrap lwm2mServer;
@JsonIgnore
public BootstrapConfig getLwM2MBootstrapConfig() { public BootstrapConfig getLwM2MBootstrapConfig() {
BootstrapConfig configBs = new BootstrapConfig(); BootstrapConfig configBs = new BootstrapConfig();
/* Delete old security objects */ /* Delete old security objects */

19
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.security.GeneralSecurityException;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.UUID; 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_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; 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 LwM2mTransportContext context;
private final LwM2mTransportServerHelper helper; private final LwM2mTransportServerHelper helper;
private final Map<String /* endpoint */, TransportProtos.SessionInfoProto> bsSessions = new ConcurrentHashMap<>();
public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContext context, LwM2mTransportServerHelper helper) { public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContext context, LwM2mTransportServerHelper helper) {
this.bootstrapConfigStore = bootstrapConfigStore; this.bootstrapConfigStore = bootstrapConfigStore;
@ -82,7 +85,12 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
} }
bootstrapConfigStore.add(endPoint, bsConfigNew); bootstrapConfigStore.add(endPoint, bsConfigNew);
} catch (InvalidConfigurationException e) { } 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(); 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); LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class);
UUID sessionUUiD = UUID.randomUUID(); UUID sessionUUiD = UUID.randomUUID();
TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); 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())); context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, null, null, sessionInfo, context.getTransportService()));
if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
@ -189,4 +198,12 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) && return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) &&
lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.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);
}
} }

11
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; package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
import lombok.extern.slf4j.Slf4j; 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.BootstrapConfig;
import org.eclipse.leshan.server.bootstrap.BootstrapSession;
import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore;
import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 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 readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock(); private final Lock writeLock = readWriteLock.writeLock();
@Override
public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
return bootstrapByEndpoint.get(endpoint);
}
@Override @Override
public Map<String, BootstrapConfig> getAll() { public Map<String, BootstrapConfig> getAll() {
readLock.lock(); readLock.lock();
@ -56,10 +63,10 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt
} }
@Override @Override
public BootstrapConfig remove(String enpoint) { public BootstrapConfig remove(String endpoint) {
writeLock.lock(); writeLock.lock();
try { try {
return super.remove(enpoint); return super.remove(endpoint);
} finally { } finally {
writeLock.unlock(); writeLock.unlock();
} }

2
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() {
} }
;
public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) { public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) {
this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId(); this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId();
this.clientSecretKey = bootstrapFromCredential.getClientSecretKey(); this.clientSecretKey = bootstrapFromCredential.getClientSecretKey();

178
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; package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
import lombok.extern.slf4j.Slf4j; 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.BootstrapRequest;
import org.eclipse.leshan.core.request.Identity; 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.BootstrapSession;
import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider;
import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession;
import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager; 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.BootstrapSecurityStore;
import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityChecker;
import org.eclipse.leshan.server.security.SecurityInfo; 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.Iterator;
import java.util.List;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
@Slf4j @Slf4j
public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager { public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager {
private BootstrapSecurityStore bsSecurityStore; private BootstrapSecurityStore bsSecurityStore;
private SecurityChecker securityChecker; 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 * 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}. * @param bsSecurityStore the {@link BootstrapSecurityStore} used by default {@link SecurityChecker}.
*/ */
public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore) { public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore, TransportService transportService) {
this(bsSecurityStore, new SecurityChecker()); 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.bsSecurityStore = bsSecurityStore;
this.securityChecker = securityChecker; this.securityChecker = securityChecker;
this.tasksProvider = tasksProvider;
this.modelProvider = modelProvider;
} }
@SuppressWarnings("deprecation") @Override
public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
boolean authorized; boolean authorized;
if (bsSecurityStore != null) { Iterator<SecurityInfo> securityInfos;
Iterator<SecurityInfo> securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? if (bsSecurityStore != null && securityChecker != null) {
Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())).iterator() : bsSecurityStore.getAllByEndpoint(request.getEndpointName()); if (clientIdentity.isSecure() && clientIdentity.isPSK()) {
log.info("Bootstrap session started securityInfos: [{}]", securityInfos); securityInfos = bsSecurityStore.getAllByEndpoint(clientIdentity.getPskIdentity());
} else {
securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName());
}
authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos); authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos);
} else { } else {
authorized = true; authorized = true;
} }
DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized); 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; 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<LwM2mResponse>(tasks.requestsToSend.size()));
// is last Tasks ?
session.setMoreTasks(!tasks.last);
}
@Override
public BootstrapDownlinkRequest<? extends LwM2mResponse> getFirstRequest(BootstrapSession bsSession) {
return nextRequest(bsSession);
}
protected BootstrapDownlinkRequest<? extends LwM2mResponse> nextRequest(BootstrapSession bsSession) {
DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
List<BootstrapDownlinkRequest<? extends LwM2mResponse>> 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<? extends LwM2mResponse> 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<? extends LwM2mResponse> 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<? extends LwM2mResponse> 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);
}
} }

7
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; package org.thingsboard.server.transport.lwm2m.config;
import com.google.common.io.Resources;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.data.ResourceUtils;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore; import java.security.KeyStore;
@Slf4j @Slf4j

3
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; package org.thingsboard.server.transport.lwm2m.server;
import lombok.extern.slf4j.Slf4j; 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.californium.core.server.resources.CoapExchange;
import org.eclipse.leshan.core.californium.LwM2mCoapResource; import org.eclipse.leshan.core.californium.LwM2mCoapResource;
import org.thingsboard.server.common.transport.TransportServiceCallback;
@Slf4j @Slf4j
public abstract class AbstractLwM2mTransportResource extends LwM2mCoapResource { public abstract class AbstractLwM2mTransportResource extends LwM2mCoapResource {

8
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.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
@ -110,9 +110,9 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
LeshanServerBuilder builder = new LeshanServerBuilder(); LeshanServerBuilder builder = new LeshanServerBuilder();
builder.setLocalAddress(config.getHost(), config.getPort()); builder.setLocalAddress(config.getHost(), config.getPort());
builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort()); 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. */ /* 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 */ /* Create CoAP Config */
builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config));

15
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), DELETE(16, "Delete", true),
// only for RPC // 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 @Getter
private final int code; private final int code;
@ -78,7 +81,7 @@ public enum LwM2mOperationType {
this.type = type; this.type = type;
this.hasObjectId = hasObjectId; this.hasObjectId = hasObjectId;
this.composite = composite; this.composite = composite;
if(hasObjectId && composite){ if (hasObjectId && composite) {
throw new IllegalArgumentException("Can't set both Composite and hasObjectId for the same operation!"); throw new IllegalArgumentException("Can't set both Composite and hasObjectId for the same operation!");
} }
} }

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java

@ -98,7 +98,7 @@ public class LwM2mServerListener {
@Override @Override
public void onError(Observation observation, Registration registration, Exception error) { 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 @Override

1
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.data.ResourceType;
import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportService; 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;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;

4
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; package org.thingsboard.server.transport.lwm2m.server;
import com.google.gson.JsonParser;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser; import org.eclipse.leshan.core.model.DDFFileParser;
@ -50,6 +51,7 @@ import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.
public class LwM2mTransportServerHelper { public class LwM2mTransportServerHelper {
private final LwM2mTransportContext context; private final LwM2mTransportContext context;
private final static JsonParser JSON_PARSER = new JsonParser();;
public void sendParametersOnThingsboardAttribute(List<TransportProtos.KeyValueProto> result, SessionInfoProto sessionInfo) { public void sendParametersOnThingsboardAttribute(List<TransportProtos.KeyValueProto> result, SessionInfoProto sessionInfo) {
PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder(); PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder();
@ -229,7 +231,7 @@ public class LwM2mTransportServerHelper {
case STRING_V: case STRING_V:
return kv.getStringV(); return kv.getStringV();
case JSON_V: case JSON_V:
return kv.getJsonV(); return JSON_PARSER.parse(kv.getJsonV());
} }
return null; return null;
} }

72
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; package org.thingsboard.server.transport.lwm2m.server;
import com.fasterxml.jackson.databind.ObjectMapper; 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 lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.leshan.core.attributes.Attribute; 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.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource; 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.node.codec.CodecException;
import org.eclipse.leshan.core.request.SimpleDownlinkRequest; import org.eclipse.leshan.core.request.SimpleDownlinkRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest; 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.config.LwM2mVersion;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; 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.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.FirmwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; 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.SoftwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState; 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.server.uplink.DefaultLwM2MUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION;
@ -302,6 +311,12 @@ public class LwM2mTransportUtil {
return attributeLists.toArray(Attribute[]::new); 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) { public static ResourceModel.Type equalsResourceTypeGetSimpleName(Object value) {
switch (value.getClass().getSimpleName()) { switch (value.getClass().getSimpleName()) {
case "Double": 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<String> 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<Integer, Float>();
case INTEGER:
return new HashMap<Integer, Integer>();
case STRING:
return new HashMap<Integer, String>();
case BOOLEAN:
return new HashMap<Integer, Boolean>();
case OPAQUE:
return new HashMap<Integer, byte[]>();
case TIME:
return new HashMap<Integer, Date>();
case OBJLNK:
return new HashMap<Integer, ObjectLink>();
default:
return null;
}
}
public static Object convertWriteAttributes(String type, Object value, DefaultLwM2MUplinkMsgHandler serviceImpl, String target) { public static Object convertWriteAttributes(String type, Object value, DefaultLwM2MUplinkMsgHandler serviceImpl, String target) {
switch (type) { switch (type) {
/** Integer [0:255]; */ /** Integer [0:255]; */

113
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.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; 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 org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; 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.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_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; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
@Slf4j @Slf4j
@ -145,7 +153,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
newSoftwareTag = getStrValue(tsKvProto); newSoftwareTag = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) { } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) {
newSoftwareUrl = getStrValue(tsKvProto); newSoftwareUrl = getStrValue(tsKvProto);
}else { } else {
otherAttributes.add(tsKvProto); otherAttributes.add(tsKvProto);
} }
} }
@ -156,7 +164,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl), Optional.ofNullable(newSoftwareTag)); otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl), Optional.ofNullable(newSoftwareTag));
} }
if (!otherAttributes.isEmpty()) { if (!otherAttributes.isEmpty()) {
onAttributesUpdate(lwM2MClient, otherAttributes); onAttributesUpdate(lwM2MClient, otherAttributes, true);
} }
} else if (lwM2MClient == null) { } else if (lwM2MClient == null) {
log.error("OnAttributeUpdate, lwM2MClient is 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) * => send to client Request Update of value (new value from shared attribute)
* and LwM2MClient.delayedRequests.add(path) * 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 * #2.1 if there is not a difference in values between the current resource values and the shared attribute values
*
*/ */
@Override @Override
public void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos) { public void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos, boolean logFailedUpdateOfNonChangedValue) {
log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos); log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos);
Map <String, TransportProtos.TsKvProto> attributesUpdate = new ConcurrentHashMap<>();
tsKvProtos.forEach(tsKvProto -> { tsKvProtos.forEach(tsKvProto -> {
String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey()); try {
if (pathIdVer != null) { String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey(), false);
// #1.1 if (pathIdVer != null) {
if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) { // #1.1
if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) { 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); 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); clientContext.update(lwM2MClient);
// #2.1 // #2.1
lwM2MClient.getSharedAttributes().forEach((pathIdVer, tsKvProto) -> { attributesUpdate.forEach((pathIdVer, tsKvProto) -> {
this.pushUpdateToClientIfNeeded(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config.getModelProvider());
getValueFromKvProto(tsKvProto.getKv()), pathIdVer); 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<Integer, LwM2mResourceInstance>) oldResourceValue, pathIdVer);
}
}); });
} }
private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) { private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue,
if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { 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(); TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(clientContext.getRequestTimeout(lwM2MClient)).build();
downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId)); downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId));
}
}
private void pushUpdateMultiToClientIfNeeded(LwM2mClient client, ResourceModel resourceModel, JsonElement newValProto,
Map<Integer, LwM2mResourceInstance> 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 { } 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", String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad",
LOG_LWM2M_ERROR, versionedId, newValue); LOG_LWM2M_ERROR, versionedId, newValProto);
logService.log(lwM2MClient, logMsg); logService.log(client, logMsg);
log.info("Failed update resource [{}] [{}]", versionedId, newValue);
} }
} }
@ -217,8 +280,14 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
if (resourceValue != null) { if (resourceValue != null) {
ResourceModel.Type currentType = resourceValue.getType(); ResourceModel.Type currentType = resourceValue.getType();
ResourceModel.Type expectedType = helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); ResourceModel.Type expectedType = helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
return LwM2mValueConverterImpl.getInstance().convertValue(resourceValue.getValue(), currentType, expectedType, if (!resourceValue.isMultiInstances()) {
new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); 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 { } else {
return null; return null;
} }

2
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(TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification, TransportProtos.SessionInfoProto sessionInfo);
void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos); void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos, boolean logFailedUpdateOfNonChangedValue);
} }

121
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.LwM2mSingleResource;
import org.eclipse.leshan.core.node.codec.LwM2mValueConverter; import org.eclipse.leshan.core.node.codec.LwM2mValueConverter;
import org.eclipse.leshan.core.request.ContentFormat; 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.model.LwM2mModelProvider;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.common.data.Device; 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.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; 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.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; 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.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.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.convertObjectIdToVersionedId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
@ -101,11 +104,11 @@ public class LwM2mClient implements Serializable {
@Getter @Getter
private Long psmActivityTimer; private Long psmActivityTimer;
@Getter @Getter
private Long edrxCycle;
@Getter
private Long pagingTransmissionWindow; private Long pagingTransmissionWindow;
@Getter @Getter
@Setter @Setter
private Long edrxCycle;
@Getter
private Registration registration; private Registration registration;
@Getter @Getter
@Setter @Setter
@ -118,6 +121,10 @@ public class LwM2mClient implements Serializable {
private boolean firstEdrxDownlink = true; private boolean firstEdrxDownlink = true;
@Getter
Set<ContentFormat> clientSupportContentFormats;
@Getter
ContentFormat defaultContentFormat;
@Getter @Getter
private final AtomicInteger retryAttempts; private final AtomicInteger retryAttempts;
@ -147,6 +154,12 @@ public class LwM2mClient implements Serializable {
this.pagingTransmissionWindow = credentials.getDeviceInfo().getPagingTransmissionWindow(); this.pagingTransmissionWindow = credentials.getDeviceInfo().getPagingTransmissionWindow();
} }
public void setRegistration(Registration registration) {
this.registration = registration;
this.clientSupportContentFormats = clientSupportContentFormat(registration);
this.defaultContentFormat = calculateDefaultContentFormat(registration);
}
public void lock() { public void lock() {
lock.lock(); lock.lock();
} }
@ -210,15 +223,16 @@ public class LwM2mClient implements Serializable {
.build(); .build();
} }
public boolean saveResourceValue(String pathRezIdVer, LwM2mResource rez, LwM2mModelProvider modelProvider) { public boolean saveResourceValue(String pathRezIdVer, LwM2mResource resource, LwM2mModelProvider modelProvider, Mode mode) {
if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null) { if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null &&
this.resources.get(pathRezIdVer).setLwM2mResource(rez); resourceEqualsModel(resource, this.resources.get(pathRezIdVer).getResourceModel())) {
this.resources.get(pathRezIdVer).updateLwM2mResource(resource, mode);
return true; return true;
} else { } else {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null) { if (resourceModel != null && resourceEqualsModel(resource, resourceModel)) {
this.resources.put(pathRezIdVer, new ResourceValue(rez, resourceModel)); this.resources.put(pathRezIdVer, new ResourceValue(resource, resourceModel));
return true; return true;
} else { } else {
return false; 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) { public Object getResourceValue(String pathRezIdVer, String pathRezId) {
String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer; String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer;
if (this.resources.get(pathRez) != null) { if (this.resources.get(pathRez) != null) {
@ -282,7 +301,6 @@ public class LwM2mClient implements Serializable {
} }
public Collection<LwM2mResource> getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, public Collection<LwM2mResource> getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider,
LwM2mValueConverter converter) { LwM2mValueConverter converter) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer));
@ -299,31 +317,61 @@ public class LwM2mClient implements Serializable {
return resources; return resources;
} }
/**
* The instance must have all the resources that have the property
* <Mandatory>Mandatory</Mandatory>
*/
public Collection<LwM2mResource> getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, public Collection<LwM2mResource> getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider,
LwM2mValueConverter converter) { LwM2mValueConverter converter) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer));
Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet(); Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet();
Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration) Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration)
.getObjectModel(pathIds.getObjectId()).resources; .getObjectModel(pathIds.getObjectId()).resources;
resourceModels.forEach((resId, resourceModel) -> { if (params != null) {
if (((Map) params).containsKey(String.valueOf(resId))) { resourceModels.forEach((resourceId, resourceModel) -> {
Object value = ((Map) params).get((String.valueOf(resId))); if (((Map) params).containsKey(String.valueOf(resourceId))) {
resources.add(LwM2mSingleResource.newResource(resId, Object value = ((Map) params).get((String.valueOf(resourceId)));
converter.convertValue(value, equalsResourceTypeGetSimpleName(value), resourceModel.type, pathIds), resourceModel.type)); LwM2mResource resource = null;
if (resourceModel.multiple) {
} if (value instanceof LinkedHashMap) {
}); Map values = convertMultiResourceValuesFromRpcBody((LinkedHashMap) value, resourceModel.type, pathRezIdVer);
resource = LwM2mMultipleResource.newResource(resourceId, (Map<Integer, ?>) 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; return resources;
} }
public void isValidObjectVersion(String path) { public String isValidObjectVersion(String path) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path));
String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());
String verRez = getVerFromPathIdVerOrId(path); if (verSupportedObject == null) {
if ((verRez != null && !verRez.equals(verSupportedObject)) || return String.format("Specified resource id %s is missing from the client!", path);
(verRez == null && !LWM2M_OBJECT_VERSION_DEFAULT.equals(verSupportedObject))) { } else {
throw new IllegalArgumentException(String.format("Specified resource id %s is not valid version! Must be version: %s", path, verSupportedObject)); 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()); .collect(Collectors.toSet());
} }
public ContentFormat getDefaultContentFormat() { private ContentFormat calculateDefaultContentFormat(Registration registration) {
if (registration == null) { if (registration == null) {
return ContentFormat.DEFAULT; return ContentFormat.DEFAULT;
} else{
return LwM2mVersion.fromVersion(registration.getLwM2mVersion()).getContentFormat();
}
}
private Set<ContentFormat> clientSupportContentFormat(Registration registration) {
Set<ContentFormat> contentFormats = new HashSet<>();
if (registration == null) {
contentFormats.add(ContentFormat.DEFAULT);
} else { } 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<ContentFormat> 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 { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {

5
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; package org.thingsboard.server.transport.lwm2m.server.client;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
@ -51,7 +52,7 @@ public interface LwM2mClientContext {
LwM2mClient getClientByDeviceId(UUID deviceId); LwM2mClient getClientByDeviceId(UUID deviceId);
String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName); String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName, boolean isCompositeOperation);
void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials); void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials);
@ -61,7 +62,7 @@ public interface LwM2mClientContext {
void sendMsgsAfterSleeping(LwM2mClient lwM2MClient); void sendMsgsAfterSleeping(LwM2mClient lwM2MClient);
boolean isComposite(LwM2mClient client); ContentFormat getContentFormatComposite(LwM2mClient client);
void onUplink(LwM2mClient client); void onUplink(LwM2mClient client);

34
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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode; 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.node.LwM2mPath;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; 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.AfterStartUp;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; 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.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; 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.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.convertObjectIdToVersionedId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getMsgException;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey;
@Slf4j @Slf4j
@Service @Service
@ -291,12 +289,13 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
} }
@Override @Override
public String getObjectIdByKeyNameFromProfile(LwM2mClient client, String keyName) { public String getObjectIdByKeyNameFromProfile(LwM2mClient client, String keyName, boolean isCompositeOperation) {
Lwm2mDeviceProfileTransportConfiguration profile = getProfile(client.getProfileId()); Lwm2mDeviceProfileTransportConfiguration profile = getProfile(client.getProfileId());
Set<String> msgException = ConcurrentHashMap.newKeySet();
msgException.add("");
return profile.getObserveAttr().getKeyName().entrySet().stream() return profile.getObserveAttr().getKeyName().entrySet().stream()
.filter(e -> e.getValue().equals(keyName) && validateResourceInModel(client, e.getKey(), false)).findFirst().orElseThrow( .filter(e -> e.getValue().equals(keyName) && (isCompositeOperation || !msgException.add(client.isValidObjectVersion(e.getKey())))).findFirst().orElseThrow(
() -> new IllegalArgumentException(keyName + " is not configured in the device profile!") () -> new IllegalArgumentException(getMsgException (keyName, msgException))
).getKey(); ).getKey();
} }
@ -336,7 +335,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
if (LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) { if (LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) {
PowerMode powerMode = getPowerMode(lwM2MClient); PowerMode powerMode = getPowerMode(lwM2MClient);
if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) { if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) {
defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient); defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient, false);
TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg
.newBuilder() .newBuilder()
.setSessionInfo(lwM2MClient.getSession()) .setSessionInfo(lwM2MClient.getSession())
@ -412,9 +411,9 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
} }
@Override @Override
public boolean isComposite(LwM2mClient client) { public ContentFormat getContentFormatComposite(LwM2mClient client) {
return LwM2mVersion.fromVersionStr(client.getRegistration().getLwM2mVersion()).isComposite() & return client.getClientSupportContentFormats().contains(ContentFormat.SENML_JSON) ? ContentFormat.SENML_JSON :
getProfile(client.getProfileId()).getClientLwM2mSettings().isCompositeOperationsSupport(); client.getClientSupportContentFormats().contains(ContentFormat.SENML_CBOR) ? ContentFormat.SENML_CBOR : null;
} }
@Override @Override
@ -541,15 +540,4 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
} }
return timeout; 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)));
}
} }

44
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; package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ResourceModel; 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.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance; 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.io.Serializable;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j
@Data @Data
public class ResourceValue implements Serializable { public class ResourceValue implements Serializable {
@ -32,32 +39,35 @@ public class ResourceValue implements Serializable {
private TbResourceModel resourceModel; private TbResourceModel resourceModel;
public ResourceValue(LwM2mResource lwM2mResource, ResourceModel resourceModel) { public ResourceValue(LwM2mResource lwM2mResource, ResourceModel resourceModel) {
this.lwM2mResource = toTbLwM2MResource(lwM2mResource);
this.resourceModel = toTbResourceModel(resourceModel); this.resourceModel = toTbResourceModel(resourceModel);
updateLwM2mResource(lwM2mResource, Mode.UPDATE);
} }
public void setLwM2mResource(LwM2mResource lwM2mResource) { public void updateLwM2mResource(LwM2mResource lwM2mResource, Mode mode) {
this.lwM2mResource = toTbLwM2MResource(lwM2mResource); 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 <TbLwM2MResourceInstance> instancesSet = lwM2mResource.getInstances().values().stream().map(ResourceValue::toTbLwM2MResourceInstance).collect(Collectors.toSet());
if (Mode.REPLACE.equals(mode) && this.lwM2mResource != null) {
Map<Integer, LwM2mResourceInstance> 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) { public void setResourceModel(ResourceModel resourceModel) {
this.resourceModel = toTbResourceModel(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) { private static TbLwM2MResourceInstance toTbLwM2MResourceInstance(LwM2mResourceInstance instance) {
return new TbLwM2MResourceInstance(instance.getId(), instance.getValue(), instance.getType()); return new TbLwM2MResourceInstance(instance.getId(), instance.getValue(), instance.getType());
} }

6
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MMultipleResource.java → 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.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import java.io.Serializable; 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; 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); super(id, type, instances);
} }
} }

1
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 org.thingsboard.common.util.ThingsBoardExecutors;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
public abstract class LwM2MExecutorAwareService { public abstract class LwM2MExecutorAwareService {

423
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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.Link; 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.Attribute;
import org.eclipse.leshan.core.attributes.AttributeSet; 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.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.ObjectLink; 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.observation.Observation;
import org.eclipse.leshan.core.request.CompositeDownlinkRequest; import org.eclipse.leshan.core.request.CompositeDownlinkRequest;
import org.eclipse.leshan.core.request.ContentFormat; 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.DeleteRequest;
import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DiscoverRequest;
import org.eclipse.leshan.core.request.DownlinkRequest; 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.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.request.exception.ClientSleepingException; 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.request.exception.TimeoutException;
import org.eclipse.leshan.core.response.CreateResponse;
import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DeleteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.DiscoverResponse;
import org.eclipse.leshan.core.response.ExecuteResponse; 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.model.LwM2mModelProvider;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.springframework.stereotype.Service; 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.common.data.device.data.lwm2m.ObjectAttributes;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
@ -73,9 +77,12 @@ import javax.annotation.PreDestroy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; 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.MAXIMUM_PERIOD;
import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_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.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.fromVersionedIdToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateVersionedId;
@Slf4j @Slf4j
@Service @Service
@ -123,40 +134,49 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
@Override @Override
public void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback<ReadRequest, ReadResponse> callback) { public void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback<ReadRequest, ReadResponse> callback) {
validateVersionedId(client, request); try {
ReadRequest downlink = new ReadRequest(getRequestContentFormat(client, request, this.config.getModelProvider()), request.getObjectId()); validateVersionedId(client, request);
sendSimpleRequest(client, downlink, request.getTimeout(), callback); 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 @Override
public void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback<ReadCompositeRequest, ReadCompositeResponse> callback) { public void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request,
validateVersionedIds(client, request); DownlinkRequestCallback<ReadCompositeRequest, ReadCompositeResponse> callback, ContentFormat contentFormatComposite) {
ContentFormat requestContentFormat = ContentFormat.SENML_JSON; try {
ContentFormat responseContentFormat = ContentFormat.SENML_JSON; ReadCompositeRequest downlink = new ReadCompositeRequest(contentFormatComposite, contentFormatComposite, request.getObjectIds());
sendCompositeRequest(client, downlink, this.config.getTimeout(), callback);
ReadCompositeRequest downlink = new ReadCompositeRequest(requestContentFormat, responseContentFormat, request.getObjectIds()); } catch (InvalidRequestException e) {
sendCompositeRequest(client, downlink, request.getTimeout(), callback); callback.onValidationError(request.toString(), e.getMessage());
}
} }
@Override @Override
public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback) { public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback) {
validateVersionedId(client, request); try {
LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); validateVersionedId(client, request);
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration()); LwM2mPath resultIds = new LwM2mPath(request.getObjectId());
if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration());
ObserveRequest downlink; if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) {
ContentFormat contentFormat = getRequestContentFormat(client, request, this.config.getModelProvider()); ObserveRequest downlink;
if (resultIds.isResource()) { ContentFormat contentFormat = getReadRequestContentFormat(client, request, this.config.getModelProvider());
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); if (resultIds.isResource()) {
} else if (resultIds.isObjectInstance()) { downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); } 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 { } else {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); callback.onValidationError(resultIds.toString(), "Observation is already registered!");
} }
log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); } catch (InvalidRequestException e) {
sendSimpleRequest(client, downlink, request.getTimeout(), callback); callback.onValidationError(request.toString(), e.getMessage());
} else {
callback.onValidationError(resultIds.toString(), "Observation is already registered!");
} }
} }
@ -174,25 +194,36 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
@Override @Override
public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback<ExecuteRequest, ExecuteResponse> callback) { public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback<ExecuteRequest, ExecuteResponse> callback) {
ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); try {
if (resourceModelExecute != null) { ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider());
ExecuteRequest downlink; if (resourceModelExecute != null) {
if (request.getParams() != null && !resourceModelExecute.multiple) { validateVersionedId(client, request);
downlink = new ExecuteRequest(request.getVersionedId(), (String) this.converter.convertValue(request.getParams(), resourceModelExecute.type, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId()))); ExecuteRequest downlink;
} else { if (request.getParams() != null && !resourceModelExecute.multiple) {
downlink = new ExecuteRequest(request.getVersionedId()); 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 @Override
public void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback<DeleteRequest, DeleteResponse> callback) { public void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback<DeleteRequest, DeleteResponse> 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 @Override
public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) { public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) {
validateVersionedId(client, request);
int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId()); int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId());
callback.onSuccess(request, observeCancelCnt); callback.onSuccess(request, observeCancelCnt);
} }
@ -209,51 +240,86 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
sendSimpleRequest(client, new DiscoverRequest(request.getObjectId()), request.getTimeout(), callback); 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);
* <p>
* 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 @Override
public void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback<WriteAttributesRequest, WriteAttributesResponse> callback) { public void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback<WriteAttributesRequest, WriteAttributesResponse> callback) {
validateVersionedId(client, request); try {
if (request.getAttributes() == null) { validateVersionedId(client, request);
throw new IllegalArgumentException("Attributes to write are not specified!"); if (request.getAttributes() == null) {
throw new IllegalArgumentException("Attributes to write are not specified!");
}
ObjectAttributes params = request.getAttributes();
List<Attribute> 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<Attribute> 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 @Override
public void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback) { public void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback) {
ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); LwM2mPath resultIds = new LwM2mPath(request.getObjectId());
if (resourceModelWrite != null) { if (resultIds.isResource() || resultIds.isResourceInstance()) {
ContentFormat contentFormat = convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); validateVersionedId(client, request);
try { ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider());
LwM2mPath path = new LwM2mPath(request.getObjectId()); if (resourceModelWrite != null) {
WriteRequest downlink = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat, ContentFormat contentFormat = getWriteRequestContentFormat(client, request, this.config.getModelProvider());
path.getObjectId(), path.getObjectInstanceId(), path.getResourceId(), request.getValue()); try {
sendSimpleRequest(client, downlink, request.getTimeout(), callback); WriteRequest downlink = null;
} catch (Exception e) { if (resourceModelWrite.multiple) {
callback.onError(toString(request), e); if (request.getValue() instanceof Map && ((Map) request.getValue()).size() > 0) {
downlink = new WriteRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(),
(Map<Integer, ?>) 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 { } 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 @Override
public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest, DownlinkRequestCallback<WriteCompositeRequest, WriteCompositeResponse> callback) { public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest,
ContentFormat contentFormat = ContentFormat.SENML_JSON; DownlinkRequestCallback<WriteCompositeRequest, WriteCompositeResponse> callback, ContentFormat contentFormatComposite) {
try { try {
WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormat, rpcWriteCompositeRequest.getNodes()); WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormatComposite, rpcWriteCompositeRequest.getNodes());
//TODO: replace config.getTimeout(); //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) { } catch (Exception e) {
callback.onError(toString(rpcWriteCompositeRequest), e); callback.onError(toString(rpcWriteCompositeRequest), e);
} }
@ -261,38 +327,90 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
@Override @Override
public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback) { public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> 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<LwM2mResource> 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<CreateRequest, CreateResponse> callback) {
validateVersionedId(client, request);
CreateRequest downlink = null;
LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); LwM2mPath resultIds = new LwM2mPath(request.getObjectId());
if (resultIds.isResource()) { ObjectModel objectModel = client.getObjectModel(request.getObjectId(), this.config.getModelProvider());
/* // POST /{Object ID}/{Object Instance ID} && Resources is Mandatory
* send request: path = '/3/0' node == wM2mObjectInstance if (objectModel.multiple) {
* with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} // LwM2M CBOR, SenML CBOR, SenML JSON, or TLV (see [LwM2M-CORE])
**/ ContentFormat contentFormat = getWriteRequestContentFormat(client, request, this.config.getModelProvider());
Collection<LwM2mResource> resources = client.getNewResourceForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); if (resultIds.isObject() || resultIds.isObjectInstance()) {
ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); Collection<LwM2mResource> resources;
ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); if (resultIds.isObject()) {
WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), // contentFormat = ContentFormat.TLV;
resultIds.getObjectInstanceId(), resources); if (request.getValue() != null) {
sendSimpleRequest(client, downlink, request.getTimeout(), callback); resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter);
} else if (resultIds.isObjectInstance()) { downlink = new CreateRequest(contentFormat, resultIds.getObjectId(), resources);
/* } else if (request.getNodes() != null && request.getNodes().size() > 0) {
* params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" Set<LwM2mObjectInstance> instances = ConcurrentHashMap.newKeySet();
* int rscId = resultIds.getObjectInstanceId(); request.getNodes().forEach((key, value) -> {
* contentFormat Format of the payload (TLV or JSON). Collection<LwM2mResource> resourcesForInstance = client.getNewResourcesForInstance(request.getVersionedId(), value, this.config.getModelProvider(), this.converter);
*/ LwM2mObjectInstance instance = new LwM2mObjectInstance(Integer.parseInt(key), resourcesForInstance);
Collection<LwM2mResource> resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); instances.add(instance);
if (resources.size() > 0) { });
ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : ContentFormat.DEFAULT; LwM2mObjectInstance[] instanceArrays = instances.toArray(new LwM2mObjectInstance[instances.size()]);
WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); 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); sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} else { } 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 { } 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 <R extends SimpleDownlinkRequest<T>, T extends LwM2mResponse> void sendSimpleRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback) { private <R extends SimpleDownlinkRequest<T>, T extends LwM2mResponse> void sendSimpleRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback) {
sendRequest(client, request, timeoutInMs, callback, r -> request.getPath().toString()); 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()); sendRequest(client, request, timeoutInMs, callback, r -> request.getPaths().toString());
} }
private <R extends DownlinkRequest<T>, T extends LwM2mResponse> void sendRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback, Function<R, String> pathToStringFunction) { private <R extends DownlinkRequest<T>, T extends LwM2mResponse> void sendRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback, Function<R, String> pathToStringFunction) {
if (!clientContext.isDownlinkAllowed(client)) { if (!clientContext.isDownlinkAllowed(client)) {
log.trace("[{}] ignore downlink request cause client is sleeping.", client.getEndpoint()); log.trace("[{}] ignore downlink request cause client is sleeping.", client.getEndpoint());
@ -319,7 +438,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
clientContext.awake(client); clientContext.awake(client);
} }
}); });
},e -> handleDownlinkError(client, request, callback, e)); }, e -> handleDownlinkError(client, request, callback, e));
} catch (Exception e) { } catch (Exception e) {
handleDownlinkError(client, request, callback, 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) { private WriteRequest getWriteRequestSingleResource(ResourceModel.Type type, ContentFormat contentFormat, int objectId, int instanceId, int resourceId, Object value) {
switch (type) { switch (type) {
case STRING: // String 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 <T> void addAttribute(List<Attribute> attributes, String attributeName, T value) { private static <T> void addAttribute(List<Attribute> attributes, String attributeName, T value) {
addAttribute(attributes, attributeName, value, null, null); 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) { private static <T extends HasContentFormat & HasVersionedId> ContentFormat getReadRequestContentFormat(LwM2mClient client, T request, LwM2mModelProvider modelProvider) {
switch (type) { if (request.getRequestContentFormat().isPresent()) {
case BOOLEAN: return request.getRequestContentFormat().get();
case STRING: } else {
case TIME: return getRequestContentFormat(client, request.getVersionedId(), modelProvider);
case INTEGER:
case FLOAT:
return client.getDefaultContentFormat();
case OPAQUE:
return ContentFormat.OPAQUE;
case OBJLNK:
return ContentFormat.LINK;
default:
} }
throw new CodecException("Invalid ResourceModel_Type for %s ContentFormat.", type);
} }
private static ContentFormat getRequestContentFormat(LwM2mClient client, HasContentFormat request, LwM2mModelProvider modelProvider) { private static ContentFormat getWriteRequestContentFormat(LwM2mClient client, TbLwM2MDownlinkRequest request, LwM2mModelProvider modelProvider) {
if (request.getRequestContentFormat() != null) { if (request instanceof TbLwM2MWriteReplaceRequest && ((TbLwM2MWriteReplaceRequest) request).getContentFormat() != null) {
return request.getRequestContentFormat(); return ((TbLwM2MWriteReplaceRequest) request).getContentFormat();
} else if (request instanceof TbLwM2MWriteUpdateRequest && ((TbLwM2MWriteUpdateRequest) request).getObjectContentFormat() != null) {
return ((TbLwM2MWriteUpdateRequest) request).getObjectContentFormat();
} else { } else {
String versionedId = null; String versionedId = null;
if (request instanceof TbLwM2MReadRequest) { if (request instanceof TbLwM2MWriteReplaceRequest) {
versionedId = ((TbLwM2MReadRequest) request).getVersionedId(); versionedId = ((TbLwM2MWriteReplaceRequest) request).getVersionedId();
} else if (request instanceof TbLwM2MObserveRequest) { } else if (request instanceof TbLwM2MWriteUpdateRequest) {
versionedId = ((TbLwM2MObserveRequest) request).getVersionedId(); 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); } else {
if (id != null && new LwM2mPath(id).isResource() && !client.isResourceMultiInstances(versionedId, modelProvider)) { return getContentFormatForComplex(client);
return client.getDefaultContentFormat(); }
}
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 { } 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<ContentFormat> supported, ContentFormat defaultValue, ContentFormat... desiredFormats) {
for (ContentFormat contentFormat : desiredFormats) {
if (supported.contains(contentFormat)) {
return contentFormat;
} }
} }
return defaultValue;
} }
private <R> String toString(R request) { private <R> String toString(R request) {
try { try {
try { return request != null ? request.toString() : "";
return JacksonUtil.toString(request);
} catch (Exception e) {
return request.toString();
}
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to convert request to string", e); log.trace("Failed to convert request to string", e);
return request != null ? request.getClass().getSimpleName() : ""; return request.getClass().getSimpleName();
} }
} }
} }

4
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 org.eclipse.leshan.core.request.ContentFormat;
import java.util.Optional;
public interface HasContentFormat { public interface HasContentFormat {
ContentFormat getRequestContentFormat(); Optional<ContentFormat> getRequestContentFormat();
default ContentFormat getResponseContentFormat() { default ContentFormat getResponseContentFormat() {
return null; return null;

8
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; package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.Link; 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.DeleteRequest;
import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DiscoverRequest;
import org.eclipse.leshan.core.request.ExecuteRequest; 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.WriteAttributesRequest;
import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest; 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.DeleteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.DiscoverResponse;
import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ExecuteResponse;
@ -45,7 +48,7 @@ public interface LwM2mDownlinkMsgHandler {
void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback<ReadRequest, ReadResponse> callback); void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback<ReadRequest, ReadResponse> callback);
void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback<ReadCompositeRequest, ReadCompositeResponse> callback); void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback<ReadCompositeRequest, ReadCompositeResponse> callback, ContentFormat contentFormatComposite);
void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback); void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback);
@ -67,9 +70,10 @@ public interface LwM2mDownlinkMsgHandler {
void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback); void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback);
void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback<WriteCompositeRequest, WriteCompositeResponse> callback); void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback<WriteCompositeRequest, WriteCompositeResponse> callback, ContentFormat contentFormatComposite);
void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback); void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback);
void sendCreateRequest(LwM2mClient client, TbLwM2MCreateRequest request, DownlinkRequestCallback<CreateRequest, CreateResponse> callback);
} }

4
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; package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j; 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.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.LwM2mTransportUtil.LOG_LWM2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL;
@Slf4j @Slf4j
public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback<TbLwM2MCancelObserveRequest, Integer> { public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback<TbLwM2MCancelObserveRequest, Integer> {

50
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<CreateResponse> {
@Getter
private final Object value;
@Getter
private final ContentFormat objectContentFormat;
@Getter
private final Map<String, Object> nodes;;
@Builder
private TbLwM2MCreateRequest(String versionedId, long timeout, Object value, ContentFormat objectContentFormat, Map<String, Object> nodes) {
super(versionedId, timeout);
this.value = value;
this.objectContentFormat = objectContentFormat;
this.nodes = nodes;
}
@Override
public LwM2mOperationType getType() {
return LwM2mOperationType.CREATE;
}
}

36
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<CreateRequest, CreateResponse> {
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);
}
}

1
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.eclipse.leshan.core.response.DeleteResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; 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.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
public class TbLwM2MDeleteCallback extends TbLwM2MTargetedCallback<DeleteRequest, DeleteResponse> { public class TbLwM2MDeleteCallback extends TbLwM2MTargetedCallback<DeleteRequest, DeleteResponse> {

3
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.request.DiscoverRequest;
import org.eclipse.leshan.core.response.DiscoverResponse; 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.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
public class TbLwM2MDiscoverCallback extends TbLwM2MTargetedCallback<DiscoverRequest, DiscoverResponse> { public class TbLwM2MDiscoverCallback extends TbLwM2MTargetedCallback<DiscoverRequest, DiscoverResponse> {

3
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.request.ExecuteRequest;
import org.eclipse.leshan.core.response.ExecuteResponse; 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.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
public class TbLwM2MExecuteCallback extends TbLwM2MTargetedCallback<ExecuteRequest, ExecuteResponse> { public class TbLwM2MExecuteCallback extends TbLwM2MTargetedCallback<ExecuteRequest, ExecuteResponse> {

2
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 lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.response.ObserveResponse; 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.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
@Slf4j @Slf4j
public class TbLwM2MObserveCallback extends TbLwM2MUplinkTargetedCallback<ObserveRequest, ObserveResponse> { public class TbLwM2MObserveCallback extends TbLwM2MUplinkTargetedCallback<ObserveRequest, ObserveResponse> {

13
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; package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.Builder; import lombok.Builder;
import lombok.Getter;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ObserveResponse;
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType;
import java.util.Optional;
public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ObserveResponse> implements HasContentFormat { public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ObserveResponse> implements HasContentFormat {
@Getter private final Optional<ContentFormat> requestContentFormat;
private final ContentFormat requestContentFormat;
@Builder @Builder
private TbLwM2MObserveRequest(String versionedId, long timeout, ContentFormat requestContentFormat) { private TbLwM2MObserveRequest(String versionedId, long timeout, ContentFormat requestContentFormat) {
super(versionedId, timeout); super(versionedId, timeout);
this.requestContentFormat = requestContentFormat; this.requestContentFormat = Optional.ofNullable(requestContentFormat);
} }
@Override @Override
@ -38,5 +38,8 @@ public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkReques
} }
@Override
public Optional<ContentFormat> getRequestContentFormat() {
return this.requestContentFormat;
}
} }

13
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; package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.Builder; import lombok.Builder;
import lombok.Getter;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.ReadResponse;
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType;
import java.util.Optional;
public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ReadResponse> implements HasContentFormat { public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ReadResponse> implements HasContentFormat {
@Getter private final Optional<ContentFormat> requestContentFormat;
private final ContentFormat requestContentFormat;
@Builder @Builder
private TbLwM2MReadRequest(String versionedId, long timeout, ContentFormat requestContentFormat) { private TbLwM2MReadRequest(String versionedId, long timeout, ContentFormat requestContentFormat) {
super(versionedId, timeout); super(versionedId, timeout);
this.requestContentFormat = requestContentFormat; this.requestContentFormat = Optional.ofNullable(requestContentFormat);
} }
@Override @Override
@ -38,5 +38,8 @@ public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest<R
} }
@Override
public Optional<ContentFormat> getRequestContentFormat() {
return this.requestContentFormat;
}
} }

27
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; package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j; 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.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
@ -45,8 +48,28 @@ public abstract class TbLwM2MTargetedCallback<R, T> extends AbstractTbLwM2MReque
public void onSuccess(R request, T response) { public void onSuccess(R request, T response) {
//TODO convert camelCase to "camel case" using .split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])") //TODO convert camelCase to "camel case" using .split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")
String requestName = request.getClass().getSimpleName(); String requestName = request.getClass().getSimpleName();
log.trace("[{}] {} [{}] successful: {}", client.getEndpoint(), requestName, versionedId != null ? versionedId : versionedIds, response); String responseStr = response.toString();
logService.log(client, String.format("[%s]: %s [%s] successful. Result: [%s]", LOG_LWM2M_INFO, requestName, versionedId != null ? versionedId : Arrays.toString(versionedIds), response)); if (response instanceof ReadResponse) {
responseStr = responseToString ((ReadResponse) response);
}
log.trace("[{}] {} [{}] successful: {}", client.getEndpoint(), requestName, versionedId != null ? versionedId : versionedIds, responseStr);
logService.log(client, String.format("[%s]: %s [%s] successful. Result: [%s]", LOG_LWM2M_INFO, requestName, versionedId != null ? versionedId : Arrays.toString(versionedIds), responseStr));
} }
private String responseToString (ReadResponse response) {
if (response.getContent() instanceof LwM2mSingleResource) {
if (((LwM2mSingleResource) response.getContent()).getType().name().equals("OPAQUE")) {
if (((byte[])((LwM2mSingleResource) response.getContent()).getValue()).length > 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();
}
}
} }

1
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.eclipse.leshan.core.response.WriteAttributesResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; 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.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
public class TbLwM2MWriteAttributesCallback extends TbLwM2MTargetedCallback<WriteAttributesRequest, WriteAttributesResponse> { public class TbLwM2MWriteAttributesCallback extends TbLwM2MTargetedCallback<WriteAttributesRequest, WriteAttributesResponse> {

13
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.LwM2mOperationType;
import org.thingsboard.server.transport.lwm2m.server.downlink.HasContentFormat; import org.thingsboard.server.transport.lwm2m.server.downlink.HasContentFormat;
import java.util.Optional;
public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlinkCompositeRequest<ReadCompositeResponse> implements HasContentFormat { public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlinkCompositeRequest<ReadCompositeResponse> implements HasContentFormat {
@Getter
private final ContentFormat requestContentFormat; private final Optional<ContentFormat> requestContentFormatOpt;
@Getter @Getter
private final ContentFormat responseContentFormat; private final ContentFormat responseContentFormat;
@ -33,7 +35,7 @@ public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlink
@Builder @Builder
private TbLwM2MReadCompositeRequest(String [] versionedIds, long timeout, ContentFormat requestContentFormat, ContentFormat responseContentFormat) { private TbLwM2MReadCompositeRequest(String [] versionedIds, long timeout, ContentFormat requestContentFormat, ContentFormat responseContentFormat) {
super(versionedIds, timeout); super(versionedIds, timeout);
this.requestContentFormat = requestContentFormat; this.requestContentFormatOpt = Optional.ofNullable(requestContentFormat);
this.responseContentFormat = responseContentFormat; this.responseContentFormat = responseContentFormat;
} }
@ -41,4 +43,9 @@ public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlink
public LwM2mOperationType getType() { public LwM2mOperationType getType() {
return LwM2mOperationType.READ_COMPOSITE; return LwM2mOperationType.READ_COMPOSITE;
} }
@Override
public Optional<ContentFormat> getRequestContentFormat() {
return this.requestContentFormatOpt;
}
} }

1
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.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; 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.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY;

3
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(); var clientSettings = clientContext.getProfile(client.getProfileId()).getClientLwM2mSettings();
onFirmwareStrategyUpdate(client, clientSettings); onFirmwareStrategyUpdate(client, clientSettings);
onCurrentSoftwareStrategyUpdate(client, clientSettings); onCurrentSoftwareStrategyUpdate(client, clientSettings);
if (!attributesToFetch.isEmpty()) { if (!attributesToFetch.isEmpty()) {
var future = attributesService.getSharedAttributes(client, attributesToFetch); var future = attributesService.getSharedAttributes(client, attributesToFetch);
DonAsynchron.withCallback(future, attrs -> { DonAsynchron.withCallback(future, attrs -> {

4
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.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.ota.OtaPackageType; 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.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 @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

103
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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.ResponseCode; 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.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils; 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.TbLwM2MCancelAllRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; 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.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.TbLwM2MDeleteCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverAllRequest; 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.rpc.composite.RpcWriteCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; 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 @Slf4j
@Service @Service
@ -95,7 +103,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
} }
try { try {
if (operationType.isHasObjectId()) { 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) { switch (operationType) {
case READ: case READ:
sendReadRequest(client, rpcRequest, objectId); sendReadRequest(client, rpcRequest, objectId);
@ -124,17 +137,21 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
case WRITE_REPLACE: case WRITE_REPLACE:
sendWriteReplaceRequest(client, rpcRequest, objectId); sendWriteReplaceRequest(client, rpcRequest, objectId);
break; break;
case CREATE:
sendCreateRequest(client, rpcRequest, objectId);
break;
default: default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
} }
} else if (operationType.isComposite()) { } else if (operationType.isComposite()) {
if (clientContext.isComposite(client)) { ContentFormat contentFormatComposite = clientContext.getContentFormatComposite(client);
if (contentFormatComposite != null) {
switch (operationType) { switch (operationType) {
case READ_COMPOSITE: case READ_COMPOSITE:
sendReadCompositeRequest(client, rpcRequest); sendReadCompositeRequest(client, rpcRequest, contentFormatComposite);
break; break;
case WRITE_COMPOSITE: case WRITE_COMPOSITE:
sendWriteCompositeRequest(client, rpcRequest); sendWriteCompositeRequest(client, rpcRequest, contentFormatComposite);
break; break;
default: default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
@ -173,12 +190,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
downlinkHandler.sendReadRequest(client, request, rpcCallback); 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); String[] versionedIds = getIdsFromParameters(client, requestMsg);
TbLwM2MReadCompositeRequest request = TbLwM2MReadCompositeRequest.builder().versionedIds(versionedIds).timeout(clientContext.getRequestTimeout(client)).build(); TbLwM2MReadCompositeRequest request = TbLwM2MReadCompositeRequest.builder().versionedIds(versionedIds).timeout(clientContext.getRequestTimeout(client)).build();
var mainCallback = new TbLwM2MReadCompositeCallback(uplinkHandler, logService, client, versionedIds); var mainCallback = new TbLwM2MReadCompositeCallback(uplinkHandler, logService, client, versionedIds);
var rpcCallback = new RpcReadResponseCompositeCallback(transportService, client, requestMsg, mainCallback); 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) { 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); 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) { private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
RpcWriteReplaceRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteReplaceRequest.class); 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) TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId)
.value(requestBody.getValue()) .value(requestBody.getValue())
.timeout(clientContext.getRequestTimeout(client)).build(); .timeout(clientContext.getRequestTimeout(client)).build();
@ -249,11 +283,39 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
* nodes.put("/1/0/2", 100); * nodes.put("/1/0/2", 100);
* nodes.put("/5/0/1", "coap://localhost:5685"); * 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); RpcWriteCompositeRequest rpcWriteCompositeRequest = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteCompositeRequest.class);
var mainCallback = new TbLwM2MWriteResponseCompositeCallback(uplinkHandler, logService, client, null); Map validNodes = validateNodes(client, rpcWriteCompositeRequest.getNodes());
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); if (validNodes.size() > 0) {
downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback); 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) { private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
@ -277,13 +339,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback); downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback);
} }
private String getIdFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { private String getIdFromParameters(LwM2mClient client, LwM2MRpcRequestHeader header) {
IdOrKeyRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), IdOrKeyRequest.class);
String targetId; String targetId;
if (StringUtils.isNotEmpty(requestParams.getKey())) { if (StringUtils.isNotEmpty(header.getKey())) {
targetId = clientContext.getObjectIdByKeyNameFromProfile(client, requestParams.getKey()); targetId = clientContext.getObjectIdByKeyNameFromProfile(client, header.getKey(), false);
} else if (StringUtils.isNotEmpty(requestParams.getId())) { } else if (StringUtils.isNotEmpty(header.getId())) {
targetId = requestParams.getId(); targetId = header.getId();
} else { } else {
throw new IllegalArgumentException("Can't find 'key' or 'id' in the requestParams parameters!"); 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) { private String[] getIdsFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) {
RpcReadCompositeRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), RpcReadCompositeRequest.class); RpcReadCompositeRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), RpcReadCompositeRequest.class);
if (requestParams.getKeys() != null && requestParams.getKeys().length > 0) { if (requestParams.getKeys() != null && requestParams.getKeys().length > 0) {
Set targetIds = ConcurrentHashMap.newKeySet(); Set<String> targetIds = new HashSet<>();
for (String key : requestParams.getKeys()) { for (String key : requestParams.getKeys()) {
String targetId = clientContext.getObjectIdByKeyNameFromProfile(client, key); String targetId = clientContext.getObjectIdByKeyNameFromProfile(client, key, true);
if (targetId != null) { if (targetId != null) {
targetIds.add(targetId); targetIds.add(targetId);
} }
} }
return (String[]) targetIds.toArray(String[]::new); return targetIds.toArray(String[]::new);
} else if (requestParams.getIds() != null && requestParams.getIds().length > 0) { } else if (requestParams.getIds() != null && requestParams.getIds().length > 0) {
return requestParams.getIds(); return requestParams.getIds();
} else { } else {

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHeader.java

@ -20,8 +20,9 @@ import lombok.Data;
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class IdOrKeyRequest { public class LwM2MRpcRequestHeader {
private String key; private String key;
private String id; private String id;
private String contentFormat;
} }

1
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.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; 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.TbLwM2MCancelAllRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest;
public class RpcCancelAllObserveCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelAllRequest, Integer> { public class RpcCancelAllObserveCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelAllRequest, Integer> {

7
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; package org.thingsboard.server.transport.lwm2m.server.rpc;
import org.eclipse.leshan.core.ResponseCode; 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.common.transport.TransportService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; 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.DownlinkRequestCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest;
import java.util.Optional;
public class RpcCancelObserveCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelObserveRequest, Integer> { public class RpcCancelObserveCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelObserveRequest, Integer> {
public RpcCancelObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) { public RpcCancelObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) {

18
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java → 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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 lombok.Data;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter; import lombok.EqualsAndHashCode;
public class LwM2MInMemoryBootstrapConfigurationAdapter extends BootstrapConfigurationStoreAdapter { import java.util.Map;
public LwM2MInMemoryBootstrapConfigurationAdapter(BootstrapConfigStore store) { @Data
super(store); @EqualsAndHashCode(callSuper = true)
} public class RpcCreateRequest extends LwM2MRpcRequestHeader {
private Object value;
private String contentFormat;
private Map<String, Object> nodes;
} }

38
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<R extends LwM2mRequest<T>, T extends CreateResponse> extends RpcLwM2MDownlinkCallback<R, T> {
public RpcCreateResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<R, T> callback) {
super(transportService, client, requestMsg, callback);
}
@Override
protected Optional<String> serializeSuccessfulResponse(T response) {
String value = response.getLocation() != null ? "location=" + response.getLocation() : "";
return Optional.of(value);
}
}

6
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; package org.thingsboard.server.transport.lwm2m.server.rpc;
import org.eclipse.leshan.core.Link; 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.request.DiscoverRequest;
import org.eclipse.leshan.core.response.DiscoverResponse; 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.common.transport.TransportService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;

5
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; 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.ResponseCode;
import org.eclipse.leshan.core.response.DiscoverResponse;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; 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.DownlinkRequestCallback;
import java.util.Optional;
import java.util.Set;
public class RpcLinkSetCallback<R, T> extends RpcDownlinkRequestCallbackProxy<R, T> { public class RpcLinkSetCallback<R, T> extends RpcDownlinkRequestCallbackProxy<R, T> {
public RpcLinkSetCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<R, T> callback) { public RpcLinkSetCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<R, T> callback) {

2
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 @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class RpcWriteAttributesRequest extends IdOrKeyRequest { public class RpcWriteAttributesRequest extends LwM2MRpcRequestHeader {
private ObjectAttributes attributes; private ObjectAttributes attributes;

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java

@ -20,7 +20,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class RpcWriteReplaceRequest extends IdOrKeyRequest { public class RpcWriteReplaceRequest extends LwM2MRpcRequestHeader {
private Object value; private Object value;

3
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.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class RpcWriteUpdateRequest extends IdOrKeyRequest { public class RpcWriteUpdateRequest extends LwM2MRpcRequestHeader {
private Object value; private Object value;
private String contentFormat; private String contentFormat;

4
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) @JsonIgnoreProperties(ignoreUnknown = true)
public class RpcReadCompositeRequest { public class RpcReadCompositeRequest {
private String [] keys; private String[] keys;
private String [] ids; private String[] ids;
} }

8
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; 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.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ota.OtaPackageType; 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.LwM2MClientOtaInfo;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo;
import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo;
import java.util.concurrent.locks.Lock;
public class TbLwM2mRedisClientOtaInfoStore implements TbLwM2MClientOtaInfoStore { public class TbLwM2mRedisClientOtaInfoStore implements TbLwM2MClientOtaInfoStore {
private static final String OTA_EP = "OTA#EP#"; private static final String OTA_EP = "OTA#EP#";

2
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() { public synchronized void start() {
if (!started) { if (!started) {
started = true; started = true;
cleanerTask = schedExecutor.scheduleAtFixedRate(new TbLwM2mRedisRegistrationStore.Cleaner(), cleanPeriod, cleanPeriod, TimeUnit.SECONDS); cleanerTask = schedExecutor.scheduleAtFixedRate(new Cleaner(), cleanPeriod, cleanPeriod, TimeUnit.SECONDS);
} }
} }

60
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 lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel; 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.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; 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.observation.Observation;
import org.eclipse.leshan.core.request.CreateRequest;
import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.ReadRequest;
import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest; 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.ObserveResponse;
import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.eclipse.leshan.core.response.ReadCompositeResponse;
import org.eclipse.leshan.core.response.ReadResponse; 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.LwM2mDownlinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; 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.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.TbLwM2MLatchCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; 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()); + registration.getLwM2mVersion() + " and modes: " + registration.getQueueMode() + ", " + registration.getBindingMode());
sessionManager.register(lwM2MClient.getSession()); sessionManager.register(lwM2MClient.getSession());
this.initClientTelemetry(lwM2MClient); this.initClientTelemetry(lwM2MClient);
this.initAttributes(lwM2MClient); this.initAttributes(lwM2MClient, true);
otaService.init(lwM2MClient); otaService.init(lwM2MClient);
lwM2MClient.getRetryAttempts().set(0); lwM2MClient.getRetryAttempts().set(0);
} catch (LwM2MClientStateException stateException) { } catch (LwM2MClientStateException stateException) {
@ -319,7 +322,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path); this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path);
} else if (response.getContent() instanceof LwM2mResource) { } else if (response.getContent() instanceof LwM2mResource) {
LwM2mResource lwM2mResource = (LwM2mResource) response.getContent(); LwM2mResource lwM2mResource = (LwM2mResource) response.getContent();
this.updateResourcesValue(lwM2MClient, lwM2mResource, path); this.updateResourcesValue(lwM2MClient, lwM2mResource, path, Mode.UPDATE);
} }
} }
if (clientContext.awake(lwM2MClient)) { if (clientContext.awake(lwM2MClient)) {
@ -342,7 +345,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
} else if (v instanceof LwM2mObjectInstance) { } else if (v instanceof LwM2mObjectInstance) {
this.updateObjectInstanceResourceValue(lwM2MClient, (LwM2mObjectInstance) v, k.toString()); this.updateObjectInstanceResourceValue(lwM2MClient, (LwM2mObjectInstance) v, k.toString());
} else if (v instanceof LwM2mResource) { } 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) { private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId) {
sendReadRequest(lwM2MClient, versionedId, new TbLwM2MReadCallback(this, logService, lwM2MClient, 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)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer));
lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> {
String pathRez = pathIds.toString() + "/" + resourceId; 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) * #2 Update new Resources (replace old Resource Value on new Resource Value)
* #3 If fr_update -> UpdateFirmware * #3 If fr_update -> UpdateFirmware
* #4 updateAttrTelemetry * #4 updateAttrTelemetry
* * @param lwM2MClient - Registration LwM2M Client
* @param lwM2MClient - Registration LwM2M Client
* @param lwM2mResource - LwM2mSingleResource response.getContent() * @param lwM2mResource - LwM2mSingleResource response.getContent()
* @param path - resource * @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(); 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))) { if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, registration))) {
otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue());
} else if (path.equals(convertObjectIdToVersionedId(FW_3_VER_ID, registration))) { } 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)); this.updateAttrTelemetry(registration, Collections.singleton(path));
} else { } 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(); Gson gson = new GsonBuilder().create();
ResourceModel.Type finalCurrentType = currentType; ResourceModel.Type finalCurrentType = currentType;
resourceValue.getInstances().forEach((k, v) -> { 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))); new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)));
JsonElement element = gson.toJsonTree(val, val.getClass()); JsonElement element = gson.toJsonTree(val, val.getClass());
((JsonObject) finalvalueKvProto).add(String.valueOf(k), element); ((JsonObject) finalvalueKvProto).add(String.valueOf(k), element);
@ -714,21 +712,37 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
@Override @Override
public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request) { public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request) {
if (request.getNode() instanceof LwM2mResource) { 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); clientContext.update(client);
} else if (request.getNode() instanceof LwM2mObjectInstance) { } else if (request.getNode() instanceof LwM2mObjectInstance) {
((LwM2mObjectInstance) request.getNode()).getResources().forEach((resId, resource) -> { ((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); 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 @Override
public void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request) { public void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request) {
log.trace("ReadCompositeResponse: [{}]", request.getNodes()); log.trace("ReadCompositeResponse: [{}]", request.getNodes());
request.getNodes().forEach((k, v) -> { 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 * @param lwM2MClient - LwM2M Client
*/ */
public void initAttributes(LwM2mClient lwM2MClient) { public void initAttributes(LwM2mClient lwM2MClient, boolean logFailedUpdateOfNonChangedValue) {
Map<String, String> keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient); Map<String, String> keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient);
if (!keyNamesMap.isEmpty()) { if (!keyNamesMap.isEmpty()) {
Set<String> keysToFetch = new HashSet<>(keyNamesMap.values()); Set<String> keysToFetch = new HashSet<>(keyNamesMap.values());
keysToFetch.removeAll(OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS); keysToFetch.removeAll(OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS);
keysToFetch.removeAll(OtaPackageUtil.ALL_SW_ATTRIBUTE_KEYS); keysToFetch.removeAll(OtaPackageUtil.ALL_SW_ATTRIBUTE_KEYS);
DonAsynchron.withCallback(attributesService.getSharedAttributes(lwM2MClient, keysToFetch), 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), t -> log.error("[{}] Failed to get attributes", lwM2MClient.getEndpoint(), t),
executor); executor);
} }
@ -950,7 +964,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
return this.config; return this.config;
} }
private void reportActivitySubscription(TransportProtos.SessionInfoProto sessionInfo) { private void reportActivitySubscription(SessionInfoProto sessionInfo) {
transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder()
.setAttributeSubscription(true) .setAttributeSubscription(true)
.setRpcSubscription(true) .setRpcSubscription(true)

3
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; package org.thingsboard.server.transport.lwm2m.server.uplink;
import org.eclipse.leshan.core.observation.Observation; 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.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.eclipse.leshan.core.response.ReadCompositeResponse;
@ -56,6 +57,8 @@ public interface LwM2mUplinkMsgHandler {
void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request); void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request);
void onCreateResponseOk(LwM2mClient client, String path, CreateRequest request);
void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request); void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request);
void onToTransportUpdateCredentials(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToTransportUpdateCredentialsProto updateCredentials); void onToTransportUpdateCredentials(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToTransportUpdateCredentialsProto updateCredentials);

12
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; package org.thingsboard.server.transport.lwm2m.utils;
import com.google.api.client.util.Base64;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.model.ResourceModel.Type;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
@ -166,13 +167,16 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter {
case OPAQUE: case OPAQUE:
if (currentType == Type.STRING) { if (currentType == Type.STRING) {
/** let's assume we received an hexadecimal string */ /** let's assume we received an hexadecimal string */
log.debug("Trying to convert hexadecimal string [{}] to byte array", value); log.debug("Trying to convert hexadecimal/base64 string [{}] to byte array", value);
// TODO check if we shouldn't instead assume that the string contains Base64 encoded data
try { try {
return Hex.decodeHex(((String)value).toCharArray()); return Hex.decodeHex(((String)value).toCharArray());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new CodecException("Unable to convert hexastring [%s] to byte array for resource %s", value, try {
resourcePath); 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; break;

2
pom.xml

@ -71,7 +71,7 @@
<jackson-core.version>2.12.1</jackson-core.version> <jackson-core.version>2.12.1</jackson-core.version>
<json-schema-validator.version>2.2.6</json-schema-validator.version> <json-schema-validator.version>2.2.6</json-schema-validator.version>
<californium.version>2.6.1</californium.version> <californium.version>2.6.1</californium.version>
<leshan.version>2.0.0-M3</leshan.version> <leshan.version>2.0.0-M4</leshan.version>
<gson.version>2.6.2</gson.version> <gson.version>2.6.2</gson.version>
<freemarker.version>2.3.30</freemarker.version> <freemarker.version>2.3.30</freemarker.version>
<mail.version>1.6.2</mail.version> <mail.version>1.6.2</mail.version>

Loading…
Cancel
Save