diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java index a1f056ff30..588de9bba2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.bootstrap; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.leshan.core.model.StaticModel; +import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; @@ -28,18 +29,40 @@ import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; 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.LwM2MSetSecurityStoreBootstrap; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; + +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.KeySpec; +import java.util.Arrays; + +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig; @Slf4j @Component @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true'&& '${transport.lwm2m.bootstrap.enable}'=='true')") public class LwM2MTransportBootstrapServerConfiguration { + private PublicKey publicKey; + private PrivateKey privateKey; @Autowired private LwM2MTransportContextBootstrap contextBs; @@ -90,7 +113,7 @@ public class LwM2MTransportBootstrapServerConfiguration { builder.setDtlsConfig(dtlsConfig); /** Create credentials */ - new LwM2MSetSecurityStoreBootstrap(builder, contextBs, contextS, dtlsMode); + LwM2MSetSecurityStoreBootstrap(builder, dtlsMode); BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore); builder.setSessionManager(sessionManager); @@ -98,4 +121,151 @@ public class LwM2MTransportBootstrapServerConfiguration { /** Create BootstrapServer */ return builder.build(); } + + public void LwM2MSetSecurityStoreBootstrap(LeshanBootstrapServerBuilder builder, LwM2MSecurityMode dtlsMode) { + + /** Set securityStore with new registrationStore */ + + switch (dtlsMode) { + /** Use No_Sec only */ + case NO_SEC: + setServerWithX509Cert(builder, NO_SEC.code); + break; + /** Use PSK/RPK */ + case PSK: + case RPK: + setRPK(builder); + break; + case X509: + setServerWithX509Cert(builder, X509.code); + break; + /** Use X509_EST only */ + case X509_EST: + // TODO support sentinel pool and make pool configurable + break; + /** Use ather X509, PSK, No_Sec ?? */ + default: + break; + } + } + + private void setRPK(LeshanBootstrapServerBuilder builder) { + try { + /** Get Elliptic Curve Parameter spec for secp256r1 */ + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); + algoParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); + if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) { + /** Get point values */ + byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray()); + byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray()); + /** Create key specs */ + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), + parameterSpec); + /** Get keys */ + this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + } + if (this.contextBs.getCtxBootStrap().getBootstrapPrivateS() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateS().isEmpty()) { + /** Get point values */ + byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateS().toCharArray()); + /** Create key specs */ + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); + /** Get keys */ + this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); + } + if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && + this.privateKey != null && this.privateKey.getEncoded().length > 0) { + builder.setPublicKey(this.publicKey); + builder.setPrivateKey(this.privateKey); + this.contextBs.getCtxBootStrap().setBootstrapPublicKey(this.publicKey); + getParamsRPK(); + } + } catch (GeneralSecurityException | IllegalArgumentException e) { + log.error("[{}] Failed generate Server PSK/RPK", e.getMessage()); + throw new RuntimeException(e); + } + } + + private void setServerWithX509Cert(LeshanBootstrapServerBuilder builder, int securityModeCode) { + try { + if (this.contextS.getCtxServer().getKeyStoreValue() != null) { + KeyStore keyStoreServer = this.contextS.getCtxServer().getKeyStoreValue(); + setBuilderX509(builder); + X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getCtxServer().getRootAlias()); + if (rootCAX509Cert != null && securityModeCode == X509.code) { + X509Certificate[] trustedCertificates = new X509Certificate[1]; + trustedCertificates[0] = rootCAX509Cert; + builder.setTrustedCertificates(trustedCertificates); + } else { + /** by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + } + } + else { + /** by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + log.error("Unable to load X509 files for BootStrapServer"); + } + } catch (KeyStoreException ex) { + log.error("[{}] Unable to load X509 files server", ex.getMessage()); + } + + } + + private void setBuilderX509(LeshanBootstrapServerBuilder builder) { + /** + * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE + * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks + */ + try { + X509Certificate serverCertificate = (X509Certificate) this.contextS.getCtxServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias()); + this.privateKey = (PrivateKey) this.contextS.getCtxServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getCtxServer().getKeyStorePasswordServer() == null ? null : this.contextS.getCtxServer().getKeyStorePasswordServer().toCharArray()); + if (this.privateKey != null && this.privateKey.getEncoded().length > 0) { + builder.setPrivateKey(this.privateKey); + } + if (serverCertificate != null) { + builder.setCertificateChain(new X509Certificate[]{serverCertificate}); + this.contextBs.getCtxBootStrap().setBootstrapCertificate(serverCertificate); + infoParamsX509(serverCertificate); + } + } catch (Exception ex) { + log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); + } + } + + private void getParamsRPK() { + if (this.publicKey instanceof ECPublicKey) { + /** Get x coordinate */ + byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray(); + if (x[0] == 0) + x = Arrays.copyOfRange(x, 1, x.length); + + /** Get Y coordinate */ + byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray(); + if (y[0] == 0) + y = Arrays.copyOfRange(y, 1, y.length); + + /** Get Curves params */ + String params = ((ECPublicKey) this.publicKey).getParams().toString(); + log.info( + " \nBootstrap uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]", + params, Hex.encodeHexString(x), Hex.encodeHexString(y), + Hex.encodeHexString(this.publicKey.getEncoded()), + Hex.encodeHexString(this.privateKey.getEncoded())); + } else { + throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); + } + } + + private void infoParamsX509(X509Certificate certificate) { + try { + log.info("BootStrap uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]", + Hex.encodeHexString(certificate.getEncoded()), + Hex.encodeHexString(this.privateKey.getEncoded())); + } catch (CertificateEncodingException e) { + log.error("", e); + } + } + + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index a4e49fc546..6d21d48c65 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -29,10 +29,10 @@ import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MGetSecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; +import org.thingsboard.server.transport.lwm2m.secure.LwM2mValidateCredentialsSecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2MSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer; @@ -53,14 +53,14 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandle import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getBootstrapParametersFromThingsboard; @Slf4j -@Component("LwM2MBootstrapSecurityStore") +@Service("LwM2MBootstrapSecurityStore") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true' && '${transport.lwm2m.bootstrap.enable}'=='true')") public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private final EditableBootstrapConfigStore bootstrapConfigStore; @Autowired - LwM2MGetSecurityInfo lwM2MGetSecurityInfo; + LwM2mValidateCredentialsSecurityInfo lwM2MValidateCredentialsSecurityInfo; @Autowired public LwM2MTransportContextServer context; @@ -72,8 +72,8 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public List getAllByEndpoint(String endPoint) { String endPointKey = endPoint; - ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(endPointKey, TypeServer.BOOTSTRAP); - if (store.getBootstrapJsonCredential() != null) { + ReadResultSecurityStore store = lwM2MValidateCredentialsSecurityInfo.validateCredentialsSecurityInfo(endPointKey, TypeServer.BOOTSTRAP); + if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { /** add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfigNew = store.getBootstrapConfig(); @@ -86,7 +86,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } bootstrapConfigStore.add(endPoint, bsConfigNew); } catch (InvalidConfigurationException e) { - e.printStackTrace(); + log.error("", e); } return store.getSecurityInfo() == null ? null : Arrays.asList(store.getSecurityInfo()); } @@ -96,17 +96,16 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { - ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(identity, TypeServer.BOOTSTRAP); - /** add value to store from BootstrapJson */ - this.setBootstrapConfigScurityInfo(store); - - if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + ReadResultSecurityStore store = lwM2MValidateCredentialsSecurityInfo.validateCredentialsSecurityInfo(identity, TypeServer.BOOTSTRAP); + if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + /** add value to store from BootstrapJson */ + this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfig = store.getBootstrapConfig(); if (bsConfig.security != null) { try { bootstrapConfigStore.add(store.getEndPoint(), bsConfig); } catch (InvalidConfigurationException e) { - e.printStackTrace(); + log.error("", e); } return store.getSecurityInfo(); } @@ -154,33 +153,36 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private LwM2MBootstrapConfig getParametersBootstrap(ReadResultSecurityStore store) { try { JsonObject bootstrapJsonCredential = store.getBootstrapJsonCredential(); - ObjectMapper mapper = new ObjectMapper(); - LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class); - JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); - lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class); - LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); - LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class); - UUID sessionUUiD = UUID.randomUUID(); - TransportProtos.SessionInfoProto sessionInfo = context.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); - context.getTransportService().registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(null, sessionInfo)); - if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { - lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); - lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); - String logMsg = String.format(LOG_LW2M_INFO + ": getParametersBootstrap: %s Access connect client with bootstrap server.", store.getEndPoint()); - context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo); - return lwM2MBootstrapConfig; - } - else { - log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); - log.error(LOG_LW2M_ERROR + " getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); - String logMsg = String.format(LOG_LW2M_ERROR + ": getParametersBootstrap: %s Different values SecurityMode between of client and profile.", store.getEndPoint()); - context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo); - return null; + if (bootstrapJsonCredential != null) { + ObjectMapper mapper = new ObjectMapper(); + LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class); + JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); + lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class); + LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); + LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class); + UUID sessionUUiD = UUID.randomUUID(); + TransportProtos.SessionInfoProto sessionInfo = context.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); + context.getTransportService().registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(null, sessionInfo)); + if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { + lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); + lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); + String logMsg = String.format(LOG_LW2M_INFO + ": getParametersBootstrap: %s Access connect client with bootstrap server.", store.getEndPoint()); + context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo); + return lwM2MBootstrapConfig; + } else { + log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); + log.error(LOG_LW2M_ERROR + " getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); + String logMsg = String.format(LOG_LW2M_ERROR + ": getParametersBootstrap: %s Different values SecurityMode between of client and profile.", store.getEndPoint()); + context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo); + return null; + } } } catch (JsonProcessingException e) { log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndPoint(), e.getMessage()); return null; } + log.error("Unable to decode Json or Certificate for [{}]", store.getEndPoint()); + return null; } /** @@ -193,7 +195,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { * @return false if not sync between SecurityMode of Bootstrap credential and profile */ private boolean getValidatedSecurityMode(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap, LwM2MServerBootstrap lwm2mFromCredential, LwM2MServerBootstrap profileLwm2mServer) { - return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) && + return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) && lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.getSecurityMode())); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MSetSecurityStoreBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MSetSecurityStoreBootstrap.java deleted file mode 100644 index f648799204..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MSetSecurityStoreBootstrap.java +++ /dev/null @@ -1,205 +0,0 @@ -/** - * Copyright © 2016-2020 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.bootstrap.secure; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.Hex; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; -import org.eclipse.leshan.server.security.EditableSecurityStore; -import org.thingsboard.server.transport.lwm2m.bootstrap.LwM2MTransportContextBootstrap; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; -import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer; - -import java.math.BigInteger; -import java.security.AlgorithmParameters; -import java.security.KeyStore; -import java.security.PublicKey; -import java.security.PrivateKey; -import java.security.KeyFactory; -import java.security.GeneralSecurityException; -import java.security.KeyStoreException; -import java.security.cert.X509Certificate; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.ECPoint; -import java.security.spec.KeySpec; -import java.security.spec.ECPrivateKeySpec; -import java.util.Arrays; - -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; - -@Slf4j -@Data -public class LwM2MSetSecurityStoreBootstrap { - - private KeyStore keyStore; - private PublicKey publicKey; - private PrivateKey privateKey; - private LwM2MTransportContextBootstrap contextBs; - private LwM2MTransportContextServer contextS; - private LeshanBootstrapServerBuilder builder; - EditableSecurityStore securityStore; - - public LwM2MSetSecurityStoreBootstrap(LeshanBootstrapServerBuilder builder, LwM2MTransportContextBootstrap contextBs, LwM2MTransportContextServer contextS, LwM2MSecurityMode dtlsMode) { - this.builder = builder; - this.contextBs = contextBs; - this.contextS = contextS; - /** Set securityStore with new registrationStore */ - - switch (dtlsMode) { - /** Use No_Sec only */ - case NO_SEC: - setServerWithX509Cert(NO_SEC.code); - break; - /** Use PSK/RPK */ - case PSK: - case RPK: - setRPK(); - break; - case X509: - setServerWithX509Cert(X509.code); - break; - /** Use X509_EST only */ - case X509_EST: - // TODO support sentinel pool and make pool configurable - break; - /** Use ather X509, PSK, No_Sec ?? */ - default: - break; - } - } - - private void setRPK() { - try { - /** Get Elliptic Curve Parameter spec for secp256r1 */ - AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); - algoParameters.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) { - /** Get point values */ - byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray()); - byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray()); - /** Create key specs */ - KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), - parameterSpec); - /** Get keys */ - this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - } - if (this.contextBs.getCtxBootStrap().getBootstrapPrivateS() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateS().isEmpty()) { - /** Get point values */ - byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateS().toCharArray()); - /** Create key specs */ - KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); - /** Get keys */ - this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); - } - if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && - this.privateKey != null && this.privateKey.getEncoded().length > 0) { - this.builder.setPublicKey(this.publicKey); - this.builder.setPrivateKey(this.privateKey); - this.contextBs.getCtxBootStrap().setBootstrapPublicKey(this.publicKey); - getParamsRPK(); - } - } catch (GeneralSecurityException | IllegalArgumentException e) { - log.error("[{}] Failed generate Server PSK/RPK", e.getMessage()); - throw new RuntimeException(e); - } - } - - private void setServerWithX509Cert(int securityModeCode) { - try { - if (this.contextS.getCtxServer().getKeyStoreValue() != null) { - KeyStore keyStoreServer = this.contextS.getCtxServer().getKeyStoreValue(); - setBuilderX509(); - X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getCtxServer().getRootAlias()); - if (rootCAX509Cert != null && securityModeCode == X509.code) { - X509Certificate[] trustedCertificates = new X509Certificate[1]; - trustedCertificates[0] = rootCAX509Cert; - this.builder.setTrustedCertificates(trustedCertificates); - } else { - /** by default trust all */ - this.builder.setTrustedCertificates(new X509Certificate[0]); - } - } - else { - /** by default trust all */ - this.builder.setTrustedCertificates(new X509Certificate[0]); - log.error("Unable to load X509 files for BootStrapServer"); - } - } catch (KeyStoreException ex) { - log.error("[{}] Unable to load X509 files server", ex.getMessage()); - } - - } - - private void setBuilderX509() { - /** - * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE - * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks - */ - try { - X509Certificate serverCertificate = (X509Certificate) this.contextS.getCtxServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias()); - this.privateKey = (PrivateKey) this.contextS.getCtxServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getCtxServer().getKeyStorePasswordServer() == null ? null : this.contextS.getCtxServer().getKeyStorePasswordServer().toCharArray()); - if (this.privateKey != null && this.privateKey.getEncoded().length > 0) { - this.builder.setPrivateKey(this.privateKey); - } - if (serverCertificate != null) { - this.builder.setCertificateChain(new X509Certificate[]{serverCertificate}); - this.contextBs.getCtxBootStrap().setBootstrapCertificate(serverCertificate); - } - } catch (Exception ex) { - log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); - } - } - - private void getParamsRPK() { - if (this.publicKey instanceof ECPublicKey) { - /** Get x coordinate */ - byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray(); - if (x[0] == 0) - x = Arrays.copyOfRange(x, 1, x.length); - - /** Get Y coordinate */ - byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray(); - if (y[0] == 0) - y = Arrays.copyOfRange(y, 1, y.length); - - /** Get Curves params */ - String params = ((ECPublicKey) this.publicKey).getParams().toString(); - log.info( - " \nBootstrap uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]", - params, Hex.encodeHexString(x), Hex.encodeHexString(y), - Hex.encodeHexString(this.publicKey.getEncoded()), - Hex.encodeHexString(this.privateKey.getEncoded())); - } else { - throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); - } - } - -// private void getParamsX509() { -// try { -// log.info("BootStrap uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]", -// Hex.encodeHexString(this.certificate.getEncoded()), -// Hex.encodeHexString(this.privateKey.getEncoded())); -// } catch (CertificateEncodingException e) { -// e.printStackTrace(); -// } -// } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java index 1d70ae2d83..0921df1104 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java @@ -73,15 +73,15 @@ public class LWM2MGenerationPSkRPkECC { try { kpg = KeyPairGenerator.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); + log.error("", e); } catch (NoSuchProviderException e) { - e.printStackTrace(); + log.error("", e); } ECGenParameterSpec ecsp = new ECGenParameterSpec(nameParameterSpec); try { kpg.initialize(ecsp); } catch (InvalidAlgorithmParameterException e) { - e.printStackTrace(); + log.error("", e); } KeyPair kp = kpg.genKeyPair(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MGetSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mValidateCredentialsSecurityInfo.java similarity index 80% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MGetSecurityInfo.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mValidateCredentialsSecurityInfo.java index a02107ca45..0983f9c87f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MGetSecurityInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mValidateCredentialsSecurityInfo.java @@ -44,11 +44,10 @@ import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PS import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; - @Slf4j @Component("LwM2MGetSecurityInfo") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") -public class LwM2MGetSecurityInfo { +public class LwM2mValidateCredentialsSecurityInfo { @Autowired public LwM2MTransportContextServer contextS; @@ -57,7 +56,13 @@ public class LwM2MGetSecurityInfo { public LwM2MTransportContextBootstrap contextBS; - public ReadResultSecurityStore getSecurityInfo(String endPoint, TypeServer keyValue) { + /** + * Request to thingsboard Response from thingsboard ValidateDeviceLwM2MCredentials + * @param endPoint - + * @param keyValue - + * @return ValidateDeviceCredentialsResponseMsg and SecurityInfo + */ + public ReadResultSecurityStore validateCredentialsSecurityInfo(String endPoint, TypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1]; contextS.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endPoint).build(), @@ -65,7 +70,7 @@ public class LwM2MGetSecurityInfo { @Override public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) { String credentialsBody = msg.getCredentialsBody(); - resultSecurityStore[0] = putSecurityInfo(endPoint, msg.getDeviceInfo().getDeviceName(), credentialsBody, keyValue); + resultSecurityStore[0] = createSecurityInfo(endPoint, credentialsBody, keyValue); resultSecurityStore[0].setMsg(msg); Optional deviceProfileOpt = LwM2MTransportHandler.decode(msg.getProfileBody().toByteArray()); deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile)); @@ -74,20 +79,27 @@ public class LwM2MGetSecurityInfo { @Override public void onError(Throwable e) { - log.trace("[{}] Failed to process credentials PSK: {}", endPoint, e); - resultSecurityStore[0] = putSecurityInfo(endPoint, null, null, null); + log.trace("[{}] [{}] Failed to process credentials PSK ", endPoint, e.toString()); + resultSecurityStore[0] = createSecurityInfo(endPoint, null, null); latch.countDown(); } }); try { latch.await(contextS.getCtxServer().getTimeout(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { - e.printStackTrace(); + log.error("", e); } return resultSecurityStore[0]; } - private ReadResultSecurityStore putSecurityInfo(String endPoint, String deviceName, String jsonStr, TypeServer keyValue) { + /** + * Create new SecurityInfo + * @param endPoint - + * @param jsonStr - + * @param keyValue - + * @return SecurityInfo + */ + private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, TypeServer keyValue) { ReadResultSecurityStore result = new ReadResultSecurityStore(); JsonObject objectMsg = LwM2MTransportHandler.validateJson(jsonStr); if (objectMsg != null && !objectMsg.isJsonNull()) { @@ -103,20 +115,21 @@ public class LwM2MGetSecurityInfo { if (keyValue.equals(TypeServer.BOOTSTRAP)) { result.setBootstrapJsonCredential(object); result.setEndPoint(endPoint); + result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code); } else { LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase()); switch (lwM2MSecurityMode) { case NO_SEC: - getClientSecurityInfoNoSec(result); + createClientSecurityInfoNoSec(result); break; case PSK: - getClientSecurityInfoPSK(result, endPoint, object); + createClientSecurityInfoPSK(result, endPoint, object); break; case RPK: - getClientSecurityInfoRPK(result, endPoint, object); + createClientSecurityInfoRPK(result, endPoint, object); break; case X509: - getClientSecurityInfoX509(result, endPoint); + createClientSecurityInfoX509(result, endPoint); break; default: break; @@ -127,12 +140,12 @@ public class LwM2MGetSecurityInfo { return result; } - private void getClientSecurityInfoNoSec(ReadResultSecurityStore result) { + private void createClientSecurityInfoNoSec(ReadResultSecurityStore result) { result.setSecurityInfo(null); result.setSecurityMode(NO_SEC.code); } - private void getClientSecurityInfoPSK(ReadResultSecurityStore result, String endPoint, JsonObject object) { + private void createClientSecurityInfoPSK(ReadResultSecurityStore result, String endPoint, JsonObject object) { /** PSK Deserialization */ String identity = (object.has("identity") && object.get("identity").isJsonPrimitive()) ? object.get("identity").getAsString() : null; if (identity != null && !identity.isEmpty()) { @@ -152,7 +165,7 @@ public class LwM2MGetSecurityInfo { } } - private void getClientSecurityInfoRPK(ReadResultSecurityStore result, String endpoint, JsonObject object) { + private void createClientSecurityInfoRPK(ReadResultSecurityStore result, String endpoint, JsonObject object) { try { if (object.has("key") && object.get("key").isJsonPrimitive()) { byte[] rpkkey = Hex.decodeHex(object.get("key").getAsString().toLowerCase().toCharArray()); @@ -167,7 +180,7 @@ public class LwM2MGetSecurityInfo { } } - private void getClientSecurityInfoX509(ReadResultSecurityStore result, String endpoint) { + private void createClientSecurityInfoX509(ReadResultSecurityStore result, String endpoint) { result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); result.setSecurityMode(X509.code); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSessionMsgListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSessionMsgListener.java index 97963038b8..7c3450486e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSessionMsgListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSessionMsgListener.java @@ -33,10 +33,10 @@ import java.util.Optional; @Slf4j public class LwM2MSessionMsgListener implements GenericFutureListener>, SessionMsgListener { - private LwM2MTransportService service; + private LwM2MTransportServiceImpl service; private TransportProtos.SessionInfoProto sessionInfo; - public LwM2MSessionMsgListener(LwM2MTransportService service, TransportProtos.SessionInfoProto sessionInfo) { + public LwM2MSessionMsgListener(LwM2MTransportServiceImpl service, TransportProtos.SessionInfoProto sessionInfo) { this.service = service; this.sessionInfo = sessionInfo; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java index 14d06eb7e6..76cc2a8015 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java @@ -31,20 +31,14 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.util.Hex; -import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.nustaq.serialization.FSTConfiguration; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; -import javax.annotation.PostConstruct; import java.io.File; import java.io.IOException; import java.util.Arrays; @@ -53,8 +47,8 @@ import java.util.LinkedList; import java.util.Optional; @Slf4j -@Component("LwM2MTransportHandler") -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") +//@Component("LwM2MTransportHandler") +//@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2MTransportHandler { // We choose a default timeout a bit higher to the MAX_TRANSMIT_WAIT(62-93s) which is the time from starting to @@ -114,32 +108,38 @@ public class LwM2MTransportHandler { public static final String SERVICE_CHANNEL = "SERVICE"; public static final String RESPONSE_CHANNEL = "RESP"; - @Autowired - @Qualifier("LeshanServerCert") - private LeshanServer lhServerCert; +// @Autowired +// @Qualifier("LeshanServerCert") +// private LeshanServer lhServerCert; +// +// @Autowired +// @Qualifier("LeshanServerNoSecPskRpk") +// private LeshanServer lhServerNoSecPskRpk; - @Autowired - @Qualifier("leshanServerNoSecPskRpk") - private LeshanServer lhServerNoSecPskRpk; +// @Autowired +// @Qualifier("ServerListenerCert") +// private LwM2mServerListener serverListenerCert; +// +// @Autowired +// @Qualifier("ServerListenerNoSecPskRpk") +// private LwM2mServerListener serverListenerNoSecPskRpk; - @Autowired - private LwM2MTransportService service; - @PostConstruct - public void init() { - try { - LwM2mServerListener lwM2mServerListener = new LwM2mServerListener(lhServerCert, service); - this.lhServerCert.getRegistrationService().addListener(lwM2mServerListener.registrationListener); - this.lhServerCert.getPresenceService().addListener(lwM2mServerListener.presenceListener); - this.lhServerCert.getObservationService().addListener(lwM2mServerListener.observationListener); - lwM2mServerListener = new LwM2mServerListener(lhServerNoSecPskRpk, service); - this.lhServerNoSecPskRpk.getRegistrationService().addListener(lwM2mServerListener.registrationListener); - this.lhServerNoSecPskRpk.getPresenceService().addListener(lwM2mServerListener.presenceListener); - this.lhServerNoSecPskRpk.getObservationService().addListener(lwM2mServerListener.observationListener); - } catch (java.lang.NullPointerException e) { - log.error("init [{}]", e.toString()); - } - } +// @PostConstruct +// public void init() { +// try { +// serverListenerCert.init(lhServerCert); +// this.lhServerCert.getRegistrationService().addListener(serverListenerCert.registrationListener); +// this.lhServerCert.getPresenceService().addListener(serverListenerCert.presenceListener); +// this.lhServerCert.getObservationService().addListener(serverListenerCert.observationListener); +// serverListenerNoSecPskRpk.init(lhServerNoSecPskRpk); +// this.lhServerNoSecPskRpk.getRegistrationService().addListener(serverListenerNoSecPskRpk.registrationListener); +// this.lhServerNoSecPskRpk.getPresenceService().addListener(serverListenerNoSecPskRpk.presenceListener); +// this.lhServerNoSecPskRpk.getObservationService().addListener(serverListenerNoSecPskRpk.observationListener); +// } catch (Exception e) { +// log.error("init [{}]", e.toString()); +// } +// } public static NetworkConfig getCoapConfig() { NetworkConfig coapConfig; @@ -217,7 +217,7 @@ public class LwM2MTransportHandler { JsonObject objectMsg = (observeAttrStr != null) ? validateJson(observeAttrStr) : null; return (getValidateCredentialsBodyFromThingsboard(objectMsg)) ? objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject() : null; } catch (IOException e) { - e.printStackTrace(); + log.error("", e); } } return null; @@ -232,7 +232,7 @@ public class LwM2MTransportHandler { JsonObject objectMsg = (bootstrapStr != null) ? validateJson(bootstrapStr) : null; return (getValidateBootstrapProfileFromThingsboard(objectMsg)) ? objectMsg.get(BOOTSTRAP).getAsJsonObject() : null; } catch (IOException e) { - e.printStackTrace(); + log.error("", e); } } return null; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java index 5ee37ac380..bc7cbd5b46 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java @@ -86,7 +86,7 @@ public class LwM2MTransportRequest { private LwM2mValueConverterImpl converter; @Autowired - LwM2MTransportService service; + LwM2MTransportServiceImpl service; @PostConstruct diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java index c17400ede8..ac2221054c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java @@ -20,10 +20,16 @@ import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder; +import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.VersionedModelProvider; +import org.eclipse.leshan.server.redis.RedisRegistrationStore; +import org.eclipse.leshan.server.redis.RedisSecurityStore; +import org.eclipse.leshan.server.security.DefaultAuthorizer; +import org.eclipse.leshan.server.security.EditableSecurityStore; +import org.eclipse.leshan.server.security.SecurityChecker; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Bean; @@ -31,11 +37,33 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; -import org.thingsboard.server.transport.lwm2m.server.secure.LwM2MSetSecurityStoreServer; import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.util.Pool; + +import java.math.BigInteger; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.KeySpec; +import java.util.Arrays; + import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig; @@ -45,6 +73,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandle @Configuration("LwM2MTransportServerConfiguration") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2MTransportServerConfiguration { + private PublicKey publicKey; + private PrivateKey privateKey; @Autowired private LwM2MTransportContextServer context; @@ -52,14 +82,26 @@ public class LwM2MTransportServerConfiguration { @Autowired private LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; + @Bean + public LwM2mServerListener lwM2mServerListenerCert() { + return new LwM2mServerListener(); + } + + @Bean + public LwM2mServerListener lwM2mServerListenerNoSecPskRpk() { + return new LwM2mServerListener(); + } + @Primary @Bean(name = "LeshanServerCert") public LeshanServer getLeshanServerCert() { log.info("Starting LwM2M transport ServerCert... PostConstruct"); - return getLeshanServer(this.context.getCtxServer().getServerPortCert(), this.context.getCtxServer().getServerSecurePortCert(), X509); + LeshanServer leshanServerCert = getLeshanServer(this.context.getCtxServer().getServerPortCert(), this.context.getCtxServer().getServerSecurePortCert(), X509); + + return leshanServerCert; } - @Bean(name = "leshanServerNoSecPskRpk") + @Bean(name = "LeshanServerNoSecPskRpk") public LeshanServer getLeshanServerNoSecPskRpk() { log.info("Starting LwM2M transport ServerNoSecPskRpk... PostConstruct"); return getLeshanServer(this.context.getCtxServer().getServerPort(), this.context.getCtxServer().getServerSecurePort(), RPK); @@ -94,9 +136,178 @@ public class LwM2MTransportServerConfiguration { /** Create DTLS security mode * There can be only one DTLS security mode */ - new LwM2MSetSecurityStoreServer(builder, context, lwM2mInMemorySecurityStore, dtlsMode); + this.LwM2MSetSecurityStoreServer(builder, dtlsMode); /** Create LWM2M server */ return builder.build(); } + + private void LwM2MSetSecurityStoreServer(LeshanServerBuilder builder, LwM2MSecurityMode dtlsMode) { + /** Set securityStore with new registrationStore */ + EditableSecurityStore securityStore = lwM2mInMemorySecurityStore; + + switch (dtlsMode) { + /** Use PSK only */ + case PSK: + generatePSK_RPK(); + if (this.privateKey != null && this.privateKey.getEncoded().length > 0) { + builder.setPrivateKey(this.privateKey); + builder.setPublicKey(null); + infoParamsPSK(); + } + break; + /** Use RPK only */ + case RPK: + generatePSK_RPK(); + if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && + this.privateKey != null && this.privateKey.getEncoded().length > 0) { + builder.setPublicKey(this.publicKey); + builder.setPrivateKey(this.privateKey); + infoParamsRPK(); + } + break; + /** Use x509 only */ + case X509: + setServerWithX509Cert(builder); + break; + /** No security */ + case NO_SEC: + builder.setTrustedCertificates(new X509Certificate[0]); + break; + /** Use x509 with EST */ + case X509_EST: + // TODO support sentinel pool and make pool configurable + break; + case REDIS: + /** + * Set securityStore with new registrationStore (if use redis store) + * Connect to redis + */ + Pool jedis = null; + try { + jedis = new JedisPool(new URI(this.context.getCtxServer().getRedisUrl())); + securityStore = new RedisSecurityStore(jedis); + builder.setRegistrationStore(new RedisRegistrationStore(jedis)); + } catch (URISyntaxException e) { + log.error("", e); + } + break; + default: + } + + /** Set securityStore with registrationStore (if x509)*/ + if (dtlsMode == X509) { + builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() { + @Override + protected boolean matchX509Identity(String endpoint, String receivedX509CommonName, + String expectedX509CommonName) { + return endpoint.startsWith(expectedX509CommonName); + } + })); + } + + /** Set securityStore with new registrationStore */ + builder.setSecurityStore(securityStore); + } + + private void generatePSK_RPK() { + try { + /** Get Elliptic Curve Parameter spec for secp256r1 */ + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); + algoParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); + if (this.context.getCtxServer().getServerPublicX() != null && !this.context.getCtxServer().getServerPublicX().isEmpty() && this.context.getCtxServer().getServerPublicY() != null && !this.context.getCtxServer().getServerPublicY().isEmpty()) { + /** Get point values */ + byte[] publicX = Hex.decodeHex(this.context.getCtxServer().getServerPublicX().toCharArray()); + byte[] publicY = Hex.decodeHex(this.context.getCtxServer().getServerPublicY().toCharArray()); + /** Create key specs */ + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), + parameterSpec); + /** Get keys */ + this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + } + if (this.context.getCtxServer().getServerPrivateS() != null && !this.context.getCtxServer().getServerPrivateS().isEmpty()) { + /** Get point values */ + byte[] privateS = Hex.decodeHex(this.context.getCtxServer().getServerPrivateS().toCharArray()); + /** Create key specs */ + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); + /** Get keys */ + this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); + } + } catch (GeneralSecurityException | IllegalArgumentException e) { + log.error("[{}] Failed generate Server PSK/RPK", e.getMessage()); + throw new RuntimeException(e); + } + } + + private void infoParamsPSK() { + log.info("\nServer uses PSK -> private key : \n security key : [{}] \n serverSecureURI : [{}]", + Hex.encodeHexString(this.privateKey.getEncoded()), + this.context.getCtxServer().getServerSecureHost() + ":" + Integer.toString(this.context.getCtxServer().getServerSecurePort())); + } + + private void infoParamsRPK() { + if (this.publicKey instanceof ECPublicKey) { + /** Get x coordinate */ + byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray(); + if (x[0] == 0) + x = Arrays.copyOfRange(x, 1, x.length); + + /** Get Y coordinate */ + byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray(); + if (y[0] == 0) + y = Arrays.copyOfRange(y, 1, y.length); + + /** Get Curves params */ + String params = ((ECPublicKey) this.publicKey).getParams().toString(); + log.info( + " \nServer uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]", + params, Hex.encodeHexString(x), Hex.encodeHexString(y), + Hex.encodeHexString(this.publicKey.getEncoded()), + Hex.encodeHexString(this.privateKey.getEncoded())); + } else { + throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); + } + } + + + private void setServerWithX509Cert(LeshanServerBuilder builder) { + try { + if (this.context.getCtxServer().getKeyStoreValue() != null) { + setBuilderX509(builder); + X509Certificate rootCAX509Cert = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getRootAlias()); + if (rootCAX509Cert != null) { + X509Certificate[] trustedCertificates = new X509Certificate[1]; + trustedCertificates[0] = rootCAX509Cert; + builder.setTrustedCertificates(trustedCertificates); + } else { + /** by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + } + } else { + /** by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + log.error("Unable to load X509 files for LWM2MServer"); + } + } catch (KeyStoreException ex) { + log.error("[{}] Unable to load X509 files server", ex.getMessage()); + } + } + + private void setBuilderX509(LeshanServerBuilder builder) { + /** + * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE + * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks + */ + try { + X509Certificate serverCertificate = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getServerAlias()); + PrivateKey privateKey = (PrivateKey) this.context.getCtxServer().getKeyStoreValue().getKey(this.context.getCtxServer().getServerAlias(), this.context.getCtxServer().getKeyStorePasswordServer() == null ? null : this.context.getCtxServer().getKeyStorePasswordServer().toCharArray()); + builder.setPrivateKey(privateKey); + builder.setCertificateChain(new X509Certificate[]{serverCertificate}); + } catch (Exception ex) { + log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); + } + } + + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java index 4a43407a73..36d39130a3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java @@ -20,14 +20,15 @@ import org.eclipse.leshan.server.californium.LeshanServer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; + import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Slf4j -@Service("LwM2MTransportServerInitializer") +@Component("LwM2MTransportServerInitializer") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2MTransportServerInitializer { @@ -36,8 +37,18 @@ public class LwM2MTransportServerInitializer { private LeshanServer lhServerCert; @Autowired - @Qualifier("leshanServerNoSecPskRpk") + @Qualifier("LeshanServerNoSecPskRpk") private LeshanServer lhServerNoSecPskRpk; +// +// @Autowired +// @Qualifier("LeshanServerListener") +// private LwM2mServerListener lwM2mServerListener; + + @Autowired + private LwM2mServerListener lwM2mServerListenerNoSecPskRpk; + + @Autowired + private LwM2mServerListener lwM2mServerListenerCert; @Autowired private LwM2MTransportContextServer context; @@ -46,19 +57,33 @@ public class LwM2MTransportServerInitializer { public void init() { if (this.context.getCtxServer().getEnableGenPskRpk()) new LWM2MGenerationPSkRPkECC(); if (this.context.getCtxServer().isServerStartAll()) { - this.lhServerCert.start(); - this.lhServerNoSecPskRpk.start(); - } - else { + this.startLhServerCert(); + this.startLhServerNoSecPskRpk(); + } else { if (this.context.getCtxServer().getServerDtlsMode() == LwM2MSecurityMode.X509.code) { - this.lhServerCert.start(); - } - else { - this.lhServerNoSecPskRpk.start(); + this.startLhServerCert(); + } else { + this.startLhServerNoSecPskRpk(); } } } + private void startLhServerCert() { + this.lhServerCert.start(); + LwM2mServerListener serverListenerCert = this.lwM2mServerListenerCert.init(this.lhServerCert); + this.lhServerCert.getRegistrationService().addListener(serverListenerCert.registrationListener); + this.lhServerCert.getPresenceService().addListener(serverListenerCert.presenceListener); + this.lhServerCert.getObservationService().addListener(serverListenerCert.observationListener); + } + + private void startLhServerNoSecPskRpk() { + this.lhServerNoSecPskRpk.start(); + LwM2mServerListener serverListenerNoSecPskRpk = this.lwM2mServerListenerNoSecPskRpk.init(this.lhServerNoSecPskRpk); + this.lhServerNoSecPskRpk.getRegistrationService().addListener(serverListenerNoSecPskRpk.registrationListener); + this.lhServerNoSecPskRpk.getPresenceService().addListener(serverListenerNoSecPskRpk.presenceListener); + this.lhServerNoSecPskRpk.getObservationService().addListener(serverListenerNoSecPskRpk.observationListener); + } + @PreDestroy public void shutdown() { log.info("Stopping LwM2M transport Server!"); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java index 9b082ce09f..a9fcbcb669 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java @@ -15,1114 +15,42 @@ */ package org.thingsboard.server.transport.lwm2m.server; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.model.ResourceModel; -import org.eclipse.leshan.core.node.LwM2mMultipleResource; -import org.eclipse.leshan.core.node.LwM2mObject; -import org.eclipse.leshan.core.node.LwM2mObjectInstance; -import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.observation.Observation; -import org.eclipse.leshan.core.request.ContentFormat; -import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadResponse; -import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.registration.Registration; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; -import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; -import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; -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.ResultsAnalyzerParameters; -import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore; -import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import javax.annotation.PostConstruct; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.Random; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; -import static org.thingsboard.server.common.transport.util.JsonUtils.getJsonObject; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.CLIENT_NOT_AUTHORIZED; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEFAULT_TIMEOUT; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEVICE_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEVICE_ATTRIBUTES_TOPIC; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_OBSERVE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_READ; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_TELEMETRY; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_EXECUTE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_WRITE_REPLACE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.SERVICE_CHANNEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getAckCallback; +public interface LwM2MTransportService { -@Slf4j -@Service("LwM2MTransportService") -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") -public class LwM2MTransportService { + void onRegistered(LeshanServer lwServer, Registration registration, Collection previousObsersations); - private ExecutorService executorRegistered; - private ExecutorService executorUpdateRegistered; - private ExecutorService executorUnRegistered; - private LwM2mValueConverterImpl converter; + void updatedReg(LeshanServer lwServer, Registration registration); + void unReg(Registration registration, Collection observations); - @Autowired - private TransportService transportService; + void onSleepingDev(Registration registration); - @Autowired - public LwM2MTransportContextServer context; + void setCancelObservations(LeshanServer lwServer, Registration registration); - @Autowired - private LwM2MTransportRequest lwM2MTransportRequest; + void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path); - @Autowired - LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; + void onObservationResponse(Registration registration, String path, ReadResponse response); - @PostConstruct - public void init() { - this.context.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) context.getCtxServer().getSessionReportTimeout()), context.getCtxServer().getSessionReportTimeout(), TimeUnit.MILLISECONDS); - this.executorRegistered = Executors.newCachedThreadPool( - new NamedThreadFactory(String.format("LwM2M %s channel registered", SERVICE_CHANNEL))); - this.executorUpdateRegistered = Executors.newCachedThreadPool( - new NamedThreadFactory(String.format("LwM2M %s channel update registered", SERVICE_CHANNEL))); - this.executorUnRegistered = Executors.newCachedThreadPool( - new NamedThreadFactory(String.format("LwM2M %s channel un registered", SERVICE_CHANNEL))); - this.converter = LwM2mValueConverterImpl.getInstance(); - } + void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo); - /** - * Start registration device - * Create session: Map, LwM2MClient> - * 1. replaceNewRegistration -> (solving the problem of incorrect termination of the previous session with this endpoint) - * 1.1 When we initialize the registration, we register the session by endpoint. - * 1.2 If the server has incomplete requests (canceling the registration of the previous session), - * delete the previous session only by the previous registration.getId - * 1.2 Add Model (Entity) for client (from registration & observe) by registration.getId - * 1.2 Remove from sessions Model by enpPoint - * Next -> Create new LwM2MClient for current session -> setModelClient... - * - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - * @param previousObsersations - may be null - */ - public void onRegistered(LeshanServer lwServer, Registration registration, Collection previousObsersations) { - executorRegistered.submit(() -> { - try { - log.info("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.updateInSessionsLwM2MClient(lwServer, registration); - if (lwM2MClient != null) { - lwM2MClient.setLwM2MTransportService(this); - lwM2MClient.setSessionUuid(UUID.randomUUID()); - this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client Registered", registration); - this.setLwM2MClient(lwServer, registration, lwM2MClient); - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); - if (sessionInfo != null) { - lwM2MClient.setDeviceUuid(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - lwM2MClient.setProfileUuid(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - lwM2MClient.setDeviceName(sessionInfo.getDeviceName()); - lwM2MClient.setDeviceProfileName(sessionInfo.getDeviceType()); - transportService.registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(this, sessionInfo)); - transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null); - transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); - this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration); - } else { - log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); - } - } else { - log.error("Client: [{}] onRegistered [{}] name [{}] lwM2MClient ", registration.getId(), registration.getEndpoint(), null); - } - } catch (Throwable t) { - log.error("[{}] endpoint [{}] error Unable registration.", registration.getEndpoint(), t); - } - }); - } + void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile); - /** - * if sessionInfo removed from sessions, then new registerAsyncSession - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - */ - public void updatedReg(LeshanServer lwServer, Registration registration) { - executorUpdateRegistered.submit(() -> { - try { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); - if (sessionInfo != null) { - this.checkInactivity(sessionInfo); - log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); - } else { - log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); - } - } catch (Throwable t) { - log.error("[{}] endpoint [{}] error Unable update registration.", registration.getEndpoint(), t); - } - }); - } + void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt); + void doTrigger(LeshanServer lwServer, Registration registration, String path); - /** - * @param registration - Registration LwM2M Client - * @param observations - All paths observations before unReg - * !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect - */ - public void unReg(Registration registration, Collection observations) { - executorUnRegistered.submit(() -> { - try { - this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration); - this.closeClientSession(registration); - } catch (Throwable t) { - log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t); - } - }); - } + void doDisconnect(TransportProtos.SessionInfoProto sessionInfo); - private void closeClientSession(Registration registration) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); - if (sessionInfo != null) { - transportService.deregisterSession(sessionInfo); - this.doCloseSession(sessionInfo); - lwM2mInMemorySecurityStore.delRemoveSessionAndListener(registration.getId()); - if (lwM2mInMemorySecurityStore.getProfiles().size() > 0) { - this.syncSessionsAndProfiles(); - } - log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); - } else { - log.error("Client close session: [{}] unReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); - } - } - public void onSleepingDev(Registration registration) { - log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); - //TODO: associate endpointId with device information. - } - - /** - * Those methods are called by the protocol stage thread pool, this means that execution MUST be done in a short delay, - * * if you need to do long time processing use a dedicated thread pool. - * - * @param registration - - */ - protected void onAwakeDev(Registration registration) { - log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); - //TODO: associate endpointId with device information. - } - - /** - * This method is used to sync with sessions - * Removes a profile if not used in sessions - */ - private void syncSessionsAndProfiles() { - Map profilesClone = lwM2mInMemorySecurityStore.getProfiles().entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - profilesClone.forEach((k, v) -> { - String registrationId = lwM2mInMemorySecurityStore.getSessions().entrySet() - .stream() - .filter(e -> e.getValue().getProfileUuid().equals(k)) - .findFirst() - .map(Map.Entry::getKey) // return the key of the matching entry if found - .orElse(""); - if (registrationId.isEmpty()) { - lwM2mInMemorySecurityStore.getProfiles().remove(k); - } - }); - } - - /** - * #0 Add new ObjectModel to context - * Create new LwM2MClient for current session -> setModelClient... - * #1 Add all ObjectLinks (instance) to control the process of executing requests to the client - * to get the client model with current values - * #2 Get the client model with current values. Analyze the response in -> lwM2MTransportRequest.sendResponse - * - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - * @param lwM2MClient - object with All parameters off client - */ - private void setLwM2MClient(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient) { - Arrays.stream(registration.getObjectLinks()).forEach(url -> { - LwM2mPath pathIds = new LwM2mPath(url.getUrl()); - if (pathIds.isObjectInstance() && !pathIds.isResource()) { - lwM2MClient.getPendingRequests().add(url.getUrl()); - } - }); - // #2 - Arrays.stream(registration.getObjectLinks()).forEach(url -> { - LwM2mPath pathIds = new LwM2mPath(url.getUrl()); - if (pathIds.isObjectInstance() && !pathIds.isResource()) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), - lwM2MClient, null, null, this.context.getCtxServer().getTimeout(), false); - } - }); - } - - /** - * @param registration - Registration LwM2M Client - * @return - sessionInfo after access connect client - */ - private SessionInfoProto getValidateSessionInfo(Registration registration) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); - return getNewSessionInfoProto(lwM2MClient); - - } - - /** - * - * @param registrationId - - * @return - - */ - private SessionInfoProto getValidateSessionInfo(String registrationId) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(null, registrationId); - return getNewSessionInfoProto(lwM2MClient); - } - - private SessionInfoProto getNewSessionInfoProto(LwM2MClient lwM2MClient) { - if (lwM2MClient != null) { - ValidateDeviceCredentialsResponseMsg msg = lwM2MClient.getCredentialsResponse(); - if (msg == null || msg.getDeviceInfo() == null) { - log.error("[{}] [{}]", lwM2MClient.getEndPoint(), CLIENT_NOT_AUTHORIZED); - this.closeClientSession(lwM2MClient.getRegistration()); - return null; - } else { - return SessionInfoProto.newBuilder() - .setNodeId(this.context.getNodeId()) - .setSessionIdMSB(lwM2MClient.getSessionUuid().getMostSignificantBits()) - .setSessionIdLSB(lwM2MClient.getSessionUuid().getLeastSignificantBits()) - .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) - .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) - .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) - .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) - .setDeviceName(msg.getDeviceInfo().getDeviceName()) - .setDeviceType(msg.getDeviceInfo().getDeviceType()) - .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) - .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) - .build(); - } - } - return null; - } - - /** - * Add attribute/telemetry information from Client and credentials/Profile to client model and start observe - * !!! if the resource has an observation, but no telemetry or attribute - the observation will not use - * #1 Sending Attribute Telemetry with value to thingsboard only once at the start of the connection - * #2 Start observe - * - * @param lwM2MClient - LwM2M Client - */ - - public void updatesAndSentModelParameter(LwM2MClient lwM2MClient) { - // #1 - this.updateAttrTelemetry(lwM2MClient.getRegistration(), true, null); - // #2 - this.onSentObserveToClient(lwM2MClient.getLwServer(), lwM2MClient.getRegistration()); - - } - - /** - * If there is a difference in values between the current resource values and the shared attribute values - * when the client connects to the server - * #1 get attributes name from profile include name resources in ModelObject if resource isWritable - * #2.1 #1 size > 0 => send Request getAttributes to thingsboard - * #2.2 #1 size == 0 => continue normal process - * - * @param lwM2MClient - LwM2M Client - */ - public void putDelayedUpdateResourcesThingsboard(LwM2MClient lwM2MClient) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration()); - if (sessionInfo != null) { - //#1.1 + #1.2 - List attrSharedNames = this.getNamesAttrFromProfileIsWritable(lwM2MClient); - if (attrSharedNames.size() > 0) { - //#2.1 - try { - TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getAdaptor().convertToGetAttributes(null, attrSharedNames); - lwM2MClient.getDelayedRequestsId().add(getAttributeMsg.getRequestId()); - transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); - } catch (AdaptorException e) { - log.warn("Failed to decode get attributes request", e); - } - } - // #2.2 - else { - lwM2MClient.onSuccessOrErrorDelayedRequests(null); - } - } - } - - /** - * Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values - * #1 Get path resource by result attributesResponse - * #1.1 If two names have equal path => last time attribute - * #2.1 if there is a difference in values between the current resource values and the shared attribute values - * => sent to client Request Update of value (new value from shared attribute) - * and LwM2MClient.delayedRequests.add(path) - * #2.1 if there is not a difference in values between the current resource values and the shared attribute values - * - * @param attributesResponse - - * @param sessionInfo - - */ - public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClient(sessionInfo); - if (lwM2MClient.getDelayedRequestsId().contains(attributesResponse.getRequestId())) { - attributesResponse.getSharedAttributeListList().forEach(attr -> { - String path = this.getPathAttributeUpdate(sessionInfo, attr.getKv().getKey()); - // #1.1 - if (lwM2MClient.getDelayedRequests().containsKey(path) && attr.getTs() > lwM2MClient.getDelayedRequests().get(path).getTs()) { - lwM2MClient.getDelayedRequests().put(path, attr); - } else { - lwM2MClient.getDelayedRequests().put(path, attr); - } - }); - // #2.1 - lwM2MClient.getDelayedRequests().forEach((k, v) -> { - ArrayList listV = new ArrayList<>(); - listV.add(v.getKv()); - this.putDelayedUpdateResourcesClient(lwM2MClient, this.getResourceValueToString(lwM2MClient, k), getJsonObject(listV).get(v.getKv().getKey()), k); - }); - lwM2MClient.getDelayedRequestsId().remove(attributesResponse.getRequestId()); - if (lwM2MClient.getDelayedRequests().size() == 0) { - lwM2MClient.onSuccessOrErrorDelayedRequests(null); - } - } - } - - private void putDelayedUpdateResourcesClient(LwM2MClient lwM2MClient, Object valueOld, Object valueNew, String path) { - if (valueNew != null && !valueNew.toString().equals(valueOld.toString())) { - lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, - ContentFormat.TLV.getName(), lwM2MClient, null, valueNew, this.context.getCtxServer().getTimeout(), - true); - } - } - - /** - * Get names and keyNames from profile shared!!!! attr resources IsWritable - * @param lwM2MClient - - * @return ArrayList keyNames from profile attr resources shared!!!! && IsWritable - */ - private List getNamesAttrFromProfileIsWritable(LwM2MClient lwM2MClient) { - AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(lwM2MClient.getProfileUuid()); - Set attrSet = new Gson().fromJson(profile.getPostAttributeProfile(), Set.class); - ConcurrentMap keyNamesMap = new Gson().fromJson(profile.getPostKeyNameProfile().toString(), ConcurrentHashMap.class); - - ConcurrentMap keyNamesIsWritable = keyNamesMap.entrySet() - .stream() - .filter(e -> (attrSet.contains(e.getKey()) && context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())) != null && - context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())).operations.isWritable())) - .collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)); - - Set namesIsWritable = ConcurrentHashMap.newKeySet(); - namesIsWritable.addAll(new HashSet<>(keyNamesIsWritable.values())); - return new ArrayList<>(namesIsWritable); - } - - - /** - * Sent Attribute and Telemetry to Thingsboard - * #1 - get AttrName/TelemetryName with value: - * #1.1 from Client - * #1.2 from LwM2MClient: - * -- resourceId == path from AttrTelemetryObserveValue.postAttributeProfile/postTelemetryProfile/postObserveProfile - * -- AttrName/TelemetryName == resourceName from ModelObject.objectModel, value from ModelObject.instance.resource(resourceId) - * #2 - set Attribute/Telemetry - * - * @param registration - Registration LwM2M Client - */ - private void updateAttrTelemetry(Registration registration, boolean start, Set paths) { - JsonObject attributes = new JsonObject(); - JsonObject telemetries = new JsonObject(); - if (start) { - // #1.1 - JsonObject attributeClient = this.getAttributeClient(registration); - if (attributeClient != null) { - attributeClient.entrySet().forEach(p -> attributes.add(p.getKey(), p.getValue())); - } - } - // #1.2 - CountDownLatch cancelLatch = new CountDownLatch(1); - this.getParametersFromProfile(attributes, telemetries, registration, paths); - cancelLatch.countDown(); - try { - cancelLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - log.error("[{}] updateAttrTelemetry", e.toString()); - } - if (attributes.getAsJsonObject().entrySet().size() > 0) - this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration); - if (telemetries.getAsJsonObject().entrySet().size() > 0) - this.updateParametersOnThingsboard(telemetries, DEVICE_TELEMETRY_TOPIC, registration); - } - - /** - * get AttrName/TelemetryName with value from Client - * - * @param registration - - * @return - JsonObject, format: {name: value}} - */ - private JsonObject getAttributeClient(Registration registration) { - if (registration.getAdditionalRegistrationAttributes().size() > 0) { - JsonObject resNameValues = new JsonObject(); - registration.getAdditionalRegistrationAttributes().forEach(resNameValues::addProperty); - return resNameValues; - } - return null; - } - - /** - * @param attributes - new JsonObject - * @param telemetry - new JsonObject - * @param registration - Registration LwM2M Client - * result: add to JsonObject those resources to which the user is subscribed and they have a value - * if path==null add All resources else only one - * (attributes/telemetry): new {name(Attr/Telemetry):value} - */ - private void getParametersFromProfile(JsonObject attributes, JsonObject telemetry, Registration registration, Set path) { - AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid()); - attrTelemetryObserveValue.getPostAttributeProfile().forEach(p -> { - LwM2mPath pathIds = new LwM2mPath(p.getAsString().toString()); - if (pathIds.isResource()) { - if (path == null || path.contains(p.getAsString())) { - this.addParameters(p.getAsString().toString(), attributes, registration); - } - } - }); - attrTelemetryObserveValue.getPostTelemetryProfile().forEach(p -> { - LwM2mPath pathIds = new LwM2mPath(p.getAsString().toString()); - if (pathIds.isResource()) { - if (path == null || path.contains(p.getAsString())) { - this.addParameters(p.getAsString().toString(), telemetry, registration); - } - } - }); - } - - /** - * @param parameters - JsonObject attributes/telemetry - * @param registration - Registration LwM2M Client - */ - private void addParameters(String path, JsonObject parameters, Registration registration) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()); - JsonObject names = lwM2mInMemorySecurityStore.getProfiles().get(lwM2MClient.getProfileUuid()).getPostKeyNameProfile(); - String resName = String.valueOf(names.get(path)); - if (resName != null && !resName.isEmpty()) { - try { - String resValue = this.getResourceValueToString(lwM2MClient, path); - if (resValue != null) { - parameters.addProperty(resName, resValue); - } - } catch (Exception e) { - log.error(e.getStackTrace().toString()); - } - } - } - - /** - * Prepare Sent to Thigsboard callback - Attribute or Telemetry - * - * @param msg - JsonArray: [{name: value}] - * @param topicName - Api Attribute or Telemetry - * @param registration - Id of Registration LwM2M Client - */ - public void updateParametersOnThingsboard(JsonElement msg, String topicName, Registration registration) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); - if (sessionInfo != null) { - context.sentParametersOnThingsboard(msg, topicName, sessionInfo); - } else { - log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registration, null); - } - } - - /** - * Start observe - * #1 - Analyze: - * #1.1 path in observe == (attribute or telemetry) - * #2 Analyze after sent request (response): - * #2.1 First: lwM2MTransportRequest.sendResponse -> ObservationListener.newObservation - * #2.2 Next: ObservationListener.onResponse * - * - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - */ - private void onSentObserveToClient(LeshanServer lwServer, Registration registration) { - if (lwServer.getObservationService().getObservations(registration).size() > 0) { - this.setCancelObservations(lwServer, registration); - } - UUID profileUUid = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid(); - AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(profileUUid); - attrTelemetryObserveValue.getPostObserveProfile().forEach(p -> { - // #1.1 - String target = (getValidateObserve(attrTelemetryObserveValue.getPostAttributeProfile(), p.getAsString().toString())) ? - p.getAsString().toString() : (getValidateObserve(attrTelemetryObserveValue.getPostTelemetryProfile(), p.getAsString().toString())) ? - p.getAsString().toString() : null; - if (target != null) { - // #2 - if (this.getResourceValueToString(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()), target) != null) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, GET_TYPE_OPER_OBSERVE, - null, null, null, null, this.context.getCtxServer().getTimeout(), - false); - } - } - }); - } - - public void setCancelObservations(LeshanServer lwServer, Registration registration) { - if (registration != null) { - Set observations = lwServer.getObservationService().getObservations(registration); - observations.forEach(observation -> this.setCancelObservationRecourse(lwServer, registration, observation.getPath().toString())); - } - } - - /** - * lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); - * At server side this will not remove the observation from the observation store, to do it you need to use - * {@code ObservationService#cancelObservation()} - */ - public void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path) { - CountDownLatch cancelLatch = new CountDownLatch(1); - lwServer.getObservationService().cancelObservations(registration, path); - cancelLatch.countDown(); - try { - cancelLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - /** - * @param parameters - JsonArray postAttributeProfile/postTelemetryProfile - * @param path - recourse from postObserveProfile - * @return rez - true if path observe is in attribute/telemetry - */ - private boolean getValidateObserve(JsonElement parameters, String path) { - AtomicBoolean rez = new AtomicBoolean(false); - if (parameters.isJsonArray()) { - parameters.getAsJsonArray().forEach(p -> { - if (p.getAsString().toString().equals(path)) rez.set(true); - } - ); - } else if (parameters.isJsonObject()) { - rez.set((parameters.getAsJsonObject().entrySet()).stream().map(json -> json.toString()) - .filter(path::equals).findAny().orElse(null) != null); - } - return rez.get(); - } - - /** - * Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource - * - * @param registration - Registration LwM2M Client - * @param path - observe - * @param response - observe - */ - - public void onObservationResponse(Registration registration, String path, ReadResponse response) { - if (response.getContent() != null) { - if (response.getContent() instanceof LwM2mObject) { -// LwM2mObject content = (LwM2mObject) response.getContent(); - } else if (response.getContent() instanceof LwM2mObjectInstance) { -// LwM2mObjectInstance content = (LwM2mObjectInstance) response.getContent(); - } else if (response.getContent() instanceof LwM2mSingleResource) { - LwM2mSingleResource content = (LwM2mSingleResource) response.getContent(); - this.onObservationSetResourcesValue(registration, content.getValue(), null, path); - } else if (response.getContent() instanceof LwM2mMultipleResource) { - LwM2mSingleResource content = (LwM2mSingleResource) response.getContent(); - this.onObservationSetResourcesValue(registration, null, content.getValues(), path); - } - } - } - - /** - * Sending observe value of resources to thingsboard - * #1 Return old Value Resource from LwM2MClient - * #2 Update new Resources (replace old Resource Value on new Resource Value) - * - * @param registration - Registration LwM2M Client - * @param value - LwM2mSingleResource response.getContent() - * @param values - LwM2mSingleResource response.getContent() - * @param path - resource - */ - private void onObservationSetResourcesValue(Registration registration, Object value, Map values, String path) { - CountDownLatch respLatch = new CountDownLatch(1); - boolean isChange = false; - try { - // #1 - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); - LwM2mPath pathIds = new LwM2mPath(path); - log.warn("#0 nameDevice: [{}] resultIds: [{}] value: [{}], values: [{}] ", lwM2MClient.getDeviceName(), pathIds, value, values); - ResourceModel.Type resModelType = context.getCtxServer().getResourceModelType(registration, pathIds); - ResourceValue resValueOld = lwM2MClient.getResources().get(path); - // #2 - if (resValueOld.isMultiInstances() && !values.toString().equals(resValueOld.getResourceValue().toString())) { - ResourceValue resourceValue = new ResourceValue(values, null, true); - lwM2MClient.getResources().put(path, resourceValue); - isChange = true; - } else if (!LwM2MTransportHandler.equalsResourceValue(resValueOld.getValue(), value, resModelType, pathIds)) { - ResourceValue resourceValue = new ResourceValue(null, value, false); - lwM2MClient.getResources().put(path, resourceValue); - isChange = true; - } - } finally { - respLatch.countDown(); - } - try { - respLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (InterruptedException ex) { - ex.printStackTrace(); - log.error("#1_1 Update ResourcesValue after Observation in CountDownLatch is unsuccessfully path: [{}] value: [{}]", path, value); - } - if (isChange) { - Set paths = new HashSet<>(); - paths.add(path); - this.updateAttrTelemetry(registration, false, paths); - } - } - - /** - * @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...) - * config attr/telemetry... in profile - */ - public void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto updateCredentials) { - log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList()); - } - - /** - * Update - sent request in change value resources in Client - * Path to resources from profile equal keyName or from ModelObject equal name - * Only for resources: isWritable && isPresent as attribute in profile -> AttrTelemetryObserveValue (format: CamelCase) - * Delete - nothing * - * - * @param msg - - */ - public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { - if (msg.getSharedUpdatedCount() > 0) { - JsonElement el = JsonConverter.toJson(msg); - el.getAsJsonObject().entrySet().forEach(de -> { - String path = this.getPathAttributeUpdate(sessionInfo, de.getKey()); - String value = de.getValue().getAsString(); - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue(); - AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - ResourceModel resourceModel = context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(path)); - if (!path.isEmpty() && (this.validatePathInAttrProfile(profile, path) || this.validatePathInTelemetryProfile(profile, path))) { - if (resourceModel != null && resourceModel.operations.isWritable()) { - lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, - ContentFormat.TLV.getName(), lwM2MClient, null, value, this.context.getCtxServer().getTimeout(), - false); - } else { - log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", path, value); - String logMsg = String.format(LOG_LW2M_ERROR + ": attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", path, value); - this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); - } - } else { - log.error("Attribute name - [{}] value - [{}] is not present as attribute in profile and cannot be updated", de.getKey(), value); - String logMsg = String.format(LOG_LW2M_ERROR + ": attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated", de.getKey(), value); - this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); - } - }); - } else if (msg.getSharedDeletedCount() > 0) { - log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); - } - } - - /** - * Get path to resource from profile equal keyName or from ModelObject equal name - * Only for resource: isWritable && isPresent as attribute in profile -> AttrTelemetryObserveValue (format: CamelCase) - * - * @param sessionInfo - - * @param name - - * @return path if path isPresent in postProfile - */ - private String getPathAttributeUpdate(TransportProtos.SessionInfoProto sessionInfo, String name) { - String profilePath = this.getPathAttributeUpdateProfile(sessionInfo, name); -// return !profilePath.isEmpty() ? profilePath : this.getPathAttributeUpdateModelObject(name); - return !profilePath.isEmpty() ? profilePath : null; - } - - /** - * @param profile - - * @param path - - * @return true if path isPresent in postAttributeProfile - */ - private boolean validatePathInAttrProfile(AttrTelemetryObserveValue profile, String path) { - Set attributesSet = new Gson().fromJson(profile.getPostAttributeProfile(), Set.class); - return attributesSet.stream().filter(p -> p.equals(path)).findFirst().isPresent(); - } - - /** - * @param profile - - * @param path - - * @return true if path isPresent in postAttributeProfile - */ - private boolean validatePathInTelemetryProfile(AttrTelemetryObserveValue profile, String path) { - Set telemetriesSet = new Gson().fromJson(profile.getPostTelemetryProfile(), Set.class); - return telemetriesSet.stream().filter(p -> p.equals(path)).findFirst().isPresent(); - } - - - /** - * Get path to resource from profile equal keyName - * - * @param sessionInfo - - * @param name - - * @return - - */ - private String getPathAttributeUpdateProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { - AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() - .filter(e -> e.getValue().getAsString().equals(name)).findFirst().map(Map.Entry::getKey) - .orElse(""); - } - - /** - * Update resource (attribute) value on thingsboard after update value in client - * - * @param registration - - * @param path - - * @param request - - */ - public void onAttributeUpdateOk(Registration registration, String path, WriteRequest request, boolean isDelayedUpdate) { - ResourceModel resource = context.getCtxServer().getResourceModel(registration, new LwM2mPath(path)); - if (resource.multiple) { - this.onObservationSetResourcesValue(registration, null, ((LwM2mSingleResource) request.getNode()).getValues(), path); - } else { - this.onObservationSetResourcesValue(registration, ((LwM2mSingleResource) request.getNode()).getValue(), null, path); - } - if (isDelayedUpdate) lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null) - .onSuccessOrErrorDelayedRequests(request.getPath().toString()); - } - - /** - * @param sessionInfo - - * @param deviceProfile - - */ - public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { - Set registrationIds = lwM2mInMemorySecurityStore.getSessions().entrySet() - .stream() - .filter(e -> e.getValue().getProfileUuid().equals(deviceProfile.getUuidId())) - .map(Map.Entry::getKey).sorted().collect(Collectors.toCollection(LinkedHashSet::new)); - if (registrationIds.size() > 0) { - this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile); - } - } - - /** - * @param sessionInfo - - * @param device - - * @param deviceProfileOpt - - */ - public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { - Optional registrationIdOpt = lwM2mInMemorySecurityStore.getSessions().entrySet().stream() - .filter(e -> device.getUuidId().equals(e.getValue().getDeviceUuid())) - .map(Map.Entry::getKey) - .findFirst(); - registrationIdOpt.ifPresent(registrationId -> this.onDeviceUpdateLwM2MClient(registrationId, device, deviceProfileOpt)); - } - - /** - * Update parameters device in LwM2MClient - * If new deviceProfile != old deviceProfile => update deviceProfile - * - * @param registrationId - - * @param device - - */ - private void onDeviceUpdateLwM2MClient(String registrationId, Device device, Optional deviceProfileOpt) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registrationId); - lwM2MClient.setDeviceName(device.getName()); - if (!lwM2MClient.getProfileUuid().equals(device.getDeviceProfileId().getId())) { - Set registrationIds = new HashSet<>(); - registrationIds.add(registrationId); - deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile)); - } - - lwM2MClient.setProfileUuid(device.getDeviceProfileId().getId()); - } - - /** - * #1 Read new, old Value (Attribute, Telemetry, Observe, KeyName) - * #2 Update in lwM2MClient: ...Profile if changes from update device - * #3 Equivalence test: old <> new Value (Attribute, Telemetry, Observe, KeyName) - * #3.1 Attribute isChange (add&del) - * #3.2 Telemetry isChange (add&del) - * #3.3 KeyName isChange (add) - * #4 update - * #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and sent Value to thingsboard - * #4.2 del - * -- if add attributes includes del telemetry - result del for observe - * #5 - * #5.1 Observe isChange (add&del) - * #5.2 Observe.add - * -- path Attr/Telemetry includes newObserve and does not include oldObserve: sent Request observe to Client - * #5.3 Observe.del - * -- different between newObserve and oldObserve: sent Request cancel observe to client - * - * @param registrationIds - - * @param deviceProfile - - */ - public void onDeviceUpdateChangeProfile(Set registrationIds, DeviceProfile deviceProfile) { - - AttrTelemetryObserveValue attrTelemetryObserveValueOld = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); - if (lwM2mInMemorySecurityStore.addUpdateProfileParameters(deviceProfile)) { - - // #1 - JsonArray attributeOld = attrTelemetryObserveValueOld.getPostAttributeProfile(); - Set attributeSetOld = new Gson().fromJson(attributeOld, Set.class); - JsonArray telemetryOld = attrTelemetryObserveValueOld.getPostTelemetryProfile(); - Set telemetrySetOld = new Gson().fromJson(telemetryOld, Set.class); - JsonArray observeOld = attrTelemetryObserveValueOld.getPostObserveProfile(); - JsonObject keyNameOld = attrTelemetryObserveValueOld.getPostKeyNameProfile(); - - AttrTelemetryObserveValue attrTelemetryObserveValueNew = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); - JsonArray attributeNew = attrTelemetryObserveValueNew.getPostAttributeProfile(); - Set attributeSetNew = new Gson().fromJson(attributeNew, Set.class); - JsonArray telemetryNew = attrTelemetryObserveValueNew.getPostTelemetryProfile(); - Set telemetrySetNew = new Gson().fromJson(telemetryNew, Set.class); - JsonArray observeNew = attrTelemetryObserveValueNew.getPostObserveProfile(); - JsonObject keyNameNew = attrTelemetryObserveValueNew.getPostKeyNameProfile(); - - // #3 - ResultsAnalyzerParameters sentAttrToThingsboard = new ResultsAnalyzerParameters(); - // #3.1 - if (!attributeOld.equals(attributeNew)) { - ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, Set.class), attributeSetNew); - sentAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); - sentAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); - } - // #3.2 - if (!attributeOld.equals(attributeNew)) { - ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, Set.class), telemetrySetNew); - sentAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); - sentAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); - } - // #3.3 - if (!keyNameOld.equals(keyNameNew)) { - ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), ConcurrentHashMap.class), - new Gson().fromJson(keyNameNew.toString(), ConcurrentHashMap.class)); - sentAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); - } - - // #4.1 add - if (sentAttrToThingsboard.getPathPostParametersAdd().size() > 0) { - // update value in Resources - registrationIds.forEach(registrationId -> { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(null, registrationId); - LeshanServer lwServer = lwM2MClient.getLwServer(); - Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId); - log.warn("[{}] # 4.1", registration.getEndpoint()); - this.updateResourceValueObserve(lwServer, registration, sentAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ); - // sent attr/telemetry to tingsboard for new path - this.updateAttrTelemetry(registration, false, sentAttrToThingsboard.getPathPostParametersAdd()); - }); - } - // #4.2 del - if (sentAttrToThingsboard.getPathPostParametersDel().size() > 0) { - ResultsAnalyzerParameters sentAttrToThingsboardDel = this.getAnalyzerParameters(sentAttrToThingsboard.getPathPostParametersAdd(), sentAttrToThingsboard.getPathPostParametersDel()); - sentAttrToThingsboard.setPathPostParametersDel(sentAttrToThingsboardDel.getPathPostParametersDel()); - } - - // #5.1 - if (!observeOld.equals(observeNew)) { - Set observeSetOld = new Gson().fromJson(observeOld, Set.class); - Set observeSetNew = new Gson().fromJson(observeNew, Set.class); - //#5.2 add - // path Attr/Telemetry includes newObserve - attributeSetOld.addAll(telemetrySetOld); - ResultsAnalyzerParameters sentObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeSetOld); // add observe - attributeSetNew.addAll(telemetrySetNew); - ResultsAnalyzerParameters sentObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeSetNew); // add observe - // does not include oldObserve - ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sentObserveToClientOld.getPathPostParametersAdd(), sentObserveToClientNew.getPathPostParametersAdd()); - // sent Request observe to Client - registrationIds.forEach(registrationId -> { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClient(null, registrationId); - LeshanServer lwServer = lwM2MClient.getLwServer(); - Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId); - log.warn("[{}] # 5.1", registration.getEndpoint()); - this.updateResourceValueObserve(lwServer, registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE); - // 5.3 del - // sent Request cancel observe to Client - this.cancelObserveIsValue(lwServer, registration, postObserveAnalyzer.getPathPostParametersDel()); - }); - } - } - } - - /** - * Compare old list with new list after change AttrTelemetryObserve in config Profile - * - * @param parametersOld - - * @param parametersNew - - * @return ResultsAnalyzerParameters: add && new - */ - private ResultsAnalyzerParameters getAnalyzerParameters(Set parametersOld, Set parametersNew) { - ResultsAnalyzerParameters analyzerParameters = null; - if (!parametersOld.equals(parametersNew)) { - analyzerParameters = new ResultsAnalyzerParameters(); - analyzerParameters.setPathPostParametersAdd(parametersNew - .stream().filter(p -> !parametersOld.contains(p)).collect(Collectors.toSet())); - analyzerParameters.setPathPostParametersDel(parametersOld - .stream().filter(p -> !parametersNew.contains(p)).collect(Collectors.toSet())); - } - return analyzerParameters; - } - - private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap keyNameOld, ConcurrentMap keyNameNew) { - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); - Set paths = keyNameNew.entrySet() - .stream() - .filter(e -> !e.getValue().equals(keyNameOld.get(e.getKey()))) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).keySet(); - analyzerParameters.setPathPostParametersAdd(paths); - return analyzerParameters; - } - - private ResultsAnalyzerParameters getAnalyzerParametersIn(Set parametersObserve, Set parameters) { - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); - analyzerParameters.setPathPostParametersAdd(parametersObserve - .stream().filter(parameters::contains).collect(Collectors.toSet())); - return analyzerParameters; - } - - /** - * Update Resource value after change RezAttrTelemetry in config Profile - * sent response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue() - * - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] - */ - private void updateResourceValueObserve(LeshanServer lwServer, Registration registration, Set targets, String typeOper) { - targets.forEach(target -> { - LwM2mPath pathIds = new LwM2mPath(target); - if (pathIds.isResource()) { - if (GET_TYPE_OPER_READ.equals(typeOper)) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, - ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout(), - false); - } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, - null, null, null, null, this.context.getCtxServer().getTimeout(), - false); - } - } - }); - } - - private void cancelObserveIsValue(LeshanServer lwServer, Registration registration, Set paramAnallyzer) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); - paramAnallyzer.forEach(p -> { - if (this.getResourceValue(lwM2MClient, new LwM2mPath(p)) != null) { - this.setCancelObservationRecourse(lwServer, registration, p); - } - } - ); - } - - private ResourceValue getResourceValue(LwM2MClient lwM2MClient, LwM2mPath pathIds) { - ResourceValue resourceValue = null; - if (pathIds.isResource()) { - resourceValue = lwM2MClient.getResources().get(pathIds.toString()); - } - return resourceValue; - } - - /** - * Trigger Server path = "/1/0/8" - * - * Trigger bootStrap path = "/1/0/9" - have to implemented on client - */ - public void doTrigger(LeshanServer lwServer, Registration registration, String path) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_EXECUTE, - ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout(), - false); - } - - /** - * Session device in thingsboard is closed - * - * @param sessionInfo - lwm2m client - */ - private void doCloseSession(SessionInfoProto sessionInfo) { - TransportProtos.SessionEvent event = SessionEvent.CLOSED; - TransportProtos.SessionEventMsg msg = TransportProtos.SessionEventMsg.newBuilder() - .setSessionType(TransportProtos.SessionType.ASYNC) - .setEvent(event).build(); - transportService.process(sessionInfo, msg, null); - } - - /** - * Deregister session in transport - * - * @param sessionInfo - lwm2m client - */ - private void doDisconnect(SessionInfoProto sessionInfo) { - transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); - transportService.deregisterSession(sessionInfo); - } - - private void checkInactivityAndReportActivity() { - lwM2mInMemorySecurityStore.getSessions().forEach((key, value) -> this.checkInactivity(this.getValidateSessionInfo(key))); - } - - /** - * if sessionInfo removed from sessions, then new registerAsyncSession - * @param sessionInfo - - */ - private void checkInactivity(SessionInfoProto sessionInfo) { - if (transportService.reportActivity(sessionInfo) == null) { - transportService.registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(this, sessionInfo)); - } - } - - public void sentLogsToThingsboard(String msg, Registration registration) { - if (msg != null) { - JsonObject telemetries = new JsonObject(); - telemetries.addProperty(LOG_LW2M_TELEMETRY, msg); - this.updateParametersOnThingsboard(telemetries, LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, registration); - } - } - - /** - * @param path - path resource - * @return - value of Resource or null - */ - public String getResourceValueToString(LwM2MClient lwM2MClient, String path) { - LwM2mPath pathIds = new LwM2mPath(path); - ResourceValue resourceValue = this.getResourceValue(lwM2MClient, pathIds); - return (resourceValue == null) ? null : - (String) this.converter.convertValue(resourceValue.getResourceValue(), this.context.getCtxServer().getResourceModelType(lwM2MClient.getRegistration(), pathIds), ResourceModel.Type.STRING, pathIds); - } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java new file mode 100644 index 0000000000..2a6af4e19e --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java @@ -0,0 +1,1132 @@ +/** + * Copyright © 2016-2020 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; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; +import org.eclipse.leshan.core.node.LwM2mObject; +import org.eclipse.leshan.core.node.LwM2mObjectInstance; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.util.NamedThreadFactory; +import org.eclipse.leshan.server.californium.LeshanServer; +import org.eclipse.leshan.server.registration.Registration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.transport.service.DefaultTransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; +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.ResultsAnalyzerParameters; +import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore; +import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +import static org.thingsboard.server.common.transport.util.JsonUtils.getJsonObject; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.CLIENT_NOT_AUTHORIZED; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEFAULT_TIMEOUT; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEVICE_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEVICE_ATTRIBUTES_TOPIC; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_OBSERVE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_READ; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_TELEMETRY; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_EXECUTE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_WRITE_REPLACE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.SERVICE_CHANNEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getAckCallback; + +@Slf4j +@Service("LwM2MTransportService") +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") +public class LwM2MTransportServiceImpl implements LwM2MTransportService { + + private ExecutorService executorRegistered; + private ExecutorService executorUpdateRegistered; + private ExecutorService executorUnRegistered; + private LwM2mValueConverterImpl converter; + protected final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + protected final Lock writeLock = readWriteLock.writeLock(); + + + @Autowired + private TransportService transportService; + + @Autowired + public LwM2MTransportContextServer context; + + @Autowired + private LwM2MTransportRequest lwM2MTransportRequest; + + @Autowired + LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; + + @PostConstruct + public void init() { + this.context.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) context.getCtxServer().getSessionReportTimeout()), context.getCtxServer().getSessionReportTimeout(), TimeUnit.MILLISECONDS); + this.executorRegistered = Executors.newCachedThreadPool( + new NamedThreadFactory(String.format("LwM2M %s channel registered", SERVICE_CHANNEL))); + this.executorUpdateRegistered = Executors.newCachedThreadPool( + new NamedThreadFactory(String.format("LwM2M %s channel update registered", SERVICE_CHANNEL))); + this.executorUnRegistered = Executors.newCachedThreadPool( + new NamedThreadFactory(String.format("LwM2M %s channel un registered", SERVICE_CHANNEL))); + this.converter = LwM2mValueConverterImpl.getInstance(); + } + + /** + * Start registration device + * Create session: Map, LwM2MClient> + * 1. replaceNewRegistration -> (solving the problem of incorrect termination of the previous session with this endpoint) + * 1.1 When we initialize the registration, we register the session by endpoint. + * 1.2 If the server has incomplete requests (canceling the registration of the previous session), + * delete the previous session only by the previous registration.getId + * 1.2 Add Model (Entity) for client (from registration & observe) by registration.getId + * 1.2 Remove from sessions Model by enpPoint + * Next -> Create new LwM2MClient for current session -> setModelClient... + * + * @param lwServer - LeshanServer + * @param registration - Registration LwM2M Client + * @param previousObsersations - may be null + */ + public void onRegistered(LeshanServer lwServer, Registration registration, Collection previousObsersations) { + executorRegistered.submit(() -> { + try { + log.info("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.updateInSessionsLwM2MClient(lwServer, registration); + if (lwM2MClient != null) { + lwM2MClient.setLwM2MTransportServiceImpl(this); + lwM2MClient.setSessionUuid(UUID.randomUUID()); + this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client Registered", registration); + this.setLwM2MClient(lwServer, registration, lwM2MClient); + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + if (sessionInfo != null) { + lwM2MClient.setDeviceUuid(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); + lwM2MClient.setProfileUuid(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); + lwM2MClient.setDeviceName(sessionInfo.getDeviceName()); + lwM2MClient.setDeviceProfileName(sessionInfo.getDeviceType()); + transportService.registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(this, sessionInfo)); + transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null); + transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); + this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration); + } else { + log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); + } + } else { + log.error("Client: [{}] onRegistered [{}] name [{}] lwM2MClient ", registration.getId(), registration.getEndpoint(), null); + } + } catch (Throwable t) { + log.error("[{}] endpoint [{}] error Unable registration.", registration.getEndpoint(), t); + } + }); + } + + /** + * if sessionInfo removed from sessions, then new registerAsyncSession + * @param lwServer - LeshanServer + * @param registration - Registration LwM2M Client + */ + public void updatedReg(LeshanServer lwServer, Registration registration) { + executorUpdateRegistered.submit(() -> { + try { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + if (sessionInfo != null) { + this.checkInactivity(sessionInfo); + log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); + } else { + log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); + } + } catch (Throwable t) { + log.error("[{}] endpoint [{}] error Unable update registration.", registration.getEndpoint(), t); + } + }); + } + + + /** + * @param registration - Registration LwM2M Client + * @param observations - All paths observations before unReg + * !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect + */ + public void unReg(Registration registration, Collection observations) { + executorUnRegistered.submit(() -> { + try { + this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration); + this.closeClientSession(registration); + } catch (Throwable t) { + log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t); + } + }); + } + + private void closeClientSession(Registration registration) { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + if (sessionInfo != null) { + transportService.deregisterSession(sessionInfo); + this.doCloseSession(sessionInfo); + lwM2mInMemorySecurityStore.delRemoveSessionAndListener(registration.getId()); + if (lwM2mInMemorySecurityStore.getProfiles().size() > 0) { + this.syncSessionsAndProfiles(); + } + log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); + } else { + log.error("Client close session: [{}] unReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); + } + } + + public void onSleepingDev(Registration registration) { + log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); + //TODO: associate endpointId with device information. + } + + /** + * Those methods are called by the protocol stage thread pool, this means that execution MUST be done in a short delay, + * * if you need to do long time processing use a dedicated thread pool. + * + * @param registration - + */ + protected void onAwakeDev(Registration registration) { + log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); + //TODO: associate endpointId with device information. + } + + /** + * This method is used to sync with sessions + * Removes a profile if not used in sessions + */ + private void syncSessionsAndProfiles() { + Map profilesClone = lwM2mInMemorySecurityStore.getProfiles().entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + profilesClone.forEach((k, v) -> { + String registrationId = lwM2mInMemorySecurityStore.getSessions().entrySet() + .stream() + .filter(e -> e.getValue().getProfileUuid().equals(k)) + .findFirst() + .map(Map.Entry::getKey) // return the key of the matching entry if found + .orElse(""); + if (registrationId.isEmpty()) { + lwM2mInMemorySecurityStore.getProfiles().remove(k); + } + }); + } + + /** + * #0 Add new ObjectModel to context + * Create new LwM2MClient for current session -> setModelClient... + * #1 Add all ObjectLinks (instance) to control the process of executing requests to the client + * to get the client model with current values + * #2 Get the client model with current values. Analyze the response in -> lwM2MTransportRequest.sendResponse + * + * @param lwServer - LeshanServer + * @param registration - Registration LwM2M Client + * @param lwM2MClient - object with All parameters off client + */ + private void setLwM2MClient(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient) { + Arrays.stream(registration.getObjectLinks()).forEach(url -> { + LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + if (pathIds.isObjectInstance() && !pathIds.isResource()) { + lwM2MClient.getPendingRequests().add(url.getUrl()); + } + }); + // #2 + Arrays.stream(registration.getObjectLinks()).forEach(url -> { + LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + if (pathIds.isObjectInstance() && !pathIds.isResource()) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), + lwM2MClient, null, null, this.context.getCtxServer().getTimeout(), false); + } + }); + } + + /** + * @param registration - Registration LwM2M Client + * @return - sessionInfo after access connect client + */ + private SessionInfoProto getValidateSessionInfo(Registration registration) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); + return getNewSessionInfoProto(lwM2MClient); + + } + + /** + * + * @param registrationId - + * @return - + */ + private SessionInfoProto getValidateSessionInfo(String registrationId) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(null, registrationId); + return getNewSessionInfoProto(lwM2MClient); + } + + private SessionInfoProto getNewSessionInfoProto(LwM2MClient lwM2MClient) { + if (lwM2MClient != null) { + ValidateDeviceCredentialsResponseMsg msg = lwM2MClient.getCredentialsResponse(); + if (msg == null || msg.getDeviceInfo() == null) { + log.error("[{}] [{}]", lwM2MClient.getEndPoint(), CLIENT_NOT_AUTHORIZED); + this.closeClientSession(lwM2MClient.getRegistration()); + return null; + } else { + return SessionInfoProto.newBuilder() + .setNodeId(this.context.getNodeId()) + .setSessionIdMSB(lwM2MClient.getSessionUuid().getMostSignificantBits()) + .setSessionIdLSB(lwM2MClient.getSessionUuid().getLeastSignificantBits()) + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) + .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) + .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) + .setDeviceName(msg.getDeviceInfo().getDeviceName()) + .setDeviceType(msg.getDeviceInfo().getDeviceType()) + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) + .build(); + } + } + return null; + } + + /** + * Add attribute/telemetry information from Client and credentials/Profile to client model and start observe + * !!! if the resource has an observation, but no telemetry or attribute - the observation will not use + * #1 Sending Attribute Telemetry with value to thingsboard only once at the start of the connection + * #2 Start observe + * + * @param lwM2MClient - LwM2M Client + */ + + public void updatesAndSentModelParameter(LwM2MClient lwM2MClient) { + // #1 + this.updateAttrTelemetry(lwM2MClient.getRegistration(), true, null); + // #2 + this.onSentObserveToClient(lwM2MClient.getLwServer(), lwM2MClient.getRegistration()); + + } + + /** + * If there is a difference in values between the current resource values and the shared attribute values + * when the client connects to the server + * #1 get attributes name from profile include name resources in ModelObject if resource isWritable + * #2.1 #1 size > 0 => send Request getAttributes to thingsboard + * #2.2 #1 size == 0 => continue normal process + * + * @param lwM2MClient - LwM2M Client + */ + public void putDelayedUpdateResourcesThingsboard(LwM2MClient lwM2MClient) { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration()); + if (sessionInfo != null) { + //#1.1 + #1.2 + List attrSharedNames = this.getNamesAttrFromProfileIsWritable(lwM2MClient); + if (attrSharedNames.size() > 0) { + //#2.1 + try { + TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getAdaptor().convertToGetAttributes(null, attrSharedNames); + lwM2MClient.getDelayedRequestsId().add(getAttributeMsg.getRequestId()); + transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); + } catch (AdaptorException e) { + log.warn("Failed to decode get attributes request", e); + } + } + // #2.2 + else { + lwM2MClient.onSuccessOrErrorDelayedRequests(null); + } + } + } + + /** + * Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values + * #1 Get path resource by result attributesResponse + * #1.1 If two names have equal path => last time attribute + * #2.1 if there is a difference in values between the current resource values and the shared attribute values + * => sent to client Request Update of value (new value from shared attribute) + * and LwM2MClient.delayedRequests.add(path) + * #2.1 if there is not a difference in values between the current resource values and the shared attribute values + * + * @param attributesResponse - + * @param sessionInfo - + */ + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClient(sessionInfo); + if (lwM2MClient.getDelayedRequestsId().contains(attributesResponse.getRequestId())) { + attributesResponse.getSharedAttributeListList().forEach(attr -> { + String path = this.getPathAttributeUpdate(sessionInfo, attr.getKv().getKey()); + // #1.1 + if (lwM2MClient.getDelayedRequests().containsKey(path) && attr.getTs() > lwM2MClient.getDelayedRequests().get(path).getTs()) { + lwM2MClient.getDelayedRequests().put(path, attr); + } else { + lwM2MClient.getDelayedRequests().put(path, attr); + } + }); + // #2.1 + lwM2MClient.getDelayedRequests().forEach((k, v) -> { + ArrayList listV = new ArrayList<>(); + listV.add(v.getKv()); + this.putDelayedUpdateResourcesClient(lwM2MClient, this.getResourceValueToString(lwM2MClient, k), getJsonObject(listV).get(v.getKv().getKey()), k); + }); + lwM2MClient.getDelayedRequestsId().remove(attributesResponse.getRequestId()); + if (lwM2MClient.getDelayedRequests().size() == 0) { + lwM2MClient.onSuccessOrErrorDelayedRequests(null); + } + } + } + + private void putDelayedUpdateResourcesClient(LwM2MClient lwM2MClient, Object valueOld, Object valueNew, String path) { + if (valueNew != null && !valueNew.toString().equals(valueOld.toString())) { + lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, + ContentFormat.TLV.getName(), lwM2MClient, null, valueNew, this.context.getCtxServer().getTimeout(), + true); + } + } + + /** + * Get names and keyNames from profile shared!!!! attr resources IsWritable + * @param lwM2MClient - + * @return ArrayList keyNames from profile attr resources shared!!!! && IsWritable + */ + private List getNamesAttrFromProfileIsWritable(LwM2MClient lwM2MClient) { + AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(lwM2MClient.getProfileUuid()); + Set attrSet = new Gson().fromJson(profile.getPostAttributeProfile(), Set.class); + ConcurrentMap keyNamesMap = new Gson().fromJson(profile.getPostKeyNameProfile().toString(), ConcurrentHashMap.class); + + ConcurrentMap keyNamesIsWritable = keyNamesMap.entrySet() + .stream() + .filter(e -> (attrSet.contains(e.getKey()) && context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())) != null && + context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())).operations.isWritable())) + .collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)); + + Set namesIsWritable = ConcurrentHashMap.newKeySet(); + namesIsWritable.addAll(new HashSet<>(keyNamesIsWritable.values())); + return new ArrayList<>(namesIsWritable); + } + + + /** + * Sent Attribute and Telemetry to Thingsboard + * #1 - get AttrName/TelemetryName with value: + * #1.1 from Client + * #1.2 from LwM2MClient: + * -- resourceId == path from AttrTelemetryObserveValue.postAttributeProfile/postTelemetryProfile/postObserveProfile + * -- AttrName/TelemetryName == resourceName from ModelObject.objectModel, value from ModelObject.instance.resource(resourceId) + * #2 - set Attribute/Telemetry + * + * @param registration - Registration LwM2M Client + */ + private void updateAttrTelemetry(Registration registration, boolean start, Set paths) { + JsonObject attributes = new JsonObject(); + JsonObject telemetries = new JsonObject(); + if (start) { + // #1.1 + JsonObject attributeClient = this.getAttributeClient(registration); + if (attributeClient != null) { + attributeClient.entrySet().forEach(p -> attributes.add(p.getKey(), p.getValue())); + } + } + // #1.2 + CountDownLatch cancelLatch = new CountDownLatch(1); + this.getParametersFromProfile(attributes, telemetries, registration, paths); + cancelLatch.countDown(); + try { + cancelLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + log.error("[{}] updateAttrTelemetry", e.toString()); + } + if (attributes.getAsJsonObject().entrySet().size() > 0) + this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration); + if (telemetries.getAsJsonObject().entrySet().size() > 0) + this.updateParametersOnThingsboard(telemetries, DEVICE_TELEMETRY_TOPIC, registration); + } + + /** + * get AttrName/TelemetryName with value from Client + * + * @param registration - + * @return - JsonObject, format: {name: value}} + */ + private JsonObject getAttributeClient(Registration registration) { + if (registration.getAdditionalRegistrationAttributes().size() > 0) { + JsonObject resNameValues = new JsonObject(); + registration.getAdditionalRegistrationAttributes().forEach(resNameValues::addProperty); + return resNameValues; + } + return null; + } + + /** + * @param attributes - new JsonObject + * @param telemetry - new JsonObject + * @param registration - Registration LwM2M Client + * result: add to JsonObject those resources to which the user is subscribed and they have a value + * if path==null add All resources else only one + * (attributes/telemetry): new {name(Attr/Telemetry):value} + */ + private void getParametersFromProfile(JsonObject attributes, JsonObject telemetry, Registration registration, Set path) { + AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid()); + attrTelemetryObserveValue.getPostAttributeProfile().forEach(p -> { + LwM2mPath pathIds = new LwM2mPath(p.getAsString().toString()); + if (pathIds.isResource()) { + if (path == null || path.contains(p.getAsString())) { + this.addParameters(p.getAsString().toString(), attributes, registration); + } + } + }); + attrTelemetryObserveValue.getPostTelemetryProfile().forEach(p -> { + LwM2mPath pathIds = new LwM2mPath(p.getAsString().toString()); + if (pathIds.isResource()) { + if (path == null || path.contains(p.getAsString())) { + this.addParameters(p.getAsString().toString(), telemetry, registration); + } + } + }); + } + + /** + * @param parameters - JsonObject attributes/telemetry + * @param registration - Registration LwM2M Client + */ + private void addParameters(String path, JsonObject parameters, Registration registration) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()); + JsonObject names = lwM2mInMemorySecurityStore.getProfiles().get(lwM2MClient.getProfileUuid()).getPostKeyNameProfile(); + String resName = String.valueOf(names.get(path)); + if (resName != null && !resName.isEmpty()) { + try { + String resValue = this.getResourceValueToString(lwM2MClient, path); + if (resValue != null) { + parameters.addProperty(resName, resValue); + } + } catch (Exception e) { + log.error(e.getStackTrace().toString()); + } + } + } + + /** + * Prepare Sent to Thigsboard callback - Attribute or Telemetry + * + * @param msg - JsonArray: [{name: value}] + * @param topicName - Api Attribute or Telemetry + * @param registration - Id of Registration LwM2M Client + */ + public void updateParametersOnThingsboard(JsonElement msg, String topicName, Registration registration) { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + if (sessionInfo != null) { + context.sentParametersOnThingsboard(msg, topicName, sessionInfo); + } else { + log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registration, null); + } + } + + /** + * Start observe + * #1 - Analyze: + * #1.1 path in observe == (attribute or telemetry) + * #2 Analyze after sent request (response): + * #2.1 First: lwM2MTransportRequest.sendResponse -> ObservationListener.newObservation + * #2.2 Next: ObservationListener.onResponse * + * + * @param lwServer - LeshanServer + * @param registration - Registration LwM2M Client + */ + private void onSentObserveToClient(LeshanServer lwServer, Registration registration) { + if (lwServer.getObservationService().getObservations(registration).size() > 0) { + this.setCancelObservations(lwServer, registration); + } + UUID profileUUid = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid(); + AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(profileUUid); + attrTelemetryObserveValue.getPostObserveProfile().forEach(p -> { + // #1.1 + String target = (getValidateObserve(attrTelemetryObserveValue.getPostAttributeProfile(), p.getAsString().toString())) ? + p.getAsString().toString() : (getValidateObserve(attrTelemetryObserveValue.getPostTelemetryProfile(), p.getAsString().toString())) ? + p.getAsString().toString() : null; + if (target != null) { + // #2 + if (this.getResourceValueToString(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()), target) != null) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, GET_TYPE_OPER_OBSERVE, + null, null, null, null, this.context.getCtxServer().getTimeout(), + false); + } + } + }); + } + + public void setCancelObservations(LeshanServer lwServer, Registration registration) { + if (registration != null) { + Set observations = lwServer.getObservationService().getObservations(registration); + observations.forEach(observation -> this.setCancelObservationRecourse(lwServer, registration, observation.getPath().toString())); + } + } + + /** + * lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); + * At server side this will not remove the observation from the observation store, to do it you need to use + * {@code ObservationService#cancelObservation()} + */ + public void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path) { + CountDownLatch cancelLatch = new CountDownLatch(1); + lwServer.getObservationService().cancelObservations(registration, path); + cancelLatch.countDown(); + try { + cancelLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + log.error("", e); + } + } + + /** + * @param parameters - JsonArray postAttributeProfile/postTelemetryProfile + * @param path - recourse from postObserveProfile + * @return rez - true if path observe is in attribute/telemetry + */ + private boolean getValidateObserve(JsonElement parameters, String path) { + AtomicBoolean rez = new AtomicBoolean(false); + if (parameters.isJsonArray()) { + parameters.getAsJsonArray().forEach(p -> { + if (p.getAsString().toString().equals(path)) rez.set(true); + } + ); + } else if (parameters.isJsonObject()) { + rez.set((parameters.getAsJsonObject().entrySet()).stream().map(json -> json.toString()) + .filter(path::equals).findAny().orElse(null) != null); + } + return rez.get(); + } + + /** + * Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource + * + * @param registration - Registration LwM2M Client + * @param path - observe + * @param response - observe + */ + + public void onObservationResponse(Registration registration, String path, ReadResponse response) { + if (response.getContent() != null) { + if (response.getContent() instanceof LwM2mObject) { +// LwM2mObject content = (LwM2mObject) response.getContent(); + } else if (response.getContent() instanceof LwM2mObjectInstance) { +// LwM2mObjectInstance content = (LwM2mObjectInstance) response.getContent(); + } else if (response.getContent() instanceof LwM2mSingleResource) { + LwM2mSingleResource content = (LwM2mSingleResource) response.getContent(); + this.onObservationSetResourcesValue(registration, content.getValue(), null, path); + } else if (response.getContent() instanceof LwM2mMultipleResource) { + LwM2mMultipleResource content = (LwM2mMultipleResource) response.getContent(); + this.onObservationSetResourcesValue(registration, null, content.getValues(), path); + } + } + } + + /** + * Sending observe value of resources to thingsboard + * #1 Return old Value Resource from LwM2MClient + * #2 Update new Resources (replace old Resource Value on new Resource Value) + * + * @param registration - Registration LwM2M Client + * @param value - LwM2mSingleResource response.getContent() + * @param values - LwM2mSingleResource response.getContent() + * @param path - resource + */ + private void onObservationSetResourcesValue(Registration registration, Object value, Map values, String path) { + boolean isChange = false; + try { + writeLock.lock(); + try { + // #1 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); + LwM2mPath pathIds = new LwM2mPath(path); + log.warn("#0 nameDevice: [{}] resultIds: [{}] value: [{}], values: [{}] ", lwM2MClient.getDeviceName(), pathIds, value, values); + ResourceModel.Type resModelType = context.getCtxServer().getResourceModelType(registration, pathIds); + ResourceValue resValueOld = lwM2MClient.getResources().get(path); + // #2 + if (resValueOld.isMultiInstances() && !values.toString().equals(resValueOld.getResourceValue().toString())) { + ResourceValue resourceValue = new ResourceValue(values, null, true); + lwM2MClient.getResources().put(path, resourceValue); + isChange = true; + } else if (!LwM2MTransportHandler.equalsResourceValue(resValueOld.getValue(), value, resModelType, pathIds)) { + ResourceValue resourceValue = new ResourceValue(null, value, false); + lwM2MClient.getResources().put(path, resourceValue); + isChange = true; + } + } finally { + writeLock.unlock(); + } + } + catch (Exception e) { + log.error("#1_1 Update ResourcesValue after Observation in CountDownLatch is unsuccessfully path: [{}] value: [{}] [{}]", path, value, e.toString()); + } + if (isChange) { + Set paths = new HashSet<>(); + paths.add(path); + this.updateAttrTelemetry(registration, false, paths); + } + } + + /** + * @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...) + * config attr/telemetry... in profile + */ + public void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto updateCredentials) { + log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList()); + } + + /** + * Update - sent request in change value resources in Client + * Path to resources from profile equal keyName or from ModelObject equal name + * Only for resources: isWritable && isPresent as attribute in profile -> AttrTelemetryObserveValue (format: CamelCase) + * Delete - nothing * + * + * @param msg - + */ + public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { + if (msg.getSharedUpdatedCount() > 0) { + JsonElement el = JsonConverter.toJson(msg); + el.getAsJsonObject().entrySet().forEach(de -> { + String path = this.getPathAttributeUpdate(sessionInfo, de.getKey()); + String value = de.getValue().getAsString(); + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue(); + AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); + ResourceModel resourceModel = context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(path)); + if (!path.isEmpty() && (this.validatePathInAttrProfile(profile, path) || this.validatePathInTelemetryProfile(profile, path))) { + if (resourceModel != null && resourceModel.operations.isWritable()) { + lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, + ContentFormat.TLV.getName(), lwM2MClient, null, value, this.context.getCtxServer().getTimeout(), + false); + } else { + log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", path, value); + String logMsg = String.format(LOG_LW2M_ERROR + ": attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", path, value); + this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); + } + } else { + log.error("Attribute name - [{}] value - [{}] is not present as attribute in profile and cannot be updated", de.getKey(), value); + String logMsg = String.format(LOG_LW2M_ERROR + ": attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated", de.getKey(), value); + this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); + } + }); + } else if (msg.getSharedDeletedCount() > 0) { + log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); + } + } + + /** + * Get path to resource from profile equal keyName or from ModelObject equal name + * Only for resource: isWritable && isPresent as attribute in profile -> AttrTelemetryObserveValue (format: CamelCase) + * + * @param sessionInfo - + * @param name - + * @return path if path isPresent in postProfile + */ + private String getPathAttributeUpdate(TransportProtos.SessionInfoProto sessionInfo, String name) { + String profilePath = this.getPathAttributeUpdateProfile(sessionInfo, name); +// return !profilePath.isEmpty() ? profilePath : this.getPathAttributeUpdateModelObject(name); + return !profilePath.isEmpty() ? profilePath : null; + } + + /** + * @param profile - + * @param path - + * @return true if path isPresent in postAttributeProfile + */ + private boolean validatePathInAttrProfile(AttrTelemetryObserveValue profile, String path) { + Set attributesSet = new Gson().fromJson(profile.getPostAttributeProfile(), Set.class); + return attributesSet.stream().filter(p -> p.equals(path)).findFirst().isPresent(); + } + + /** + * @param profile - + * @param path - + * @return true if path isPresent in postAttributeProfile + */ + private boolean validatePathInTelemetryProfile(AttrTelemetryObserveValue profile, String path) { + Set telemetriesSet = new Gson().fromJson(profile.getPostTelemetryProfile(), Set.class); + return telemetriesSet.stream().filter(p -> p.equals(path)).findFirst().isPresent(); + } + + + /** + * Get path to resource from profile equal keyName + * + * @param sessionInfo - + * @param name - + * @return - + */ + private String getPathAttributeUpdateProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { + AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); + return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() + .filter(e -> e.getValue().getAsString().equals(name)).findFirst().map(Map.Entry::getKey) + .orElse(""); + } + + /** + * Update resource (attribute) value on thingsboard after update value in client + * + * @param registration - + * @param path - + * @param request - + */ + public void onAttributeUpdateOk(Registration registration, String path, WriteRequest request, boolean isDelayedUpdate) { + ResourceModel resource = context.getCtxServer().getResourceModel(registration, new LwM2mPath(path)); + if (resource.multiple) { + this.onObservationSetResourcesValue(registration, null, ((LwM2mSingleResource) request.getNode()).getValues(), path); + } else { + this.onObservationSetResourcesValue(registration, ((LwM2mSingleResource) request.getNode()).getValue(), null, path); + } + if (isDelayedUpdate) lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null) + .onSuccessOrErrorDelayedRequests(request.getPath().toString()); + } + + /** + * @param sessionInfo - + * @param deviceProfile - + */ + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + Set registrationIds = lwM2mInMemorySecurityStore.getSessions().entrySet() + .stream() + .filter(e -> e.getValue().getProfileUuid().equals(deviceProfile.getUuidId())) + .map(Map.Entry::getKey).sorted().collect(Collectors.toCollection(LinkedHashSet::new)); + if (registrationIds.size() > 0) { + this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile); + } + } + + /** + * @param sessionInfo - + * @param device - + * @param deviceProfileOpt - + */ + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + Optional registrationIdOpt = lwM2mInMemorySecurityStore.getSessions().entrySet().stream() + .filter(e -> device.getUuidId().equals(e.getValue().getDeviceUuid())) + .map(Map.Entry::getKey) + .findFirst(); + registrationIdOpt.ifPresent(registrationId -> this.onDeviceUpdateLwM2MClient(registrationId, device, deviceProfileOpt)); + } + + /** + * Update parameters device in LwM2MClient + * If new deviceProfile != old deviceProfile => update deviceProfile + * + * @param registrationId - + * @param device - + */ + private void onDeviceUpdateLwM2MClient(String registrationId, Device device, Optional deviceProfileOpt) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registrationId); + lwM2MClient.setDeviceName(device.getName()); + if (!lwM2MClient.getProfileUuid().equals(device.getDeviceProfileId().getId())) { + Set registrationIds = new HashSet<>(); + registrationIds.add(registrationId); + deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile)); + } + + lwM2MClient.setProfileUuid(device.getDeviceProfileId().getId()); + } + + /** + * #1 Read new, old Value (Attribute, Telemetry, Observe, KeyName) + * #2 Update in lwM2MClient: ...Profile if changes from update device + * #3 Equivalence test: old <> new Value (Attribute, Telemetry, Observe, KeyName) + * #3.1 Attribute isChange (add&del) + * #3.2 Telemetry isChange (add&del) + * #3.3 KeyName isChange (add) + * #4 update + * #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and sent Value to thingsboard + * #4.2 del + * -- if add attributes includes del telemetry - result del for observe + * #5 + * #5.1 Observe isChange (add&del) + * #5.2 Observe.add + * -- path Attr/Telemetry includes newObserve and does not include oldObserve: sent Request observe to Client + * #5.3 Observe.del + * -- different between newObserve and oldObserve: sent Request cancel observe to client + * + * @param registrationIds - + * @param deviceProfile - + */ + private void onDeviceUpdateChangeProfile(Set registrationIds, DeviceProfile deviceProfile) { + + AttrTelemetryObserveValue attrTelemetryObserveValueOld = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); + if (lwM2mInMemorySecurityStore.addUpdateProfileParameters(deviceProfile)) { + + // #1 + JsonArray attributeOld = attrTelemetryObserveValueOld.getPostAttributeProfile(); + Set attributeSetOld = new Gson().fromJson(attributeOld, Set.class); + JsonArray telemetryOld = attrTelemetryObserveValueOld.getPostTelemetryProfile(); + Set telemetrySetOld = new Gson().fromJson(telemetryOld, Set.class); + JsonArray observeOld = attrTelemetryObserveValueOld.getPostObserveProfile(); + JsonObject keyNameOld = attrTelemetryObserveValueOld.getPostKeyNameProfile(); + + AttrTelemetryObserveValue attrTelemetryObserveValueNew = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); + JsonArray attributeNew = attrTelemetryObserveValueNew.getPostAttributeProfile(); + Set attributeSetNew = new Gson().fromJson(attributeNew, Set.class); + JsonArray telemetryNew = attrTelemetryObserveValueNew.getPostTelemetryProfile(); + Set telemetrySetNew = new Gson().fromJson(telemetryNew, Set.class); + JsonArray observeNew = attrTelemetryObserveValueNew.getPostObserveProfile(); + JsonObject keyNameNew = attrTelemetryObserveValueNew.getPostKeyNameProfile(); + + // #3 + ResultsAnalyzerParameters sentAttrToThingsboard = new ResultsAnalyzerParameters(); + // #3.1 + if (!attributeOld.equals(attributeNew)) { + ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, Set.class), attributeSetNew); + sentAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); + sentAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); + } + // #3.2 + if (!attributeOld.equals(attributeNew)) { + ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, Set.class), telemetrySetNew); + sentAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); + sentAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); + } + // #3.3 + if (!keyNameOld.equals(keyNameNew)) { + ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), ConcurrentHashMap.class), + new Gson().fromJson(keyNameNew.toString(), ConcurrentHashMap.class)); + sentAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); + } + + // #4.1 add + if (sentAttrToThingsboard.getPathPostParametersAdd().size() > 0) { + // update value in Resources + registrationIds.forEach(registrationId -> { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(null, registrationId); + LeshanServer lwServer = lwM2MClient.getLwServer(); + Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId); + log.warn("[{}] # 4.1", registration.getEndpoint()); + this.updateResourceValueObserve(lwServer, registration, sentAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ); + // sent attr/telemetry to tingsboard for new path + this.updateAttrTelemetry(registration, false, sentAttrToThingsboard.getPathPostParametersAdd()); + }); + } + // #4.2 del + if (sentAttrToThingsboard.getPathPostParametersDel().size() > 0) { + ResultsAnalyzerParameters sentAttrToThingsboardDel = this.getAnalyzerParameters(sentAttrToThingsboard.getPathPostParametersAdd(), sentAttrToThingsboard.getPathPostParametersDel()); + sentAttrToThingsboard.setPathPostParametersDel(sentAttrToThingsboardDel.getPathPostParametersDel()); + } + + // #5.1 + if (!observeOld.equals(observeNew)) { + Set observeSetOld = new Gson().fromJson(observeOld, Set.class); + Set observeSetNew = new Gson().fromJson(observeNew, Set.class); + //#5.2 add + // path Attr/Telemetry includes newObserve + attributeSetOld.addAll(telemetrySetOld); + ResultsAnalyzerParameters sentObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeSetOld); // add observe + attributeSetNew.addAll(telemetrySetNew); + ResultsAnalyzerParameters sentObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeSetNew); // add observe + // does not include oldObserve + ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sentObserveToClientOld.getPathPostParametersAdd(), sentObserveToClientNew.getPathPostParametersAdd()); + // sent Request observe to Client + registrationIds.forEach(registrationId -> { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClient(null, registrationId); + LeshanServer lwServer = lwM2MClient.getLwServer(); + Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId); + log.warn("[{}] # 5.1", registration.getEndpoint()); + this.updateResourceValueObserve(lwServer, registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE); + // 5.3 del + // sent Request cancel observe to Client + this.cancelObserveIsValue(lwServer, registration, postObserveAnalyzer.getPathPostParametersDel()); + }); + } + } + } + + /** + * Compare old list with new list after change AttrTelemetryObserve in config Profile + * + * @param parametersOld - + * @param parametersNew - + * @return ResultsAnalyzerParameters: add && new + */ + private ResultsAnalyzerParameters getAnalyzerParameters(Set parametersOld, Set parametersNew) { + ResultsAnalyzerParameters analyzerParameters = null; + if (!parametersOld.equals(parametersNew)) { + analyzerParameters = new ResultsAnalyzerParameters(); + analyzerParameters.setPathPostParametersAdd(parametersNew + .stream().filter(p -> !parametersOld.contains(p)).collect(Collectors.toSet())); + analyzerParameters.setPathPostParametersDel(parametersOld + .stream().filter(p -> !parametersNew.contains(p)).collect(Collectors.toSet())); + } + return analyzerParameters; + } + + private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap keyNameOld, ConcurrentMap keyNameNew) { + ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); + Set paths = keyNameNew.entrySet() + .stream() + .filter(e -> !e.getValue().equals(keyNameOld.get(e.getKey()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).keySet(); + analyzerParameters.setPathPostParametersAdd(paths); + return analyzerParameters; + } + + private ResultsAnalyzerParameters getAnalyzerParametersIn(Set parametersObserve, Set parameters) { + ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); + analyzerParameters.setPathPostParametersAdd(parametersObserve + .stream().filter(parameters::contains).collect(Collectors.toSet())); + return analyzerParameters; + } + + /** + * Update Resource value after change RezAttrTelemetry in config Profile + * sent response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue() + * + * @param lwServer - LeshanServer + * @param registration - Registration LwM2M Client + * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] + */ + private void updateResourceValueObserve(LeshanServer lwServer, Registration registration, Set targets, String typeOper) { + targets.forEach(target -> { + LwM2mPath pathIds = new LwM2mPath(target); + if (pathIds.isResource()) { + if (GET_TYPE_OPER_READ.equals(typeOper)) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, + ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout(), + false); + } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, + null, null, null, null, this.context.getCtxServer().getTimeout(), + false); + } + } + }); + } + + private void cancelObserveIsValue(LeshanServer lwServer, Registration registration, Set paramAnallyzer) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); + paramAnallyzer.forEach(p -> { + if (this.getResourceValue(lwM2MClient, new LwM2mPath(p)) != null) { + this.setCancelObservationRecourse(lwServer, registration, p); + } + } + ); + } + + private ResourceValue getResourceValue(LwM2MClient lwM2MClient, LwM2mPath pathIds) { + ResourceValue resourceValue = null; + if (pathIds.isResource()) { + resourceValue = lwM2MClient.getResources().get(pathIds.toString()); + } + return resourceValue; + } + + /** + * Trigger Server path = "/1/0/8" + * + * Trigger bootStrap path = "/1/0/9" - have to implemented on client + */ + public void doTrigger(LeshanServer lwServer, Registration registration, String path) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_EXECUTE, + ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout(), + false); + } + + /** + * Session device in thingsboard is closed + * + * @param sessionInfo - lwm2m client + */ + private void doCloseSession(SessionInfoProto sessionInfo) { + TransportProtos.SessionEvent event = SessionEvent.CLOSED; + TransportProtos.SessionEventMsg msg = TransportProtos.SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .setEvent(event).build(); + transportService.process(sessionInfo, msg, null); + } + + /** + * Deregister session in transport + * + * @param sessionInfo - lwm2m client + */ + public void doDisconnect(SessionInfoProto sessionInfo) { + transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); + transportService.deregisterSession(sessionInfo); + } + + private void checkInactivityAndReportActivity() { + lwM2mInMemorySecurityStore.getSessions().forEach((key, value) -> this.checkInactivity(this.getValidateSessionInfo(key))); + } + + /** + * if sessionInfo removed from sessions, then new registerAsyncSession + * @param sessionInfo - + */ + private void checkInactivity(SessionInfoProto sessionInfo) { + if (transportService.reportActivity(sessionInfo) == null) { + transportService.registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(this, sessionInfo)); + } + } + + public void sentLogsToThingsboard(String msg, Registration registration) { + if (msg != null) { + JsonObject telemetries = new JsonObject(); + telemetries.addProperty(LOG_LW2M_TELEMETRY, msg); + this.updateParametersOnThingsboard(telemetries, LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, registration); + } + } + + /** + * @param path - path resource + * @return - value of Resource or null + */ + private String getResourceValueToString(LwM2MClient lwM2MClient, String path) { + LwM2mPath pathIds = new LwM2mPath(path); + ResourceValue resourceValue = this.getResourceValue(lwM2MClient, pathIds); + return (resourceValue == null) ? null : + (String) this.converter.convertValue(resourceValue.getResourceValue(), this.context.getCtxServer().getResourceModelType(lwM2MClient.getRegistration(), pathIds), ResourceModel.Type.STRING, pathIds); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index ea440a33d0..2028fb4f00 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -24,17 +24,25 @@ import org.eclipse.leshan.server.queue.PresenceListener; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationListener; import org.eclipse.leshan.server.registration.RegistrationUpdate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; import java.util.Collection; @Slf4j +@Component() +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2mServerListener { + private LeshanServer lhServer; - private LwM2MTransportService service; - public LwM2mServerListener(LeshanServer lhServer, LwM2MTransportService service) { + @Autowired + private LwM2MTransportServiceImpl service; + + public LwM2mServerListener init(LeshanServer lhServer) { this.lhServer = lhServer; - this.service = service; + return this; } public final RegistrationListener registrationListener = new RegistrationListener() { @@ -96,8 +104,7 @@ public class LwM2mServerListener { try { service.onObservationResponse(registration, observation.getPath().toString(), response); } catch (Exception e) { - e.printStackTrace(); - log.error("onResponse"); + log.error("[{}] onResponse", e.toString()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java index 6519b5cd33..d48e3e4c9e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java @@ -27,7 +27,7 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; -import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportService; +import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportServiceImpl; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.Map; @@ -48,7 +48,7 @@ public class LwM2MClient implements Cloneable { private UUID sessionUuid; private UUID profileUuid; private LeshanServer lwServer; - private LwM2MTransportService lwM2MTransportService; + private LwM2MTransportServiceImpl lwM2MTransportServiceImpl; private Registration registration; private ValidateDeviceCredentialsResponseMsg credentialsResponse; private Map attributes; @@ -92,7 +92,7 @@ public class LwM2MClient implements Cloneable { this.pendingRequests.remove(path); if (this.pendingRequests.size() == 0) { this.initValue(); - this.lwM2MTransportService.putDelayedUpdateResourcesThingsboard(this); + this.lwM2MTransportServiceImpl.putDelayedUpdateResourcesThingsboard(this); } } @@ -123,7 +123,7 @@ public class LwM2MClient implements Cloneable { public void onSuccessOrErrorDelayedRequests(String path) { if (path != null) this.delayedRequests.remove(path); if (this.delayedRequests.size() == 0 && this.getDelayedRequestsId().size() == 0) { - this.lwM2MTransportService.updatesAndSentModelParameter(this); + this.lwM2MTransportServiceImpl.updatesAndSentModelParameter(this); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2MSetSecurityStoreServer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2MSetSecurityStoreServer.java deleted file mode 100644 index 7e2a9960d1..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2MSetSecurityStoreServer.java +++ /dev/null @@ -1,241 +0,0 @@ -/** - * Copyright © 2016-2020 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.secure; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.Hex; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; -import org.eclipse.leshan.server.redis.RedisRegistrationStore; -import org.eclipse.leshan.server.redis.RedisSecurityStore; -import org.eclipse.leshan.server.security.DefaultAuthorizer; -import org.eclipse.leshan.server.security.EditableSecurityStore; -import org.eclipse.leshan.server.security.SecurityChecker; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; -import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.util.Pool; - -import java.math.BigInteger; -import java.net.URI; -import java.net.URISyntaxException; -import java.security.KeyStore; -import java.security.PublicKey; -import java.security.PrivateKey; -import java.security.AlgorithmParameters; -import java.security.KeyFactory; -import java.security.GeneralSecurityException; -import java.security.KeyStoreException; -import java.security.cert.X509Certificate; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.KeySpec; -import java.util.Arrays; - -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.REDIS; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; - - -@Slf4j -@Data -public class LwM2MSetSecurityStoreServer { - - private KeyStore keyStore; - private X509Certificate certificate; - private PublicKey publicKey; - private PrivateKey privateKey; - private LwM2MTransportContextServer context; - private LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; - - private LeshanServerBuilder builder; - EditableSecurityStore securityStore; - - public LwM2MSetSecurityStoreServer(LeshanServerBuilder builder, LwM2MTransportContextServer context, LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore, LwM2MSecurityMode dtlsMode) { - this.builder = builder; - this.context = context; - this.lwM2mInMemorySecurityStore = lwM2mInMemorySecurityStore; - /** Set securityStore with new registrationStore */ - switch (dtlsMode) { - /** Use PSK only */ - case PSK: - generatePSK_RPK(); - if (this.privateKey != null && this.privateKey.getEncoded().length > 0) { - builder.setPrivateKey(this.privateKey); - builder.setPublicKey(null); - getParamsPSK(); - } - break; - /** Use RPK only */ - case RPK: - generatePSK_RPK(); - if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && - this.privateKey != null && this.privateKey.getEncoded().length > 0) { - builder.setPublicKey(this.publicKey); - builder.setPrivateKey(this.privateKey); - getParamsRPK(); - } - break; - /** Use x509 only */ - case X509: - setServerWithX509Cert(); - break; - /** No security */ - case NO_SEC: - builder.setTrustedCertificates(new X509Certificate[0]); - break; - /** Use x509 with EST */ - case X509_EST: - // TODO support sentinel pool and make pool configurable - break; - case REDIS: - /** - * Set securityStore with new registrationStore (if use redis store) - * Connect to redis - */ - Pool jedis = null; - try { - jedis = new JedisPool(new URI(this.context.getCtxServer().getRedisUrl())); - securityStore = new RedisSecurityStore(jedis); - builder.setRegistrationStore(new RedisRegistrationStore(jedis)); - } catch (URISyntaxException e) { - e.printStackTrace(); - } - break; - default: - } - - /** Set securityStore with new registrationStore (if not redis)*/ - if (dtlsMode.code < REDIS.code) { - securityStore = lwM2mInMemorySecurityStore; - if (dtlsMode == X509) { - builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() { - @Override - protected boolean matchX509Identity(String endpoint, String receivedX509CommonName, - String expectedX509CommonName) { - return endpoint.startsWith(expectedX509CommonName); - } - })); - } - } - - /** Set securityStore with new registrationStore */ - builder.setSecurityStore(securityStore); - } - - private void generatePSK_RPK() { - try { - /** Get Elliptic Curve Parameter spec for secp256r1 */ - AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); - algoParameters.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - if (this.context.getCtxServer().getServerPublicX() != null && !this.context.getCtxServer().getServerPublicX().isEmpty() && this.context.getCtxServer().getServerPublicY() != null && !this.context.getCtxServer().getServerPublicY().isEmpty()) { - /** Get point values */ - byte[] publicX = Hex.decodeHex(this.context.getCtxServer().getServerPublicX().toCharArray()); - byte[] publicY = Hex.decodeHex(this.context.getCtxServer().getServerPublicY().toCharArray()); - /** Create key specs */ - KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), - parameterSpec); - /** Get keys */ - this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - } - if (this.context.getCtxServer().getServerPrivateS() != null && !this.context.getCtxServer().getServerPrivateS().isEmpty()) { - /** Get point values */ - byte[] privateS = Hex.decodeHex(this.context.getCtxServer().getServerPrivateS().toCharArray()); - /** Create key specs */ - KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); - /** Get keys */ - this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); - } - } catch (GeneralSecurityException | IllegalArgumentException e) { - log.error("[{}] Failed generate Server PSK/RPK", e.getMessage()); - throw new RuntimeException(e); - } - } - - private void setServerWithX509Cert() { - try { - if (this.context.getCtxServer().getKeyStoreValue() != null) { - setBuilderX509(); - X509Certificate rootCAX509Cert = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getRootAlias()); - if (rootCAX509Cert != null) { - X509Certificate[] trustedCertificates = new X509Certificate[1]; - trustedCertificates[0] = rootCAX509Cert; - builder.setTrustedCertificates(trustedCertificates); - } else { - /** by default trust all */ - builder.setTrustedCertificates(new X509Certificate[0]); - } - } - else { - /** by default trust all */ - this.builder.setTrustedCertificates(new X509Certificate[0]); - log.error("Unable to load X509 files for LWM2MServer"); - } - } catch (KeyStoreException ex) { - log.error("[{}] Unable to load X509 files server", ex.getMessage()); - } - } - - private void setBuilderX509() { - /** - * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE - * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks - */ - try { - X509Certificate serverCertificate = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getServerAlias()); - PrivateKey privateKey = (PrivateKey) this.context.getCtxServer().getKeyStoreValue().getKey(this.context.getCtxServer().getServerAlias(), this.context.getCtxServer().getKeyStorePasswordServer() == null ? null : this.context.getCtxServer().getKeyStorePasswordServer().toCharArray()); - this.builder.setPrivateKey(privateKey); - this.builder.setCertificateChain(new X509Certificate[]{serverCertificate}); - } catch (Exception ex) { - log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); - } - } - - private void getParamsPSK() { - log.info("\nServer uses PSK -> private key : \n security key : [{}] \n serverSecureURI : [{}]", - Hex.encodeHexString(this.privateKey.getEncoded()), - this.context.getCtxServer().getServerSecureHost() + ":" + Integer.toString(this.context.getCtxServer().getServerSecurePort())); - } - - private void getParamsRPK() { - if (this.publicKey instanceof ECPublicKey) { - /** Get x coordinate */ - byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray(); - if (x[0] == 0) - x = Arrays.copyOfRange(x, 1, x.length); - - /** Get Y coordinate */ - byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray(); - if (y[0] == 0) - y = Arrays.copyOfRange(y, 1, y.length); - - /** Get Curves params */ - String params = ((ECPublicKey) this.publicKey).getParams().toString(); - log.info( - " \nServer uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]", - params, Hex.encodeHexString(x), Hex.encodeHexString(y), - Hex.encodeHexString(this.publicKey.getEncoded()), - Hex.encodeHexString(this.privateKey.getEncoded())); - } else { - throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); - } - } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java index ad8dd4b0f8..fb90b1537c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java @@ -25,32 +25,32 @@ import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityStoreListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MGetSecurityInfo; +import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; +import org.thingsboard.server.transport.lwm2m.secure.LwM2mValidateCredentialsSecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler; import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; import org.thingsboard.server.transport.lwm2m.utils.TypeServer; -import java.util.Map; -import java.util.UUID; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DEFAULT_MODE; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; @Slf4j -@Component("LwM2mInMemorySecurityStore") +@Service("LwM2mInMemorySecurityStore") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { // lock for the two maps @@ -63,26 +63,36 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { private SecurityStoreListener listener; @Autowired - LwM2MGetSecurityInfo lwM2MGetSecurityInfo; + LwM2mValidateCredentialsSecurityInfo lwM2MValidateCredentialsSecurityInfo; + /** + * Start after DefaultAuthorizer or LwM2mPskStore + * @param endPoint - + * @return SecurityInfo + */ @Override public SecurityInfo getByEndpoint(String endPoint) { readLock.lock(); try { String registrationId = this.getByRegistrationId(endPoint, null); - SecurityInfo info = (registrationId != null && sessions.size() > 0 && sessions.get(registrationId) != null) ? sessions.get(registrationId).getInfo() : this.add(endPoint); + SecurityInfo info = (registrationId != null && sessions.size() > 0 && sessions.get(registrationId) != null) ? sessions.get(registrationId).getInfo() : this.addLwM2MClientToSession(endPoint); return info; } finally { readLock.unlock(); } } + /** + * Start after LwM2mPskStore + * @param identity - + * @return SecurityInfo + */ @Override public SecurityInfo getByIdentity(String identity) { readLock.lock(); try { String integrationId = this.getByRegistrationId(null, identity); - return (integrationId != null) ? sessions.get(integrationId).getInfo() : add(identity); + return (integrationId != null) ? sessions.get(integrationId).getInfo() : this.addLwM2MClientToSession(identity); } finally { readLock.unlock(); } @@ -141,11 +151,19 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { return this.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue(); } + + /** + * Update in sessions (LwM2MClient for key registration_Id) after starting registration LwM2MClient in LwM2MTransportServiceImpl + * Remove from sessions LwM2MClient with key registration_Endpoint + * @param lwServer - + * @param registration - + * @return LwM2MClient after adding it to session + */ public LwM2MClient updateInSessionsLwM2MClient(LeshanServer lwServer, Registration registration) { writeLock.lock(); try { if (this.sessions.get(registration.getEndpoint()) == null) { - this.add(registration.getEndpoint()); + this.addLwM2MClientToSession(registration.getEndpoint()); } LwM2MClient lwM2MClient = this.sessions.get(registration.getEndpoint()); lwM2MClient.setLwServer(lwServer); @@ -166,45 +184,43 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { return (registrationIds != null && registrationIds.size() > 0) ? registrationIds.get(0) : null; } - public String getByRegistrationId(String credentialsId) { - List registrationIds = (this.sessions.entrySet().stream().filter(model -> credentialsId.equals(model.getValue().getEndPoint())).map(model -> model.getKey()).collect(Collectors.toList()).size() > 0) ? - this.sessions.entrySet().stream().filter(model -> credentialsId.equals(model.getValue().getEndPoint())).map(model -> model.getKey()).collect(Collectors.toList()) : - this.sessions.entrySet().stream().filter(model -> credentialsId.equals(model.getValue().getIdentity())).map(model -> model.getKey()).collect(Collectors.toList()); - return (registrationIds != null && registrationIds.size() > 0) ? registrationIds.get(0) : null; - } - public Registration getByRegistration(String registrationId) { return this.sessions.get(registrationId).getRegistration(); } - private SecurityInfo add(String identity) { - ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(identity, TypeServer.CLIENT); - UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; - if (store.getSecurityInfo() != null) { - if (store.getSecurityMode() < DEFAULT_MODE.code) { + /** + * Add new LwM2MClient to session + * @param identity- + * @return SecurityInfo. If error - SecurityInfoError + * and log: + * - FORBIDDEN - if there is no authorization + * - profileUuid - if the device does not have a profile + * - device - if the thingsboard does not have a device with a name equal to the identity + */ + private SecurityInfo addLwM2MClientToSession(String identity) { + ReadResultSecurityStore store = lwM2MValidateCredentialsSecurityInfo.validateCredentialsSecurityInfo(identity, TypeServer.CLIENT); + if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; + if (store.getSecurityInfo() != null && profileUuid != null) { String endpoint = store.getSecurityInfo().getEndpoint(); -// sessions.put(endpoint, new LwM2MClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), null, null, profileUuid)); sessions.put(endpoint, new LwM2MClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), null, profileUuid)); - } - } else { - if (store.getSecurityMode() == NO_SEC.code && profileUuid != null) -// sessions.put(identity, new LwM2MClient(identity, null, null, store.getMsg(), null, null, profileUuid)); + } else if (store.getSecurityMode() == NO_SEC.code && profileUuid != null) { sessions.put(identity, new LwM2MClient(identity, null, null, store.getMsg(), null, profileUuid)); - else { - log.error("Registration failed: FORBIDDEN/profileUuid/device [{}] , endpointId: [{}]", profileUuid, identity); - /** - * Return Error securityInfo - */ - byte[] preSharedKey = Hex.decodeHex("0A0B".toCharArray()); - SecurityInfo info = SecurityInfo.newPreSharedKeyInfo("error", "error_identity", preSharedKey); - return info; + } else { + log.error("Registration failed: FORBIDDEN/profileUuid/device [{}] , endpointId: [{}]", profileUuid, identity); + /** + * Return Error securityInfo + */ + byte[] preSharedKey = Hex.decodeHex("0A0B".toCharArray()); + SecurityInfo infoError = SecurityInfo.newPreSharedKeyInfo("error", "error_identity", preSharedKey); + return infoError; + } } - } - return store.getSecurityInfo(); + return store.getSecurityInfo(); } - public Map getSession (UUID sessionUuId){ - return this.sessions.entrySet().stream().filter(e -> e.getValue().getSessionUuid().equals(sessionUuId)).collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); + public Map getSession(UUID sessionUuId) { + return this.sessions.entrySet().stream().filter(e -> e.getValue().getSessionUuid().equals(sessionUuId)).collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); } public Map getSessions() { @@ -219,13 +235,10 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { return this.profiles.get(profileUuId); } - public MapsetProfiles(Map profiles) { + public Map setProfiles(Map profiles) { return this.profiles = profiles; } - /** - * @param deviceProfile - */ public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) { JsonObject profilesConfigData = LwM2MTransportHandler.getObserveAttrTelemetryFromThingsboard(deviceProfile); if (profilesConfigData != null) { @@ -233,5 +246,4 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { } return (profilesConfigData != null); } - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index cca1effae4..4a52124af9 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -129,7 +129,6 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { case FLOAT: return String.valueOf(value); case TIME: -// return Long.toString(((Date) value).getTime()); String DATE_FORMAT = "MMM d, yyyy HH:mm a"; Long timeValue = ((Date) value).getTime(); DateFormat formatter = new SimpleDateFormat(DATE_FORMAT); diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 793ea83373..bcf36485a9 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -67,11 +67,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 - - 1.8 - 1.8 - org.apache.maven.plugins @@ -87,4 +82,4 @@ - \ No newline at end of file + diff --git a/pom.xml b/pom.xml index 4522ea51bf..3cbd57e508 100755 --- a/pom.xml +++ b/pom.xml @@ -579,7 +579,7 @@ org.apache.maven.plugins maven-compiler-plugin - 2.5.1 + 3.8.1 1.8 1.8