From 8b5083c7148c2f44e30c03464cffbd89953ab205 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Tue, 11 Oct 2022 10:29:12 +0200 Subject: [PATCH] Add Transport Layer abstraction at Client side. --- .../CaliforniumConnectionController.java | 23 + .../CaliforniumEndpointsManager.java | 469 ------------------ .../californium/LwM2mClientCoapResource.java | 44 +- .../ObserveCompositeRelationFilter.java | 2 +- .../client/californium/RootResource.java | 111 ++--- .../bootstrap/BootstrapResource.java | 22 +- .../endpoint/CaliforniumClientEndpoint.java | 177 +++++++ .../CaliforniumClientEndpointFactory.java | 44 ++ .../CaliforniumClientEndpointsProvider.java | 378 ++++++++++++++ .../endpoint/ClientCoapMessageTranslator.java | 125 +++++ .../endpoint/ClientProtocolProvider.java | 33 ++ .../endpoint/ServerIdentityExtractor.java | 24 + .../coap/CoapClientEndpointFactory.java | 124 +++++ .../coap/CoapClientProtocolProvider.java | 54 ++ .../coap/CoapOscoreClientEndpointFactory.java | 153 ++++++ .../coap/CoapOscoreProtocolProvider.java | 26 + .../coaps/CoapsClientEndpointFactory.java | 421 ++++++++++++++++ .../coaps/CoapsClientProtocolProvider.java | 64 +++ .../californium/object/ObjectResource.java | 156 +++--- .../CaliforniumLwM2mRequestSender.java | 137 ----- .../request/CoapRequestBuilder.java | 9 +- ...Builder.java => LwM2mResponseBuilder.java} | 4 +- .../client/californium/LeshanBuilderTest.java | 40 -- .../eclipse/leshan/client}/LeshanClient.java | 206 ++------ .../leshan/client}/LeshanClientBuilder.java | 160 +----- .../leshan/client/bootstrap/CertPathUtil.java | 157 ++++++ .../DefaultBootstrapConsistencyChecker.java | 7 +- .../endpoint/ClientEndpointToolbox.java | 60 +++ .../endpoint/DefaultEndpointsManager.java | 117 +++++ .../client/endpoint/LwM2mClientEndpoint.java | 43 ++ .../LwM2mClientEndpointsProvider.java | 49 ++ .../engine/DefaultRegistrationEngine.java | 20 +- .../DefaultRegistrationEngineFactory.java | 4 +- .../engine/RegistrationEngineFactory.java | 4 +- .../request/DefaultDownlinkReceiver.java | 403 +++++++++++++++ .../request/DefaultUplinkRequestSender.java | 47 ++ .../request/DownlinkRequestReceiver.java | 30 ++ ...stSender.java => UplinkRequestSender.java} | 8 +- .../leshan/client/send/DataSenderManager.java | 6 +- .../leshan/client/servers/ServerIdentity.java | 19 +- .../leshan/client/util/ServerTest.java | 45 -- .../leshan/client/demo/DtlsSessionLogger.java | 90 ++++ .../leshan/client/demo/LeshanClientDemo.java | 144 ++---- .../client/demo/cli/LeshanClientDemoCLI.java | 9 - .../cli/interactive/InteractiveCommands.java | 2 +- .../identity/IdentityHandlerProvider.java | 4 + .../integration/tests/SecurityTest.java | 8 +- .../tests/lockstep/LockStepLwM2mClient.java | 3 +- .../util/BootstrapIntegrationTestHelper.java | 11 +- .../tests/util/IntegrationTestHelper.java | 11 +- .../util/QueueModeIntegrationTestHelper.java | 4 +- .../util/SecureIntegrationTestHelper.java | 130 ++--- 52 files changed, 3050 insertions(+), 1391 deletions(-) create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumConnectionController.java delete mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpoint.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpointFactory.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpointsProvider.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ClientCoapMessageTranslator.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ClientProtocolProvider.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ServerIdentityExtractor.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapClientEndpointFactory.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapClientProtocolProvider.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapOscoreClientEndpointFactory.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapOscoreProtocolProvider.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coaps/CoapsClientEndpointFactory.java create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coaps/CoapsClientProtocolProvider.java delete mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java rename leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/{LwM2mClientResponseBuilder.java => LwM2mResponseBuilder.java} (97%) delete mode 100644 leshan-client-cf/src/test/java/org/eclipse/leshan/client/californium/LeshanBuilderTest.java rename {leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium => leshan-client-core/src/main/java/org/eclipse/leshan/client}/LeshanClient.java (53%) rename {leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium => leshan-client-core/src/main/java/org/eclipse/leshan/client}/LeshanClientBuilder.java (65%) create mode 100644 leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/CertPathUtil.java rename {leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium => leshan-client-core/src/main/java/org/eclipse/leshan/client}/bootstrap/DefaultBootstrapConsistencyChecker.java (88%) create mode 100644 leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/ClientEndpointToolbox.java create mode 100644 leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/DefaultEndpointsManager.java create mode 100644 leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/LwM2mClientEndpoint.java create mode 100644 leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/LwM2mClientEndpointsProvider.java create mode 100644 leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DefaultDownlinkReceiver.java create mode 100644 leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DefaultUplinkRequestSender.java create mode 100644 leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DownlinkRequestReceiver.java rename leshan-client-core/src/main/java/org/eclipse/leshan/client/request/{LwM2mRequestSender.java => UplinkRequestSender.java} (94%) delete mode 100644 leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ServerTest.java create mode 100644 leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/DtlsSessionLogger.java diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumConnectionController.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumConnectionController.java new file mode 100644 index 0000000000..1dce758a8d --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumConnectionController.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium; + +import org.eclipse.californium.core.network.Endpoint; +import org.eclipse.leshan.client.servers.ServerIdentity; + +public interface CaliforniumConnectionController { + void forceReconnection(Endpoint endpoint, ServerIdentity identity, boolean resume); +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java deleted file mode 100644 index 701919a2d6..0000000000 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java +++ /dev/null @@ -1,469 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Sierra Wireless and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v2.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v20.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.html. - * - * Contributors: - * Sierra Wireless - initial API and implementation - * Rikard Höglund (RISE SICS) - Additions to support OSCORE - *******************************************************************************/ -package org.eclipse.leshan.client.californium; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.eclipse.californium.core.CoapServer; -import org.eclipse.californium.core.config.CoapConfig; -import org.eclipse.californium.core.network.CoapEndpoint; -import org.eclipse.californium.core.network.Endpoint; -import org.eclipse.californium.cose.AlgorithmID; -import org.eclipse.californium.cose.CoseException; -import org.eclipse.californium.elements.Connector; -import org.eclipse.californium.elements.EndpointContext; -import org.eclipse.californium.elements.auth.RawPublicKeyIdentity; -import org.eclipse.californium.elements.config.Configuration; -import org.eclipse.californium.elements.util.CertPathUtil; -import org.eclipse.californium.scandium.DTLSConnector; -import org.eclipse.californium.scandium.config.DtlsConfig; -import org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder; -import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; -import org.eclipse.californium.scandium.dtls.pskstore.AdvancedSinglePskStore; -import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; -import org.eclipse.californium.scandium.dtls.x509.SingleCertificateProvider; -import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; -import org.eclipse.leshan.client.EndpointsManager; -import org.eclipse.leshan.client.servers.ServerIdentity; -import org.eclipse.leshan.client.servers.ServerIdentity.Role; -import org.eclipse.leshan.client.servers.ServerInfo; -import org.eclipse.leshan.core.CertificateUsage; -import org.eclipse.leshan.core.SecurityMode; -import org.eclipse.leshan.core.californium.EndpointContextUtil; -import org.eclipse.leshan.core.californium.EndpointFactory; -import org.eclipse.leshan.core.californium.oscore.cf.InMemoryOscoreContextDB; -import org.eclipse.leshan.core.californium.oscore.cf.OscoreParameters; -import org.eclipse.leshan.core.californium.oscore.cf.StaticOscoreStore; -import org.eclipse.leshan.core.oscore.InvalidOscoreSettingException; -import org.eclipse.leshan.core.oscore.OscoreIdentity; -import org.eclipse.leshan.core.oscore.OscoreValidator; -import org.eclipse.leshan.core.request.Identity; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.upokecenter.cbor.CBORObject; - -/** - * An {@link EndpointsManager} based on Californium(CoAP implementation) and Scandium (DTLS implementation) which - * supports only 1 server. - */ -public class CaliforniumEndpointsManager implements EndpointsManager { - - private static final Logger LOG = LoggerFactory.getLogger(CaliforniumEndpointsManager.class); - - protected boolean started = false; - - protected ServerIdentity currentServer; - protected CoapEndpoint currentEndpoint; - - protected Builder dtlsConfigbuilder; - protected List trustStore; - protected Configuration coapConfig; - protected InetSocketAddress localAddress; - protected CoapServer coapServer; - protected EndpointFactory endpointFactory; - - public CaliforniumEndpointsManager(InetSocketAddress localAddress, Configuration coapConfig, - Builder dtlsConfigBuilder, EndpointFactory endpointFactory) { - this(localAddress, coapConfig, dtlsConfigBuilder, null, endpointFactory); - } - - /** - * @since 2.0 - */ - public CaliforniumEndpointsManager(InetSocketAddress localAddress, Configuration coapConfig, - Builder dtlsConfigBuilder, List trustStore, EndpointFactory endpointFactory) { - this.localAddress = localAddress; - this.coapConfig = coapConfig; - this.dtlsConfigbuilder = dtlsConfigBuilder; - this.trustStore = trustStore; - this.endpointFactory = endpointFactory; - } - - public void setCoapServer(CoapServer coapServer) { - this.coapServer = coapServer; - } - - @Override - public synchronized ServerIdentity createEndpoint(ServerInfo serverInfo, boolean clientInitiatedOnly) { - // Clear previous endpoint - if (currentEndpoint != null) { - coapServer.getEndpoints().remove(currentEndpoint); - currentEndpoint.destroy(); - } - - // Create new endpoint - Identity serverIdentity; - if (serverInfo.isSecure()) { - DtlsConnectorConfig incompleteConfig = dtlsConfigbuilder.getIncompleteConfig(); - Builder newBuilder = DtlsConnectorConfig.builder(incompleteConfig); - - // Support PSK - if (serverInfo.secureMode == SecurityMode.PSK) { - AdvancedSinglePskStore staticPskStore = new AdvancedSinglePskStore(serverInfo.pskId, serverInfo.pskKey); - newBuilder.setAdvancedPskStore(staticPskStore); - serverIdentity = Identity.psk(serverInfo.getAddress(), serverInfo.pskId); - filterCipherSuites(newBuilder, dtlsConfigbuilder.getIncompleteConfig().getSupportedCipherSuites(), true, - false); - } else if (serverInfo.secureMode == SecurityMode.RPK) { - // set identity - SingleCertificateProvider singleCertificateProvider = new SingleCertificateProvider( - serverInfo.privateKey, serverInfo.publicKey); - // we don't want to check Key Pair here, if we do it this should be done in BootstrapConsistencyChecker - singleCertificateProvider.setVerifyKeyPair(false); - newBuilder.setCertificateIdentityProvider(singleCertificateProvider); - // set RPK truststore - final PublicKey expectedKey = serverInfo.serverPublicKey; - NewAdvancedCertificateVerifier rpkVerifier = new StaticNewAdvancedCertificateVerifier.Builder() - .setTrustedRPKs(new RawPublicKeyIdentity(expectedKey)).build(); - newBuilder.setAdvancedCertificateVerifier(rpkVerifier); - serverIdentity = Identity.rpk(serverInfo.getAddress(), expectedKey); - filterCipherSuites(newBuilder, dtlsConfigbuilder.getIncompleteConfig().getSupportedCipherSuites(), - false, true); - } else if (serverInfo.secureMode == SecurityMode.X509) { - // set identity - SingleCertificateProvider singleCertificateProvider = new SingleCertificateProvider( - serverInfo.privateKey, new Certificate[] { serverInfo.clientCertificate }); - // we don't want to check Key Pair here, if we do it this should be done in BootstrapConsistencyChecker - singleCertificateProvider.setVerifyKeyPair(false); - newBuilder.setCertificateIdentityProvider(singleCertificateProvider); - - // LWM2M v1.1.1 - 5.2.8.7. Certificate Usage Field - // - // 0: Certificate usage 0 ("CA constraint") - // - trustStore is combination of client's configured trust store and provided certificate in server - // info - // - must do PKIX validation with trustStore to build certPath - // - must check that given certificate is part of certPath - // - validate server name - // - // 1: Certificate usage 1 ("service certificate constraint") - // - trustStore is client's configured trust store - // - must do PKIX validation with trustStore - // - target certificate must match what is provided certificate in server info - // - validate server name - // - // 2: Certificate usage 2 ("trust anchor assertion") - // - trustStore is only the provided certificate in server info - // - must do PKIX validation with trustStore - // - validate server name - // - // 3: Certificate usage 3 ("domain-issued certificate") (default mode if missing) - // - no trustStore used in this mode - // - target certificate must match what is provided certificate in server info - // - validate server name - - CertificateUsage certificateUsage = serverInfo.certificateUsage != null ? serverInfo.certificateUsage - : CertificateUsage.DOMAIN_ISSUER_CERTIFICATE; - - if (certificateUsage == CertificateUsage.CA_CONSTRAINT) { - X509Certificate[] trustedCertificates = null; - if (this.trustStore != null) { - trustedCertificates = CertPathUtil.toX509CertificatesList(this.trustStore) - .toArray(new X509Certificate[this.trustStore.size()]); - } - newBuilder.setAdvancedCertificateVerifier( - new CaConstraintCertificateVerifier(serverInfo.serverCertificate, trustedCertificates)); - } else if (certificateUsage == CertificateUsage.SERVICE_CERTIFICATE_CONSTRAINT) { - X509Certificate[] trustedCertificates = null; - - // - trustStore is client's configured trust store - if (this.trustStore != null) { - trustedCertificates = CertPathUtil.toX509CertificatesList(this.trustStore) - .toArray(new X509Certificate[this.trustStore.size()]); - } - - newBuilder.setAdvancedCertificateVerifier(new ServiceCertificateConstraintCertificateVerifier( - serverInfo.serverCertificate, trustedCertificates)); - } else if (certificateUsage == CertificateUsage.TRUST_ANCHOR_ASSERTION) { - newBuilder.setAdvancedCertificateVerifier(new TrustAnchorAssertionCertificateVerifier( - (X509Certificate) serverInfo.serverCertificate)); - } else if (certificateUsage == CertificateUsage.DOMAIN_ISSUER_CERTIFICATE) { - newBuilder.setAdvancedCertificateVerifier( - new DomainIssuerCertificateVerifier(serverInfo.serverCertificate)); - } - - // TODO We set CN with '*' as we are not able to know the CN for some certificate usage and so this is - // not used anymore to identify a server with x509. - // See : https://github.com/eclipse/leshan/issues/992 - serverIdentity = Identity.x509(serverInfo.getAddress(), "*"); - filterCipherSuites(newBuilder, dtlsConfigbuilder.getIncompleteConfig().getSupportedCipherSuites(), - false, true); - } else { - throw new RuntimeException("Unable to create connector : unsupported security mode"); - } - - // Handle DTLS mode - DtlsRole dtlsRole = incompleteConfig.getConfiguration().get(DtlsConfig.DTLS_ROLE); - if (dtlsRole == null) { - if (serverInfo.bootstrap) { - // For bootstrap no need to have DTLS role exchange - // and so we can set DTLS Connection as client only by default. - newBuilder.set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY); - } else if (clientInitiatedOnly) { - // if client initiated only we don't allow connector to work as server role. - newBuilder.set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY); - } else { - newBuilder.set(DtlsConfig.DTLS_ROLE, DtlsRole.BOTH); - } - } - - if (incompleteConfig.getConfiguration().get(DtlsConfig.DTLS_ROLE) == DtlsRole.BOTH) { - // Ensure that BOTH mode can be used or fallback to CLIENT_ONLY - if (serverInfo.secureMode == SecurityMode.X509) { - X509Certificate certificate = (X509Certificate) serverInfo.clientCertificate; - if (CertPathUtil.canBeUsedForAuthentication(certificate, true)) { - if (!CertPathUtil.canBeUsedForAuthentication(certificate, false)) { - newBuilder.set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY); - LOG.warn("Client certificate does not allow Server Authentication usage." - + "\nThis will prevent a LWM2M server to initiate DTLS connection to this client." - + "\nSee : https://github.com/eclipse/leshan/wiki/Server-Failover#about-connections"); - } - } - } - } - - currentEndpoint = endpointFactory.createSecuredEndpoint(newBuilder.build(), coapConfig, null, null); - } else if (serverInfo.useOscore) { - // oscore only mode - LOG.warn("Experimental OSCORE support is used for {}", serverInfo.getFullUri().toASCIIString()); - - try { - new OscoreValidator().validateOscoreSetting(serverInfo.oscoreSetting); - } catch (InvalidOscoreSettingException e) { - throw new RuntimeException(String.format("Unable to create endpoint for %s using OSCORE : Invalid %s.", - serverInfo, serverInfo.oscoreSetting), e); - } - - AlgorithmID hkdfAlg = null; - try { - hkdfAlg = AlgorithmID - .FromCBOR(CBORObject.FromObject(serverInfo.oscoreSetting.getHkdfAlgorithm().getValue())); - } catch (CoseException e) { - throw new RuntimeException(String.format( - "Unable to create endpoint for %s using OSCORE : Unable to decode OSCORE HKDF from %s.", - serverInfo, serverInfo.oscoreSetting), e); - } - AlgorithmID aeadAlg = null; - try { - aeadAlg = AlgorithmID - .FromCBOR(CBORObject.FromObject(serverInfo.oscoreSetting.getAeadAlgorithm().getValue())); - } catch (CoseException e) { - throw new RuntimeException(String.format( - "Unable to create endpoint for %s using OSCORE : Unable to decode OSCORE AEAD from %s.", - serverInfo, serverInfo.oscoreSetting), e); - } - - // TODO OSCORE kind of hack because californium doesn't support an empty byte[] array for salt ? - byte[] masterSalt = serverInfo.oscoreSetting.getMasterSalt().length == 0 ? null - : serverInfo.oscoreSetting.getMasterSalt(); - - OscoreParameters oscoreParameters = new OscoreParameters(serverInfo.oscoreSetting.getSenderId(), - serverInfo.oscoreSetting.getRecipientId(), serverInfo.oscoreSetting.getMasterSecret(), aeadAlg, - hkdfAlg, masterSalt); - - currentEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null, - new InMemoryOscoreContextDB(new StaticOscoreStore(oscoreParameters))); - - // Build server identity for OSCORE - serverIdentity = Identity.oscoreOnly(serverInfo.getAddress(), - new OscoreIdentity(serverInfo.oscoreSetting.getRecipientId())); - } else { - currentEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null, null); - serverIdentity = Identity.unsecure(serverInfo.getAddress()); - } - - // Add new endpoint - coapServer.addEndpoint(currentEndpoint); - - // Start endpoint if needed - if (serverInfo.bootstrap) { - currentServer = new ServerIdentity(serverIdentity, serverInfo.serverId, Role.LWM2M_BOOTSTRAP_SERVER); - } else { - currentServer = new ServerIdentity(serverIdentity, serverInfo.serverId); - } - if (started) { - coapServer.start(); - try { - currentEndpoint.start(); - LOG.info("New endpoint created for server {} at {}", currentServer.getUri(), currentEndpoint.getUri()); - } catch (IOException e) { - throw new RuntimeException("Unable to start endpoint", e); - } - } - return currentServer; - } - - @Override - public synchronized Collection createEndpoints(Collection serverInfo, - boolean clientInitiatedOnly) { - if (serverInfo == null || serverInfo.isEmpty()) - return null; - else { - // TODO support multi server - if (serverInfo.size() > 1) { - LOG.warn( - "CaliforniumEndpointsManager support only connection to 1 LWM2M server, first server will be used from the server list of {}", - serverInfo.size()); - } - ServerInfo firstServer = serverInfo.iterator().next(); - Collection servers = new ArrayList<>(1); - servers.add(createEndpoint(firstServer, clientInitiatedOnly)); - return servers; - } - } - - @Override - public long getMaxCommunicationPeriodFor(ServerIdentity server, long lifetimeInMs) { - // See https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/283 to better understand. - // TODO For DTLS, worst Handshake scenario should be taking into account too. - - int floor = 30000; // value from which we stop to adjust communication period using COAP EXCHANGE LIFETIME. - - // To be sure registration doesn't expired, update request should be send considering all CoAP retransmissions - // and registration lifetime. - // See https://tools.ietf.org/html/rfc7252#section-4.8.2 - long exchange_lifetime = coapConfig.get(CoapConfig.EXCHANGE_LIFETIME, TimeUnit.MILLISECONDS); - if (lifetimeInMs - exchange_lifetime >= floor) { - return lifetimeInMs - exchange_lifetime; - } else { - LOG.warn("Too small lifetime : we advice to not use a lifetime < (COAP EXCHANGE LIFETIME + 30s)"); - // lifetime value is too short, so we do a compromise and we don't remove COAP EXCHANGE LIFETIME completely - // We distribute the remaining lifetime range [0, exchange_lifetime + floor] on the remaining range - // [1,floor]s. - return lifetimeInMs * (floor - 1000) / (exchange_lifetime + floor) + 1000; - } - } - - @Override - public synchronized void forceReconnection(ServerIdentity server, boolean resume) { - // TODO support multi server - if (server == null || !server.equals(currentServer)) - return; - - Connector connector = currentEndpoint.getConnector(); - if (connector instanceof DTLSConnector) { - if (resume) { - LOG.info("Clear DTLS session for resumption for server {}", server.getUri()); - ((DTLSConnector) connector).forceResumeAllSessions(); - } else { - LOG.info("Clear DTLS session for server {}", server.getUri()); - ((DTLSConnector) connector).clearConnectionState(); - } - } - - } - - public synchronized Endpoint getEndpoint(ServerIdentity server) { - // TODO support multi server - if (server != null && server.equals(currentServer) && currentEndpoint.isStarted()) - return currentEndpoint; - return null; - } - - public synchronized ServerIdentity getServerIdentity(Endpoint endpoint, InetSocketAddress serverAddress, - EndpointContext endpointContext) { - // TODO support multi server - - // knowing used CoAP endpoint we should be able to know the server identity because : - // - we create 1 CoAP endpoint by server. - // - the dtls configuration ensure that only server with expected credential is able to talk. - // (see https://github.com/eclipse/leshan/issues/992 for more details) - if (endpoint != null && endpoint.equals(currentEndpoint) && currentEndpoint.isStarted()) { - // For UDP (not secure) endpoint we also check socket address as anybody send data to this kind of endpoint. - if (currentEndpoint.getConnector().getProtocol() == "UDP" - && !currentServer.getIdentity().getPeerAddress().equals(serverAddress)) { - return null; - } - // For OSCORE, be sure OSCORE is used. - if (currentServer.getIdentity().isOSCORE()) { - Identity foreignPeerIdentity = EndpointContextUtil.extractIdentity(endpointContext); - if (!foreignPeerIdentity.isOSCORE() // - // we also check OscoreIdentity but this is probably not useful - // because we are using static OSCOREstore which holds only 1 OscoreParameter, - // so if the request was successfully decrypted and OSCORE is used, this MUST be the right - // server. - || !foreignPeerIdentity.getOscoreIdentity() - .equals(currentServer.getIdentity().getOscoreIdentity())) { - return null; - } - } - return currentServer; - } - return null; - } - - @Override - public synchronized void start() { - if (started) - return; - started = true; - - // we don't have any endpoint so nothing to start - if (currentEndpoint == null) - return; - - coapServer.start(); - } - - @Override - public synchronized void stop() { - if (!started) - return; - started = false; - - // If we have no endpoint this means that we never start coap server - if (currentEndpoint == null) - return; - - coapServer.stop(); - } - - @Override - public synchronized void destroy() { - if (started) - started = false; - - coapServer.destroy(); - } - - private void filterCipherSuites(Builder dtlsConfigurationBuilder, List ciphers, boolean psk, - boolean requireServerCertificateMessage) { - if (ciphers == null) - return; - - List filteredCiphers = new ArrayList<>(); - for (CipherSuite cipher : ciphers) { - if (psk && cipher.isPskBased()) { - filteredCiphers.add(cipher); - } else if (requireServerCertificateMessage && cipher.requiresServerCertificateMessage()) { - filteredCiphers.add(cipher); - } - } - dtlsConfigurationBuilder.set(DtlsConfig.DTLS_CIPHER_SUITES, filteredCiphers); - } -} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java index 45c94ef57c..958c1a237a 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java @@ -17,48 +17,35 @@ import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.coap.CoAP.ResponseCode; +import org.eclipse.californium.core.coap.Message; import org.eclipse.californium.core.network.Exchange; -import org.eclipse.californium.core.network.Exchange.Origin; import org.eclipse.californium.core.server.resources.CoapExchange; -import org.eclipse.leshan.client.engine.RegistrationEngine; +import org.eclipse.leshan.client.californium.endpoint.ServerIdentityExtractor; import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.californium.LwM2mCoapResource; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; +import org.eclipse.leshan.core.request.Identity; /** * A Common {@link CoapResource} used to handle LWM2M request with some specific method for LWM2M client. */ public class LwM2mClientCoapResource extends LwM2mCoapResource { - protected final CaliforniumEndpointsManager endpointsManager; - protected final RegistrationEngine registrationEngine; + protected final ServerIdentityExtractor serverIdentityExtractor; - public LwM2mClientCoapResource(String name, RegistrationEngine registrationEngine, - CaliforniumEndpointsManager endpointsManager) { - super(name, null); - this.registrationEngine = registrationEngine; - this.endpointsManager = endpointsManager; - } - - /** - * @return the server identity of a registered or bootstrap server, return null if this identity does match to any - * server for which we are in communication. - */ - protected ServerIdentity getServer(CoapExchange exchange) { - ServerIdentity serverIdentity = extractIdentity(exchange); - if (registrationEngine.isAllowedToCommunicate(serverIdentity)) { - return serverIdentity; - } else { - return null; - } + public LwM2mClientCoapResource(String name, IdentityHandlerProvider identityHandlerProvider, + ServerIdentityExtractor identityExtractor) { + super(name, identityHandlerProvider); + this.serverIdentityExtractor = identityExtractor; } /** * Extract the {@link ServerIdentity} for this exchange. If there is no corresponding server currently in * communication with this client. Answer with an {@link ResponseCode#INTERNAL_SERVER_ERROR}. */ - protected ServerIdentity getServerOrRejectRequest(CoapExchange exchange) { + protected ServerIdentity getServerOrRejectRequest(CoapExchange exchange, Message receivedMessage) { // search if we are in communication with this server. - ServerIdentity server = getServer(exchange); + ServerIdentity server = extractIdentity(exchange.advanced(), receivedMessage); if (server != null) return server; @@ -73,9 +60,10 @@ protected ServerIdentity getServerOrRejectRequest(CoapExchange exchange) { * @return The corresponding Leshan {@link ServerIdentity}. * @throws IllegalStateException if we are not able to extract {@link ServerIdentity}. */ - protected ServerIdentity extractIdentity(CoapExchange exchange) { - return endpointsManager.getServerIdentity(exchange.advanced().getEndpoint(), exchange.getSourceSocketAddress(), - exchange.advanced().getOrigin() == Origin.REMOTE ? exchange.advanced().getRequest().getSourceContext() - : exchange.advanced().getRequest().getDestinationContext()); + protected ServerIdentity extractIdentity(Exchange exchange, Message receivedMessage) { + Identity foreignPeerIdentity = getForeignPeerIdentity(exchange, receivedMessage); + if (foreignPeerIdentity == null) + return null; + return serverIdentityExtractor.extractIdentity(exchange, foreignPeerIdentity); } } diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/ObserveCompositeRelationFilter.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/ObserveCompositeRelationFilter.java index 1bcca9d75b..516491da29 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/ObserveCompositeRelationFilter.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/ObserveCompositeRelationFilter.java @@ -28,7 +28,7 @@ /** * An {@link ObserveRelationFilter} which select {@link ObserveRelation} based on one of resource URIs. */ -class ObserveCompositeRelationFilter implements ObserveRelationFilter { +public class ObserveCompositeRelationFilter implements ObserveRelationFilter { private final List paths; diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/RootResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/RootResource.java index a57de817a3..02b60fdbed 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/RootResource.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/RootResource.java @@ -30,17 +30,14 @@ import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.server.resources.CoapExchange; -import org.eclipse.leshan.client.bootstrap.BootstrapHandler; -import org.eclipse.leshan.client.engine.RegistrationEngine; -import org.eclipse.leshan.client.resource.LwM2mRootEnabler; -import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter; +import org.eclipse.leshan.client.californium.endpoint.ServerIdentityExtractor; +import org.eclipse.leshan.client.endpoint.ClientEndpointToolbox; +import org.eclipse.leshan.client.request.DownlinkRequestReceiver; import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.californium.ObserveUtil; -import org.eclipse.leshan.core.link.LinkSerializer; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.node.codec.LwM2mDecoder; -import org.eclipse.leshan.core.node.codec.LwM2mEncoder; import org.eclipse.leshan.core.request.BootstrapDeleteRequest; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; import org.eclipse.leshan.core.request.ContentFormat; @@ -59,46 +56,36 @@ */ public class RootResource extends LwM2mClientCoapResource { - protected CoapServer coapServer; - protected BootstrapHandler bootstrapHandler; - protected LwM2mRootEnabler rootEnabler; - protected LwM2mEncoder encoder; - protected LwM2mDecoder decoder; - protected LinkSerializer linkSerializer; - - public RootResource(RegistrationEngine registrationEngine, CaliforniumEndpointsManager endpointsManager, - BootstrapHandler bootstrapHandler, CoapServer coapServer, LwM2mRootEnabler rootEnabler, - LwM2mEncoder encoder, LwM2mDecoder decoder, LinkSerializer linkSerializer) { - super("", registrationEngine, endpointsManager); - this.bootstrapHandler = bootstrapHandler; + protected DownlinkRequestReceiver requestReceiver; + protected ClientEndpointToolbox toolbox; + + public RootResource(IdentityHandlerProvider identityHandlerProvider, + ServerIdentityExtractor serverIdentityExtractor, CoapServer coapServer, + DownlinkRequestReceiver requestReceiver, ClientEndpointToolbox toolbox) { + super("", identityHandlerProvider, serverIdentityExtractor); setVisible(false); setObservable(true); - this.coapServer = coapServer; - this.rootEnabler = rootEnabler; - this.encoder = encoder; - this.decoder = decoder; - this.linkSerializer = linkSerializer; - - addListeners(); + this.requestReceiver = requestReceiver; + this.toolbox = toolbox; } @Override public void handleGET(CoapExchange exchange) { - ServerIdentity identity = getServerOrRejectRequest(exchange); + // Manage Bootstrap Discover Request + Request coapRequest = exchange.advanced().getRequest(); + ServerIdentity identity = getServerOrRejectRequest(exchange, coapRequest); if (identity == null) return; String URI = exchange.getRequestOptions().getUriPathString(); - // Manage Bootstrap Discover Request - Request coapRequest = exchange.advanced().getRequest(); - BootstrapDiscoverResponse response = bootstrapHandler.discover(identity, - new BootstrapDiscoverRequest(URI, coapRequest)); + BootstrapDiscoverResponse response = requestReceiver + .requestReceived(identity, new BootstrapDiscoverRequest(URI, coapRequest)).getResponse(); if (response.getCode().isError()) { exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { exchange.respond(toCoapResponseCode(response.getCode()), - linkSerializer.serializeCoreLinkFormat(response.getObjectLinks()), + toolbox.getLinkSerializer().serializeCoreLinkFormat(response.getObjectLinks()), MediaTypeRegistry.APPLICATION_LINK_FORMAT); } return; @@ -106,18 +93,17 @@ public void handleGET(CoapExchange exchange) { @Override public void handleFETCH(CoapExchange exchange) { - ServerIdentity identity = getServerOrRejectRequest(exchange); + Request coapRequest = exchange.advanced().getRequest(); + ServerIdentity identity = getServerOrRejectRequest(exchange, coapRequest); if (identity == null) return; - Request coapRequest = exchange.advanced().getRequest(); - // Handle content format for the response ContentFormat responseContentFormat = ContentFormat.SENML_CBOR; // use CBOR as default if (exchange.getRequestOptions().hasAccept()) { // If an request ask for a specific content format, use it (if we support it) responseContentFormat = ContentFormat.fromCode(exchange.getRequestOptions().getAccept()); - if (!encoder.isSupported(responseContentFormat)) { + if (!toolbox.getEncoder().isSupported(responseContentFormat)) { exchange.respond(ResponseCode.NOT_ACCEPTABLE); return; } @@ -129,13 +115,13 @@ public void handleFETCH(CoapExchange exchange) { return; } ContentFormat requestContentFormat = ContentFormat.fromCode(exchange.getRequestOptions().getContentFormat()); - List paths = decoder.decodePaths(coapRequest.getPayload(), requestContentFormat); + List paths = toolbox.getDecoder().decodePaths(coapRequest.getPayload(), requestContentFormat); if (exchange.getRequestOptions().hasObserve()) { // Manage Observe Composite request ObserveCompositeRequest observeRequest = new ObserveCompositeRequest(requestContentFormat, responseContentFormat, paths, coapRequest); - ObserveCompositeResponse response = rootEnabler.observe(identity, observeRequest); + ObserveCompositeResponse response = requestReceiver.requestReceived(identity, observeRequest).getResponse(); updateUserContextWithPaths(coapRequest, paths); @@ -143,22 +129,24 @@ public void handleFETCH(CoapExchange exchange) { exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); return; } else { - exchange.respond(toCoapResponseCode(response.getCode()), - encoder.encodeNodes(response.getContent(), responseContentFormat, rootEnabler.getModel()), + exchange.respond(toCoapResponseCode(response.getCode()), toolbox.getEncoder() + .encodeNodes(response.getContent(), responseContentFormat, toolbox.getModel()), responseContentFormat.getCode()); return; } } else { // Manage Read Composite request - ReadCompositeResponse response = rootEnabler.read(identity, - new ReadCompositeRequest(paths, requestContentFormat, responseContentFormat, coapRequest)); + ReadCompositeResponse response = requestReceiver + .requestReceived(identity, + new ReadCompositeRequest(paths, requestContentFormat, responseContentFormat, coapRequest)) + .getResponse(); if (response.getCode().isError()) { exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { // TODO we could maybe face some race condition if an objectEnabler is removed from LwM2mObjectTree // between rootEnabler.read() and rootEnabler.getModel() - exchange.respond(toCoapResponseCode(response.getCode()), - encoder.encodeNodes(response.getContent(), responseContentFormat, rootEnabler.getModel()), + exchange.respond(toCoapResponseCode(response.getCode()), toolbox.getEncoder() + .encodeNodes(response.getContent(), responseContentFormat, toolbox.getModel()), responseContentFormat.getCode()); } return; @@ -176,25 +164,24 @@ private void updateUserContextWithPaths(Request coapRequest, List pat @Override public void handleIPATCH(CoapExchange exchange) { - ServerIdentity identity = getServerOrRejectRequest(exchange); - if (identity == null) - return; - // Manage Read Composite request Request coapRequest = exchange.advanced().getRequest(); + ServerIdentity identity = getServerOrRejectRequest(exchange, coapRequest); + if (identity == null) + return; // Handle content format ContentFormat contentFormat = ContentFormat.fromCode(exchange.getRequestOptions().getContentFormat()); - if (!decoder.isSupported(contentFormat)) { + if (!toolbox.getDecoder().isSupported(contentFormat)) { exchange.respond(ResponseCode.UNSUPPORTED_CONTENT_FORMAT); return; } - Map nodes = decoder.decodeNodes(coapRequest.getPayload(), contentFormat, null, - rootEnabler.getModel()); + Map nodes = toolbox.getDecoder().decodeNodes(coapRequest.getPayload(), contentFormat, + null, toolbox.getModel()); - WriteCompositeResponse response = rootEnabler.write(identity, - new WriteCompositeRequest(contentFormat, nodes, coapRequest)); + WriteCompositeResponse response = requestReceiver + .requestReceived(identity, new WriteCompositeRequest(contentFormat, nodes, coapRequest)).getResponse(); if (response.getCode().isError()) { exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { @@ -211,23 +198,13 @@ public void handleDELETE(CoapExchange exchange) { return; } - ServerIdentity identity = getServerOrRejectRequest(exchange); + Request coapRequest = exchange.advanced().getRequest(); + ServerIdentity identity = getServerOrRejectRequest(exchange, coapRequest); if (identity == null) return; - Request coapRequest = exchange.advanced().getRequest(); - BootstrapDeleteResponse response = bootstrapHandler.delete(identity, - new BootstrapDeleteRequest(URI, coapRequest)); + BootstrapDeleteResponse response = requestReceiver + .requestReceived(identity, new BootstrapDeleteRequest(URI, coapRequest)).getResponse(); exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } - - private void addListeners() { - rootEnabler.addListener(new ObjectsListenerAdapter() { - - @Override - public void resourceChanged(LwM2mPath... paths) { - changed(new ObserveCompositeRelationFilter(paths)); - } - }); - } } diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/BootstrapResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/BootstrapResource.java index 33961d9eaa..bfa967ef33 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/BootstrapResource.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/BootstrapResource.java @@ -24,11 +24,11 @@ import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; -import org.eclipse.leshan.client.bootstrap.BootstrapHandler; -import org.eclipse.leshan.client.californium.CaliforniumEndpointsManager; import org.eclipse.leshan.client.californium.LwM2mClientCoapResource; -import org.eclipse.leshan.client.engine.RegistrationEngine; +import org.eclipse.leshan.client.californium.endpoint.ServerIdentityExtractor; +import org.eclipse.leshan.client.request.DownlinkRequestReceiver; import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; import org.eclipse.leshan.core.request.BootstrapFinishRequest; import org.eclipse.leshan.core.response.BootstrapFinishResponse; import org.eclipse.leshan.core.response.SendableResponse; @@ -38,26 +38,26 @@ */ public class BootstrapResource extends LwM2mClientCoapResource { - protected BootstrapHandler bootstrapHandler; + protected DownlinkRequestReceiver requestReceiver; - public BootstrapResource(RegistrationEngine registrationEngine, CaliforniumEndpointsManager endpointsManager, - BootstrapHandler bootstrapHandler) { - super("bs", registrationEngine, endpointsManager); - this.bootstrapHandler = bootstrapHandler; + public BootstrapResource(IdentityHandlerProvider identityHandlerProvider, + ServerIdentityExtractor serverIdentityExtractor, DownlinkRequestReceiver requestReceiver) { + super("bs", identityHandlerProvider, serverIdentityExtractor); + this.requestReceiver = requestReceiver; this.setVisible(false); } @Override public void handlePOST(CoapExchange exchange) { // Handle bootstrap request - ServerIdentity identity = getServerOrRejectRequest(exchange); + Request coapRequest = exchange.advanced().getRequest(); + ServerIdentity identity = getServerOrRejectRequest(exchange, coapRequest); if (identity == null) return; // Acknowledge bootstrap finished request exchange.accept(); - Request coapRequest = exchange.advanced().getRequest(); - final SendableResponse sendableResponse = bootstrapHandler.finished(identity, + final SendableResponse sendableResponse = requestReceiver.requestReceived(identity, new BootstrapFinishRequest(coapRequest)); // Create CoAP response diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpoint.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpoint.java new file mode 100644 index 0000000000..ad691b64f7 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpoint.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.eclipse.californium.core.coap.MessageObserver; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.config.CoapConfig; +import org.eclipse.californium.core.network.Endpoint; +import org.eclipse.leshan.client.californium.CaliforniumConnectionController; +import org.eclipse.leshan.client.endpoint.ClientEndpointToolbox; +import org.eclipse.leshan.client.endpoint.LwM2mClientEndpoint; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.californium.AsyncRequestObserver; +import org.eclipse.leshan.core.californium.ExceptionTranslator; +import org.eclipse.leshan.core.californium.SyncRequestObserver; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.request.UplinkRequest; +import org.eclipse.leshan.core.response.ErrorCallback; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.ResponseCallback; +import org.eclipse.leshan.core.util.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CaliforniumClientEndpoint implements LwM2mClientEndpoint { + + private final Logger LOG = LoggerFactory.getLogger(CaliforniumClientEndpoint.class); + + private final Protocol protocol; + private final ScheduledExecutorService executor; + private final Endpoint endpoint; + private final ClientEndpointToolbox toolbox; + private final ClientCoapMessageTranslator translator; + private final IdentityHandler identityHandler; + private final CaliforniumConnectionController connectionController; + private final LwM2mModel model; + private final ExceptionTranslator exceptionTranslator; + + public CaliforniumClientEndpoint(Protocol protocol, Endpoint endpoint, ClientCoapMessageTranslator translator, + ClientEndpointToolbox toolbox, IdentityHandler identityHandler, + CaliforniumConnectionController connectionController, LwM2mModel model, + ExceptionTranslator exceptionTranslator, ScheduledExecutorService executor) { + this.protocol = protocol; + this.translator = translator; + this.toolbox = toolbox; + this.endpoint = endpoint; + this.identityHandler = identityHandler; + this.connectionController = connectionController; + this.model = model; + this.exceptionTranslator = exceptionTranslator; + this.executor = executor; + } + + @Override + public Protocol getProtocol() { + return protocol; + } + + @Override + public URI getURI() { + try { + return new URI(protocol.getUriScheme(), null, getCoapEndpoint().getAddress().getHostString(), + getCoapEndpoint().getAddress().getPort(), null, null, null); + } catch (URISyntaxException e) { + // TODO TL : handle this properly + e.printStackTrace(); + throw new IllegalStateException(e); + } + } + + @Override + public long getMaxCommunicationPeriodFor(long lifetimeInMs) { + // See https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/283 to better understand. + // TODO For DTLS, worst Handshake scenario should be taking into account too. + + int floor = 30000; // value from which we stop to adjust communication period using COAP EXCHANGE LIFETIME. + + // To be sure registration doesn't expired, update request should be send considering all CoAP retransmissions + // and registration lifetime. + // See https://tools.ietf.org/html/rfc7252#section-4.8.2 + long exchange_lifetime = endpoint.getConfig().get(CoapConfig.EXCHANGE_LIFETIME, TimeUnit.MILLISECONDS); + if (lifetimeInMs - exchange_lifetime >= floor) { + return lifetimeInMs - exchange_lifetime; + } else { + LOG.warn("Too small lifetime : we advice to not use a lifetime < (COAP EXCHANGE LIFETIME + 30s)"); + // lifetime value is too short, so we do a compromise and we don't remove COAP EXCHANGE LIFETIME completely + // We distribute the remaining lifetime range [0, exchange_lifetime + floor] on the remaining range + // [1,floor]s. + return lifetimeInMs * (floor - 1000) / (exchange_lifetime + floor) + 1000; + } + } + + @Override + public T send(ServerIdentity server, UplinkRequest lwm2mRequest, long timeoutInMs) + throws InterruptedException { + // Create the CoAP request from LwM2m request + final Request coapRequest = translator.createCoapRequest(server, lwm2mRequest, toolbox, model, identityHandler); + + // Send CoAP request synchronously + SyncRequestObserver syncMessageObserver = new SyncRequestObserver(coapRequest, timeoutInMs, + exceptionTranslator) { + + @Override + public T buildResponse(Response coapResponse) { + // Build LwM2m response + T lwM2mResponse = translator.createLwM2mResponse(server, lwm2mRequest, coapRequest, coapResponse, + toolbox); + return lwM2mResponse; + } + + }; + coapRequest.addMessageObserver(syncMessageObserver); + + // Send CoAP request asynchronously + endpoint.sendRequest(coapRequest); + + // Wait for response, then return it + return syncMessageObserver.waitForResponse(); + } + + @Override + public void send(ServerIdentity server, UplinkRequest lwm2mRequest, + ResponseCallback responseCallback, ErrorCallback errorCallback, long timeoutInMs) { + Validate.notNull(responseCallback); + Validate.notNull(errorCallback); + + // Create the CoAP request from LwM2m request + final Request coapRequest = translator.createCoapRequest(server, lwm2mRequest, toolbox, model, identityHandler); + + // Add CoAP request callback + MessageObserver obs = new AsyncRequestObserver(coapRequest, responseCallback, errorCallback, timeoutInMs, + executor, exceptionTranslator) { + @Override + public T buildResponse(Response coapResponse) { + // Build LwM2m response + T lwM2mResponse = translator.createLwM2mResponse(server, lwm2mRequest, coapRequest, coapResponse, + toolbox); + return lwM2mResponse; + } + }; + coapRequest.addMessageObserver(obs); + + // Send CoAP request asynchronously + endpoint.sendRequest(coapRequest); + + } + + @Override + public void forceReconnection(ServerIdentity server, boolean resume) { + connectionController.forceReconnection(endpoint, server, resume); + } + + public Endpoint getCoapEndpoint() { + return endpoint; + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpointFactory.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpointFactory.java new file mode 100644 index 0000000000..77cbd13622 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpointFactory.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint; + +import java.net.InetAddress; +import java.security.cert.Certificate; +import java.util.List; + +import org.eclipse.californium.core.network.Endpoint; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.leshan.client.californium.CaliforniumConnectionController; +import org.eclipse.leshan.client.endpoint.ClientEndpointToolbox; +import org.eclipse.leshan.client.servers.ServerInfo; +import org.eclipse.leshan.core.californium.ExceptionTranslator; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.endpoint.Protocol; + +public interface CaliforniumClientEndpointFactory { + + Protocol getProtocol(); + + Endpoint createCoapEndpoint(InetAddress clientAddress, Configuration defaultConfiguration, ServerInfo serverInfo, + boolean clientInitiatedOnly, List trustStore, ClientEndpointToolbox toolbox); + + CaliforniumConnectionController createConnectionController(); + + IdentityHandler createIdentityHandler(); + + ExceptionTranslator createExceptionTranslator(); + +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpointsProvider.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpointsProvider.java new file mode 100644 index 0000000000..d86112537d --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/CaliforniumClientEndpointsProvider.java @@ -0,0 +1,378 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.eclipse.californium.core.CoapServer; +import org.eclipse.californium.core.network.Endpoint; +import org.eclipse.californium.core.network.Exchange; +import org.eclipse.californium.core.server.resources.Resource; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.elements.config.Configuration.ModuleDefinitionsProvider; +import org.eclipse.leshan.client.californium.endpoint.coap.CoapClientProtocolProvider; +import org.eclipse.leshan.client.endpoint.ClientEndpointToolbox; +import org.eclipse.leshan.client.endpoint.LwM2mClientEndpoint; +import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider; +import org.eclipse.leshan.client.request.DownlinkRequestReceiver; +import org.eclipse.leshan.client.resource.LwM2mObjectTree; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.client.servers.ServerIdentity.Role; +import org.eclipse.leshan.client.servers.ServerInfo; +import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.oscore.OscoreIdentity; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.util.NamedThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CaliforniumClientEndpointsProvider implements LwM2mClientEndpointsProvider { + + // TODO TL : provide a COAP/Californium API ? like previous LeshanClient.coapAPI() + + private final Logger LOG = LoggerFactory.getLogger(CaliforniumClientEndpointsProvider.class); + + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, + new NamedThreadFactory("Leshan Async Request timeout")); + + protected boolean started = false; + + private final Configuration serverConfig; + private final List endpointsFactory; + private final ClientCoapMessageTranslator messagetranslator = new ClientCoapMessageTranslator(); + private final ServerIdentityExtractor identityExtrator; + private final IdentityHandlerProvider identityHandlerProvider; + private LwM2mObjectTree objectTree; + + private final InetAddress clientAddress; + + // we support only 1 endpoint at a time by + private ServerIdentity currentServer; + private CaliforniumClientEndpoint endpoint; + private CoapServer coapServer; + + public CaliforniumClientEndpointsProvider() { + this(new Builder().generateDefaultValue()); + } + + protected CaliforniumClientEndpointsProvider(Builder builder) { + this.serverConfig = builder.configuration; + this.endpointsFactory = builder.endpointsFactory; + this.clientAddress = builder.clientAddress; + + // create identity handler provider + identityHandlerProvider = new IdentityHandlerProvider(); + + // create identity extractor + identityExtrator = new ServerIdentityExtractor() { + + @Override + public ServerIdentity extractIdentity(Exchange exchange, Identity foreignPeerIdentity) { + // TODO support multi server + Endpoint currentCoapEndpoint = endpoint.getCoapEndpoint(); + + // get coap endpoint used for this exchanged : + Endpoint coapEndpoint = exchange.getEndpoint(); + + // knowing used CoAP endpoint we should be able to know the server identity because : + // - we create 1 CoAP endpoint by server. + // - the lower layer should ensure that only 1 server with expected credential is able to talk. + // (see https://github.com/eclipse/leshan/issues/992 for more details) + if (coapEndpoint != null && coapEndpoint.equals(currentCoapEndpoint) + && currentCoapEndpoint.isStarted()) { + // For UDP (not secure) endpoint we also check socket address as anybody send data to this kind of + // endpoint. + if (endpoint.getProtocol().equals(Protocol.COAP) && !currentServer.getIdentity().getPeerAddress() + .equals(foreignPeerIdentity.getPeerAddress())) { + return null; + } + // For OSCORE, be sure OSCORE is used. + if (currentServer.getIdentity().isOSCORE()) { + if (!foreignPeerIdentity.isOSCORE() // + // we also check OscoreIdentity but this is probably not useful + // because we are using static OSCOREstore which holds only 1 OscoreParameter, + // so if the request was successfully decrypted and OSCORE is used, this MUST be the + // right + // server. + || !foreignPeerIdentity.getOscoreIdentity() + .equals(currentServer.getIdentity().getOscoreIdentity())) { + return null; + } + } + return currentServer; + } + return null; + } + }; + } + + @Override + public void init(LwM2mObjectTree objectTree, DownlinkRequestReceiver requestReceiver, + ClientEndpointToolbox toolbox) { + this.objectTree = objectTree; + + // create coap server + coapServer = new CoapServer(serverConfig) { + @Override + protected Resource createRoot() { + return messagetranslator.createRootResource(coapServer, identityHandlerProvider, identityExtrator, + requestReceiver, toolbox, objectTree); + } + }; + + // create resources + List resources = messagetranslator.createResources(coapServer, identityHandlerProvider, + identityExtrator, requestReceiver, toolbox, objectTree); + coapServer.add(resources.toArray(new Resource[resources.size()])); + } + + @Override + public ServerIdentity createEndpoint(ServerInfo serverInfo, boolean clientInitiatedOnly, + List trustStore, ClientEndpointToolbox toolbox) { + + // create endpoints + for (CaliforniumClientEndpointFactory endpointFactory : endpointsFactory) { + + if (endpointFactory.getProtocol().getUriScheme().equals(serverInfo.getFullUri().getScheme())) { + // create Californium endpoint + Endpoint coapEndpoint = endpointFactory.createCoapEndpoint(clientAddress, serverConfig, serverInfo, + clientInitiatedOnly, trustStore, toolbox); + + if (coapEndpoint != null) { + // create identity handler and add it to provider + final IdentityHandler identityHandler = endpointFactory.createIdentityHandler(); + identityHandlerProvider.addIdentityHandler(coapEndpoint, identityHandler); + + // create LWM2M endpoint + endpoint = new CaliforniumClientEndpoint(endpointFactory.getProtocol(), coapEndpoint, + messagetranslator, toolbox, identityHandler, endpointFactory.createConnectionController(), + objectTree.getModel(), endpointFactory.createExceptionTranslator(), executor); + + // add Californium endpoint to coap server + coapServer.addEndpoint(coapEndpoint); + currentServer = extractIdentity(serverInfo); + + if (started) { + coapServer.start(); + try { + coapEndpoint.start(); + LOG.info("New endpoint created for server {} at {}", currentServer.getUri(), + coapEndpoint.getUri()); + } catch (IOException e) { + throw new RuntimeException("Unable to start endpoint", e); + } + } + + return currentServer; + } + } + } + return null; + } + + private ServerIdentity extractIdentity(ServerInfo serverInfo) { + Identity serverIdentity; + if (serverInfo.isSecure()) { + // Support PSK + if (serverInfo.secureMode == SecurityMode.PSK) { + serverIdentity = Identity.psk(serverInfo.getAddress(), serverInfo.pskId); + } else if (serverInfo.secureMode == SecurityMode.RPK) { + serverIdentity = Identity.rpk(serverInfo.getAddress(), serverInfo.serverPublicKey); + } else if (serverInfo.secureMode == SecurityMode.X509) { + // TODO We set CN with '*' as we are not able to know the CN for some certificate usage and so this is + // not used anymore to identify a server with x509. + // See : https://github.com/eclipse/leshan/issues/992 + serverIdentity = Identity.x509(serverInfo.getAddress(), "*"); + } else { + throw new RuntimeException("Unable to create connector : unsupported security mode"); + } + } else if (serverInfo.useOscore) { + // Build server identity for OSCORE + serverIdentity = Identity.oscoreOnly(serverInfo.getAddress(), + new OscoreIdentity(serverInfo.oscoreSetting.getRecipientId())); + } else { + serverIdentity = Identity.unsecure(serverInfo.getAddress()); + } + + if (serverInfo.bootstrap) { + return new ServerIdentity(serverIdentity, serverInfo.serverId, Role.LWM2M_BOOTSTRAP_SERVER, + serverInfo.serverUri); + } else { + return new ServerIdentity(serverIdentity, serverInfo.serverId, serverInfo.serverUri); + } + } + + @Override + public Collection createEndpoints(Collection serverInfo, + boolean clientInitiatedOnly, List trustStore, ClientEndpointToolbox toolbox) { + // TODO TL : need to be implemented or removed ? + return null; + } + + @Override + public void destroyEndpoints() { + identityHandlerProvider.clear(); + for (Endpoint endpoint : coapServer.getEndpoints()) { + coapServer.getEndpoints().remove(endpoint); + endpoint.destroy(); + } + } + + @Override + public LwM2mClientEndpoint getEndpoint(ServerIdentity server) { + if (currentServer.equals(server)) { + return endpoint; + } + return null; + } + + @Override + public List getEndpoints() { + return Arrays.asList(endpoint); + } + + @Override + public synchronized void start() { + if (started) + return; + started = true; + + // we don't have any endpoint so nothing to start + if (coapServer.getEndpoints().isEmpty()) + return; + + coapServer.start(); + } + + @Override + public synchronized void stop() { + if (!started) + return; + started = false; + + // If we have no endpoint this means that we never start coap server + if (coapServer.getEndpoints().isEmpty()) + return; + + coapServer.stop(); + } + + @Override + public synchronized void destroy() { + if (started) + started = false; + + executor.shutdownNow(); + try { + executor.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOG.warn("Destroying RequestSender was interrupted.", e); + } + + coapServer.destroy(); + } + + public static class Builder { + + private final List protocolProviders; + private Configuration configuration; + private final List endpointsFactory; + private InetAddress clientAddress; + + public Builder(ClientProtocolProvider... protocolProviders) { + // TODO TL : handle duplicate ? + this.protocolProviders = new ArrayList(); + if (protocolProviders.length == 0) { + this.protocolProviders.add(new CoapClientProtocolProvider()); + } else { + this.protocolProviders.addAll(Arrays.asList(protocolProviders)); + } + + this.endpointsFactory = new ArrayList<>(); + } + + /** + * create Default CoAP Server Configuration. + */ + public Configuration createDefaultConfiguration() { + // Get all Californium modules + Set moduleProviders = new HashSet<>(); + for (ClientProtocolProvider protocolProvider : protocolProviders) { + moduleProviders.addAll(protocolProvider.getModuleDefinitionsProviders()); + } + + // create Californium Configuration + Configuration configuration = new Configuration( + moduleProviders.toArray(new ModuleDefinitionsProvider[moduleProviders.size()])); + + // apply default value + for (ClientProtocolProvider protocolProvider : protocolProviders) { + protocolProvider.applyDefaultValue(configuration); + } + + return configuration; + } + + /** + * @param configuration the @{link Configuration} used by the {@link CoapServer}. + */ + public Builder setConfiguration(Configuration configuration) { + this.configuration = configuration; + return this; + } + + public Builder setClientAddress(InetAddress addr) { + clientAddress = addr; + return this; + } + + protected Builder generateDefaultValue() { + if (configuration == null) { + configuration = createDefaultConfiguration(); + } + if (clientAddress == null) { + clientAddress = new InetSocketAddress(0).getAddress(); + } + if (endpointsFactory.isEmpty()) { + for (ClientProtocolProvider protocolProvider : protocolProviders) { + // TODO TL : handle duplicates + endpointsFactory.add(protocolProvider.createDefaultEndpointFactory()); + } + } + return this; + } + + public CaliforniumClientEndpointsProvider build() { + generateDefaultValue(); + return new CaliforniumClientEndpointsProvider(this); + } + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ClientCoapMessageTranslator.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ClientCoapMessageTranslator.java new file mode 100644 index 0000000000..b59340acc4 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ClientCoapMessageTranslator.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.californium.core.CoapResource; +import org.eclipse.californium.core.CoapServer; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.server.resources.Resource; +import org.eclipse.leshan.client.californium.ObserveCompositeRelationFilter; +import org.eclipse.leshan.client.californium.RootResource; +import org.eclipse.leshan.client.californium.bootstrap.BootstrapResource; +import org.eclipse.leshan.client.californium.object.ObjectResource; +import org.eclipse.leshan.client.californium.request.CoapRequestBuilder; +import org.eclipse.leshan.client.californium.request.LwM2mResponseBuilder; +import org.eclipse.leshan.client.endpoint.ClientEndpointToolbox; +import org.eclipse.leshan.client.request.DownlinkRequestReceiver; +import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; +import org.eclipse.leshan.client.resource.LwM2mObjectTree; +import org.eclipse.leshan.client.resource.listener.ObjectListener; +import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.request.UplinkRequest; +import org.eclipse.leshan.core.response.LwM2mResponse; + +public class ClientCoapMessageTranslator { + + public Request createCoapRequest(ServerIdentity serverIdentity, UplinkRequest lwm2mRequest, + ClientEndpointToolbox toolbox, LwM2mModel model, IdentityHandler identityHandler) { + + // create CoAP Request + CoapRequestBuilder builder = new CoapRequestBuilder(serverIdentity.getIdentity(), toolbox.getEncoder(), model, + toolbox.getLinkSerializer(), identityHandler); + lwm2mRequest.accept(builder); + return builder.getRequest(); + } + + public T createLwM2mResponse(ServerIdentity serverIdentity, UplinkRequest lwm2mRequest, + Request coapRequest, Response coapResponse, ClientEndpointToolbox toolbox) { + + // create LWM2M Response + LwM2mResponseBuilder builder = new LwM2mResponseBuilder(coapResponse); + lwm2mRequest.accept(builder); + return builder.getResponse(); + } + + public Resource createRootResource(CoapServer coapServer, IdentityHandlerProvider identityHandlerProvider, + ServerIdentityExtractor identityExtrator, DownlinkRequestReceiver requestReceiver, + ClientEndpointToolbox toolbox, LwM2mObjectTree objectTree) { + // Use to handle Delete on "/" + final RootResource rootResource = new RootResource(identityHandlerProvider, identityExtrator, coapServer, + requestReceiver, toolbox); + objectTree.addListener(new ObjectsListenerAdapter() { + @Override + public void resourceChanged(LwM2mPath... paths) { + rootResource.changed(new ObserveCompositeRelationFilter(paths)); + } + }); + return rootResource; + } + + public List createResources(CoapServer coapServer, IdentityHandlerProvider identityHandlerProvider, + ServerIdentityExtractor identityExtrator, DownlinkRequestReceiver requestReceiver, + ClientEndpointToolbox toolbox, LwM2mObjectTree objectTree) { + ArrayList resources = new ArrayList<>(); + + // create bootstrap resource + resources.add(new BootstrapResource(identityHandlerProvider, identityExtrator, requestReceiver)); + + // create object resources + for (LwM2mObjectEnabler enabler : objectTree.getObjectEnablers().values()) { + resources.add( + createObjectResource(enabler, identityHandlerProvider, identityExtrator, requestReceiver, toolbox)); + } + + // link resource to object tree + objectTree.addListener(new ObjectsListenerAdapter() { + @Override + public void objectAdded(LwM2mObjectEnabler object) { + CoapResource clientObject = createObjectResource(object, identityHandlerProvider, identityExtrator, + requestReceiver, toolbox); + coapServer.add(clientObject); + } + + @Override + public void objectRemoved(LwM2mObjectEnabler object) { + Resource resource = coapServer.getRoot().getChild(Integer.toString(object.getId())); + if (resource instanceof ObjectListener) { + object.removeListener((ObjectListener) (resource)); + } + coapServer.remove(resource); + } + }); + return resources; + } + + public CoapResource createObjectResource(LwM2mObjectEnabler objectEnabler, + IdentityHandlerProvider identityHandlerProvider, ServerIdentityExtractor identityExtractor, + DownlinkRequestReceiver requestReceiver, ClientEndpointToolbox toolbox) { + ObjectResource objectResource = new ObjectResource(objectEnabler.getId(), identityHandlerProvider, + identityExtractor, requestReceiver, toolbox); + objectEnabler.addListener(objectResource); + return objectResource; + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ClientProtocolProvider.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ClientProtocolProvider.java new file mode 100644 index 0000000000..8fc5805669 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ClientProtocolProvider.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint; + +import java.util.List; + +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.elements.config.Configuration.ModuleDefinitionsProvider; +import org.eclipse.leshan.core.endpoint.Protocol; + +public interface ClientProtocolProvider { + + Protocol getProtocol(); + + List getModuleDefinitionsProviders(); + + void applyDefaultValue(Configuration configuration); + + CaliforniumClientEndpointFactory createDefaultEndpointFactory(); +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ServerIdentityExtractor.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ServerIdentityExtractor.java new file mode 100644 index 0000000000..500f779536 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/ServerIdentityExtractor.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint; + +import org.eclipse.californium.core.network.Exchange; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.request.Identity; + +public interface ServerIdentityExtractor { + ServerIdentity extractIdentity(Exchange exchange, Identity foreignPeerIdentity); +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapClientEndpointFactory.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapClientEndpointFactory.java new file mode 100644 index 0000000000..aa1f2851b9 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapClientEndpointFactory.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint.coap; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.security.cert.Certificate; +import java.util.List; + +import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.core.network.CoapEndpoint.Builder; +import org.eclipse.californium.core.network.Endpoint; +import org.eclipse.californium.elements.Connector; +import org.eclipse.californium.elements.UDPConnector; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.leshan.client.californium.CaliforniumConnectionController; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointFactory; +import org.eclipse.leshan.client.endpoint.ClientEndpointToolbox; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.client.servers.ServerInfo; +import org.eclipse.leshan.core.californium.DefaultExceptionTranslator; +import org.eclipse.leshan.core.californium.ExceptionTranslator; +import org.eclipse.leshan.core.californium.identity.DefaultCoapIdentityHandler; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.endpoint.EndpointUriUtil; +import org.eclipse.leshan.core.endpoint.Protocol; + +public class CoapClientEndpointFactory implements CaliforniumClientEndpointFactory { + + protected final String loggingTagPrefix; + + public CoapClientEndpointFactory() { + this("LWM2M Client"); + } + + public CoapClientEndpointFactory(String loggingTagPrefix) { + this.loggingTagPrefix = loggingTagPrefix; + } + + @Override + public Protocol getProtocol() { + return Protocol.COAP; + } + + protected String getLoggingTag(URI uri) { + if (loggingTagPrefix != null) { + return String.format("[%s-%s]", loggingTagPrefix, uri); + } else { + return String.format("[%s-%s]", uri); + } + } + + @Override + public Endpoint createCoapEndpoint(InetAddress clientAddress, Configuration defaultConfiguration, + ServerInfo serverInfo, boolean clientInitiatedOnly, List trustStore, + ClientEndpointToolbox toolbox) { + return createEndpointBuilder(new InetSocketAddress(clientAddress, 0), serverInfo, defaultConfiguration).build(); + } + + /** + * This method is intended to be overridden. + * + * @param address the IP address and port, if null the connector is bound to an ephemeral port on the wildcard + * address. + * @param coapConfig the CoAP config used to create this endpoint. + * @return the {@link Builder} used for unsecured communication. + */ + protected CoapEndpoint.Builder createEndpointBuilder(InetSocketAddress address, ServerInfo serverInfo, + Configuration coapConfig) { + CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); + builder.setConnector(createConnector(address, coapConfig)); + builder.setConfiguration(coapConfig); + builder.setLoggingTag(getLoggingTag(EndpointUriUtil.createUri(getProtocol().getUriScheme(), address))); + return builder; + } + + /** + * By default create an {@link UDPConnector}. + *

+ * This method is intended to be overridden. + * + * @param address the IP address and port, if null the connector is bound to an ephemeral port on the wildcard + * address + * @param coapConfig the Configuration + * @return the {@link Connector} used for unsecured {@link CoapEndpoint} + */ + protected Connector createConnector(InetSocketAddress address, Configuration coapConfig) { + return new UDPConnector(address, coapConfig); + } + + @Override + public IdentityHandler createIdentityHandler() { + return new DefaultCoapIdentityHandler(); + }; + + @Override + public CaliforniumConnectionController createConnectionController() { + return new CaliforniumConnectionController() { + @Override + public void forceReconnection(Endpoint endpoint, ServerIdentity identity, boolean resume) { + // no connection in coap, so nothing to do; + } + }; + } + + @Override + public ExceptionTranslator createExceptionTranslator() { + return new DefaultExceptionTranslator(); + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapClientProtocolProvider.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapClientProtocolProvider.java new file mode 100644 index 0000000000..6c848794fc --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapClientProtocolProvider.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint.coap; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.californium.core.config.CoapConfig; +import org.eclipse.californium.core.config.CoapConfig.TrackerMode; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.elements.config.Configuration.ModuleDefinitionsProvider; +import org.eclipse.californium.elements.config.SystemConfig; +import org.eclipse.californium.elements.config.UdpConfig; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointFactory; +import org.eclipse.leshan.client.californium.endpoint.ClientProtocolProvider; +import org.eclipse.leshan.core.endpoint.Protocol; + +public class CoapClientProtocolProvider implements ClientProtocolProvider { + + @Override + public Protocol getProtocol() { + return Protocol.COAP; + } + + @Override + public void applyDefaultValue(Configuration configuration) { + configuration.set(CoapConfig.MID_TRACKER, TrackerMode.NULL); + configuration.set(CoapConfig.MAX_ACTIVE_PEERS, 10); + configuration.set(CoapConfig.PROTOCOL_STAGE_THREAD_COUNT, 1); + } + + @Override + public List getModuleDefinitionsProviders() { + return Arrays.asList(SystemConfig.DEFINITIONS, CoapConfig.DEFINITIONS, UdpConfig.DEFINITIONS); + } + + @Override + public CaliforniumClientEndpointFactory createDefaultEndpointFactory() { + return new CoapClientEndpointFactory(); + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapOscoreClientEndpointFactory.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapOscoreClientEndpointFactory.java new file mode 100644 index 0000000000..57928441c6 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapOscoreClientEndpointFactory.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint.coap; + +import java.net.InetSocketAddress; +import java.security.Principal; + +import org.eclipse.californium.core.coap.Message; +import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.core.network.CoapEndpoint.Builder; +import org.eclipse.californium.core.network.Endpoint; +import org.eclipse.californium.cose.AlgorithmID; +import org.eclipse.californium.cose.CoseException; +import org.eclipse.californium.elements.AddressEndpointContext; +import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.oscore.OSCoreCoapStackFactory; +import org.eclipse.californium.oscore.OSCoreEndpointContextInfo; +import org.eclipse.leshan.client.californium.CaliforniumConnectionController; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.client.servers.ServerInfo; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.californium.oscore.cf.InMemoryOscoreContextDB; +import org.eclipse.leshan.core.californium.oscore.cf.OscoreParameters; +import org.eclipse.leshan.core.californium.oscore.cf.StaticOscoreStore; +import org.eclipse.leshan.core.oscore.InvalidOscoreSettingException; +import org.eclipse.leshan.core.oscore.OscoreIdentity; +import org.eclipse.leshan.core.oscore.OscoreValidator; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.util.Hex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.upokecenter.cbor.CBORObject; + +public class CoapOscoreClientEndpointFactory extends CoapClientEndpointFactory { + + private static final Logger LOG = LoggerFactory.getLogger(CoapOscoreClientEndpointFactory.class); + + /** + * This method is intended to be overridden. + * + * @param address the IP address and port, if null the connector is bound to an ephemeral port on the wildcard + * address. + * @param coapConfig the CoAP config used to create this endpoint. + * @return the {@link Builder} used for unsecured communication. + */ + @Override + protected CoapEndpoint.Builder createEndpointBuilder(InetSocketAddress address, ServerInfo serverInfo, + Configuration coapConfig) { + CoapEndpoint.Builder builder = super.createEndpointBuilder(address, serverInfo, coapConfig); + + // handle oscore + if (serverInfo.useOscore) { + // oscore only mode + LOG.warn("Experimental OSCORE support is used for {}", serverInfo.getFullUri().toASCIIString()); + + try { + new OscoreValidator().validateOscoreSetting(serverInfo.oscoreSetting); + } catch (InvalidOscoreSettingException e) { + throw new RuntimeException(String.format("Unable to create endpoint for %s using OSCORE : Invalid %s.", + serverInfo, serverInfo.oscoreSetting), e); + } + + AlgorithmID hkdfAlg = null; + try { + hkdfAlg = AlgorithmID + .FromCBOR(CBORObject.FromObject(serverInfo.oscoreSetting.getHkdfAlgorithm().getValue())); + } catch (CoseException e) { + throw new RuntimeException(String.format( + "Unable to create endpoint for %s using OSCORE : Unable to decode OSCORE HKDF from %s.", + serverInfo, serverInfo.oscoreSetting), e); + } + AlgorithmID aeadAlg = null; + try { + aeadAlg = AlgorithmID + .FromCBOR(CBORObject.FromObject(serverInfo.oscoreSetting.getAeadAlgorithm().getValue())); + } catch (CoseException e) { + throw new RuntimeException(String.format( + "Unable to create endpoint for %s using OSCORE : Unable to decode OSCORE AEAD from %s.", + serverInfo, serverInfo.oscoreSetting), e); + } + + // TODO OSCORE kind of hack because californium doesn't support an empty byte[] array for salt ? + byte[] masterSalt = serverInfo.oscoreSetting.getMasterSalt().length == 0 ? null + : serverInfo.oscoreSetting.getMasterSalt(); + + OscoreParameters oscoreParameters = new OscoreParameters(serverInfo.oscoreSetting.getSenderId(), + serverInfo.oscoreSetting.getRecipientId(), serverInfo.oscoreSetting.getMasterSecret(), aeadAlg, + hkdfAlg, masterSalt); + + InMemoryOscoreContextDB oscoreCtxDB = new InMemoryOscoreContextDB(new StaticOscoreStore(oscoreParameters)); + builder.setCustomCoapStackArgument(oscoreCtxDB).setCoapStackFactory(new OSCoreCoapStackFactory()); + } + + LOG.warn("Experimental OSCORE feature is enabled."); + + return builder; + } + + @Override + public IdentityHandler createIdentityHandler() { + return new IdentityHandler() { + + @Override + public Identity getIdentity(Message receivedMessage) { + EndpointContext context = receivedMessage.getSourceContext(); + InetSocketAddress peerAddress = context.getPeerAddress(); + Principal senderIdentity = context.getPeerIdentity(); + if (senderIdentity == null) { + // Build identity for OSCORE if it is used + if (context.get(OSCoreEndpointContextInfo.OSCORE_RECIPIENT_ID) != null) { + String recipient = context.get(OSCoreEndpointContextInfo.OSCORE_RECIPIENT_ID); + return Identity.oscoreOnly(peerAddress, + new OscoreIdentity(Hex.decodeHex(recipient.toCharArray()))); + } + return Identity.unsecure(peerAddress); + } else { + return null; + } + } + + @Override + public EndpointContext createEndpointContext(Identity identity, boolean allowConnectionInitiation) { + // TODO OSCORE : should we add properties to endpoint context ? + return new AddressEndpointContext(identity.getPeerAddress()); + } + }; + } + + @Override + public CaliforniumConnectionController createConnectionController() { + return new CaliforniumConnectionController() { + @Override + public void forceReconnection(Endpoint endpoint, ServerIdentity identity, boolean resume) { + // TODO TL : how to force oscore connection ? + } + }; + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapOscoreProtocolProvider.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapOscoreProtocolProvider.java new file mode 100644 index 0000000000..940a3e581f --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coap/CoapOscoreProtocolProvider.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint.coap; + +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointFactory; + +public class CoapOscoreProtocolProvider extends CoapClientProtocolProvider { + + @Override + public CaliforniumClientEndpointFactory createDefaultEndpointFactory() { + return new CoapOscoreClientEndpointFactory(); + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coaps/CoapsClientEndpointFactory.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coaps/CoapsClientEndpointFactory.java new file mode 100644 index 0000000000..2d11be1e3d --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coaps/CoapsClientEndpointFactory.java @@ -0,0 +1,421 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint.coaps; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.security.Principal; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +import org.eclipse.californium.core.coap.Message; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.core.network.CoapEndpoint.Builder; +import org.eclipse.californium.core.network.Endpoint; +import org.eclipse.californium.elements.AddressEndpointContext; +import org.eclipse.californium.elements.Connector; +import org.eclipse.californium.elements.DtlsEndpointContext; +import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.californium.elements.EndpointContextMatcher; +import org.eclipse.californium.elements.MapBasedEndpointContext; +import org.eclipse.californium.elements.MapBasedEndpointContext.Attributes; +import org.eclipse.californium.elements.PrincipalEndpointContextMatcher; +import org.eclipse.californium.elements.auth.PreSharedKeyIdentity; +import org.eclipse.californium.elements.auth.RawPublicKeyIdentity; +import org.eclipse.californium.elements.auth.X509CertPath; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.elements.util.CertPathUtil; +import org.eclipse.californium.scandium.DTLSConnector; +import org.eclipse.californium.scandium.config.DtlsConfig; +import org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.californium.scandium.dtls.DtlsHandshakeTimeoutException; +import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; +import org.eclipse.californium.scandium.dtls.pskstore.AdvancedSinglePskStore; +import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; +import org.eclipse.californium.scandium.dtls.x509.SingleCertificateProvider; +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; +import org.eclipse.leshan.client.californium.CaConstraintCertificateVerifier; +import org.eclipse.leshan.client.californium.CaliforniumConnectionController; +import org.eclipse.leshan.client.californium.DomainIssuerCertificateVerifier; +import org.eclipse.leshan.client.californium.ServiceCertificateConstraintCertificateVerifier; +import org.eclipse.leshan.client.californium.TrustAnchorAssertionCertificateVerifier; +import org.eclipse.leshan.client.californium.endpoint.coap.CoapClientEndpointFactory; +import org.eclipse.leshan.client.endpoint.ClientEndpointToolbox; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.client.servers.ServerInfo; +import org.eclipse.leshan.core.CertificateUsage; +import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.californium.DefaultExceptionTranslator; +import org.eclipse.leshan.core.californium.EndpointContextUtil; +import org.eclipse.leshan.core.californium.ExceptionTranslator; +import org.eclipse.leshan.core.californium.Lwm2mEndpointContextMatcher; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.endpoint.EndpointUriUtil; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.request.exception.TimeoutException; +import org.eclipse.leshan.core.request.exception.TimeoutException.Type; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CoapsClientEndpointFactory extends CoapClientEndpointFactory { + + private static final Logger LOG = LoggerFactory.getLogger(CoapsClientEndpointFactory.class); + + protected final String loggingTagPrefix; + + public CoapsClientEndpointFactory() { + this("LWM2M Client"); + } + + public CoapsClientEndpointFactory(String loggingTagPrefix) { + this.loggingTagPrefix = loggingTagPrefix; + } + + @Override + public Protocol getProtocol() { + return Protocol.COAPS; + } + + @Override + protected String getLoggingTag(URI uri) { + if (loggingTagPrefix != null) { + return String.format("[%s-%s]", loggingTagPrefix, uri); + } else { + return String.format("[%s-%s]", uri); + } + } + + @Override + public Endpoint createCoapEndpoint(InetAddress clientAddress, Configuration defaultConfiguration, + ServerInfo serverInfo, boolean clientInitiatedOnly, List trustStore, + ClientEndpointToolbox toolbox) { + + // create DTLS connector Config + DtlsConnectorConfig.Builder dtlsConfigBuilder = createDtlsConnectorConfigBuilder(defaultConfiguration); + dtlsConfigBuilder = setUpDtlsConfig(new InetSocketAddress(clientAddress, 0), serverInfo, dtlsConfigBuilder, + defaultConfiguration, clientInitiatedOnly, trustStore); + DtlsConnectorConfig dtlsConfig; + try { + dtlsConfig = dtlsConfigBuilder.build(); + } catch (IllegalStateException e) { + LOG.warn("Unable to create DTLS config to create endpont to connect to {}.", serverInfo.getFullUri(), e); + return null; + } + + // create CoAP endpoint + CoapEndpoint endpoint = createEndpointBuilder(dtlsConfig, defaultConfiguration).build(); + + return endpoint; + } + + protected DtlsConnectorConfig.Builder createDtlsConnectorConfigBuilder(Configuration configuration) { + return new DtlsConnectorConfig.Builder(configuration); + } + + protected DtlsConnectorConfig.Builder setUpDtlsConfig(InetSocketAddress addr, ServerInfo serverInfo, + DtlsConnectorConfig.Builder dtlsConfigBuilder, Configuration coapConfig, boolean clientInitiatedOnly, + List trustStore) { + + if (serverInfo.isSecure()) { + DtlsConnectorConfig incompleteConfig = dtlsConfigBuilder.getIncompleteConfig(); + DtlsConnectorConfig.Builder newBuilder = DtlsConnectorConfig.builder(incompleteConfig); + newBuilder.setAddress(addr); + + // Support PSK + if (serverInfo.secureMode == SecurityMode.PSK) { + AdvancedSinglePskStore staticPskStore = new AdvancedSinglePskStore(serverInfo.pskId, serverInfo.pskKey); + newBuilder.setAdvancedPskStore(staticPskStore); + filterCipherSuites(newBuilder, newBuilder.getIncompleteConfig().getSupportedCipherSuites(), true, + false); + } else if (serverInfo.secureMode == SecurityMode.RPK) { + // set identity + SingleCertificateProvider singleCertificateProvider = new SingleCertificateProvider( + serverInfo.privateKey, serverInfo.publicKey); + // we don't want to check Key Pair here, if we do it this should be done in BootstrapConsistencyChecker + singleCertificateProvider.setVerifyKeyPair(false); + newBuilder.setCertificateIdentityProvider(singleCertificateProvider); + // set RPK truststore + final PublicKey expectedKey = serverInfo.serverPublicKey; + NewAdvancedCertificateVerifier rpkVerifier = new StaticNewAdvancedCertificateVerifier.Builder() + .setTrustedRPKs(new RawPublicKeyIdentity(expectedKey)).build(); + newBuilder.setAdvancedCertificateVerifier(rpkVerifier); + filterCipherSuites(newBuilder, incompleteConfig.getSupportedCipherSuites(), false, true); + } else if (serverInfo.secureMode == SecurityMode.X509) { + // set identity + SingleCertificateProvider singleCertificateProvider = new SingleCertificateProvider( + serverInfo.privateKey, new Certificate[] { serverInfo.clientCertificate }); + // we don't want to check Key Pair here, if we do it this should be done in BootstrapConsistencyChecker + singleCertificateProvider.setVerifyKeyPair(false); + newBuilder.setCertificateIdentityProvider(singleCertificateProvider); + + // LWM2M v1.1.1 - 5.2.8.7. Certificate Usage Field + // + // 0: Certificate usage 0 ("CA constraint") + // - trustStore is combination of client's configured trust store and provided certificate in server + // info + // - must do PKIX validation with trustStore to build certPath + // - must check that given certificate is part of certPath + // - validate server name + // + // 1: Certificate usage 1 ("service certificate constraint") + // - trustStore is client's configured trust store + // - must do PKIX validation with trustStore + // - target certificate must match what is provided certificate in server info + // - validate server name + // + // 2: Certificate usage 2 ("trust anchor assertion") + // - trustStore is only the provided certificate in server info + // - must do PKIX validation with trustStore + // - validate server name + // + // 3: Certificate usage 3 ("domain-issued certificate") (default mode if missing) + // - no trustStore used in this mode + // - target certificate must match what is provided certificate in server info + // - validate server name + + CertificateUsage certificateUsage = serverInfo.certificateUsage != null ? serverInfo.certificateUsage + : CertificateUsage.DOMAIN_ISSUER_CERTIFICATE; + + if (certificateUsage == CertificateUsage.CA_CONSTRAINT) { + X509Certificate[] trustedCertificates = null; + if (trustStore != null) { + trustedCertificates = CertPathUtil.toX509CertificatesList(trustStore) + .toArray(new X509Certificate[trustStore.size()]); + } + newBuilder.setAdvancedCertificateVerifier( + new CaConstraintCertificateVerifier(serverInfo.serverCertificate, trustedCertificates)); + } else if (certificateUsage == CertificateUsage.SERVICE_CERTIFICATE_CONSTRAINT) { + X509Certificate[] trustedCertificates = null; + + // - trustStore is client's configured trust store + if (trustStore != null) { + trustedCertificates = CertPathUtil.toX509CertificatesList(trustStore) + .toArray(new X509Certificate[trustStore.size()]); + } + + newBuilder.setAdvancedCertificateVerifier(new ServiceCertificateConstraintCertificateVerifier( + serverInfo.serverCertificate, trustedCertificates)); + } else if (certificateUsage == CertificateUsage.TRUST_ANCHOR_ASSERTION) { + newBuilder.setAdvancedCertificateVerifier(new TrustAnchorAssertionCertificateVerifier( + (X509Certificate) serverInfo.serverCertificate)); + } else if (certificateUsage == CertificateUsage.DOMAIN_ISSUER_CERTIFICATE) { + newBuilder.setAdvancedCertificateVerifier( + new DomainIssuerCertificateVerifier(serverInfo.serverCertificate)); + } + + // TODO We set CN with '*' as we are not able to know the CN for some certificate usage and so this is + // not used anymore to identify a server with x509. + // See : https://github.com/eclipse/leshan/issues/992 + filterCipherSuites(newBuilder, incompleteConfig.getSupportedCipherSuites(), false, true); + } else { + throw new RuntimeException("Unable to create connector : unsupported security mode"); + } + + // Handle DTLS mode + DtlsRole dtlsRole = incompleteConfig.getConfiguration().get(DtlsConfig.DTLS_ROLE); + if (dtlsRole == null) { + if (serverInfo.bootstrap) { + // For bootstrap no need to have DTLS role exchange + // and so we can set DTLS Connection as client only by default. + newBuilder.set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY); + } else if (clientInitiatedOnly) { + // if client initiated only we don't allow connector to work as server role. + newBuilder.set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY); + } else { + newBuilder.set(DtlsConfig.DTLS_ROLE, DtlsRole.BOTH); + } + } + + if (incompleteConfig.getConfiguration().get(DtlsConfig.DTLS_ROLE) == DtlsRole.BOTH) { + // Ensure that BOTH mode can be used or fallback to CLIENT_ONLY + if (serverInfo.secureMode == SecurityMode.X509) { + X509Certificate certificate = (X509Certificate) serverInfo.clientCertificate; + if (CertPathUtil.canBeUsedForAuthentication(certificate, true)) { + if (!CertPathUtil.canBeUsedForAuthentication(certificate, false)) { + newBuilder.set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY); + LOG.warn("Client certificate does not allow Server Authentication usage." + + "\nThis will prevent a LWM2M server to initiate DTLS connection to this client." + + "\nSee : https://github.com/eclipse/leshan/wiki/Server-Failover#about-connections"); + } + } + } + } + return newBuilder; + } + return null; + } + + private void filterCipherSuites(DtlsConnectorConfig.Builder dtlsConfigurationBuilder, List ciphers, + boolean psk, boolean requireServerCertificateMessage) { + if (ciphers == null) + return; + + List filteredCiphers = new ArrayList<>(); + for (CipherSuite cipher : ciphers) { + if (psk && cipher.isPskBased()) { + filteredCiphers.add(cipher); + } else if (requireServerCertificateMessage && cipher.requiresServerCertificateMessage()) { + filteredCiphers.add(cipher); + } + } + dtlsConfigurationBuilder.set(DtlsConfig.DTLS_CIPHER_SUITES, filteredCiphers); + } + + /** + * This method is intended to be overridden. + * + * @param dtlsConfig the DTLS config used to create this endpoint. + * @param coapConfig the CoAP config used to create this endpoint. + * @return the {@link Builder} used for secured communication. + */ + protected CoapEndpoint.Builder createEndpointBuilder(DtlsConnectorConfig dtlsConfig, Configuration coapConfig) { + + CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); + builder.setConnector(createSecuredConnector(dtlsConfig)); + builder.setConfiguration(coapConfig); + builder.setLoggingTag( + getLoggingTag(EndpointUriUtil.createUri(getProtocol().getUriScheme(), dtlsConfig.getAddress()))); + + EndpointContextMatcher securedContextMatcher = createSecuredContextMatcher(); + builder.setEndpointContextMatcher(securedContextMatcher); + + return builder; + } + + /** + * For server {@link Lwm2mEndpointContextMatcher} is created.
+ * For client {@link PrincipalEndpointContextMatcher} is created. + *

+ * This method is intended to be overridden. + * + * @return the {@link EndpointContextMatcher} used for secured communication + */ + protected EndpointContextMatcher createSecuredContextMatcher() { + return new PrincipalEndpointContextMatcher() { + @Override + protected boolean matchPrincipals(Principal requestedPrincipal, Principal availablePrincipal) { + // As we are using 1 connector/endpoint by server at client side, + // and connector strongly limit connection from/to the expected foreign peer, + // we don't need to re-check principal at EndpointContextMatcher level. + return true; + } + }; + } + + /** + * By default create a {@link DTLSConnector}. + *

+ * This method is intended to be overridden. + * + * @param dtlsConfig the DTLS config used to create the Secured Connector. + * @return the {@link Connector} used for unsecured {@link CoapEndpoint} + */ + protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) { + return new DTLSConnector(dtlsConfig); + } + + @Override + public IdentityHandler createIdentityHandler() { + return new IdentityHandler() { + + @Override + public Identity getIdentity(Message receivedMessage) { + EndpointContext context = receivedMessage.getSourceContext(); + InetSocketAddress peerAddress = context.getPeerAddress(); + Principal senderIdentity = context.getPeerIdentity(); + if (senderIdentity != null) { + if (senderIdentity instanceof PreSharedKeyIdentity) { + return Identity.psk(peerAddress, ((PreSharedKeyIdentity) senderIdentity).getIdentity()); + } else if (senderIdentity instanceof RawPublicKeyIdentity) { + PublicKey publicKey = ((RawPublicKeyIdentity) senderIdentity).getKey(); + return Identity.rpk(peerAddress, publicKey); + } else if (senderIdentity instanceof X500Principal || senderIdentity instanceof X509CertPath) { + // Extract common name + String x509CommonName = EndpointContextUtil.extractCN(senderIdentity.getName()); + return Identity.x509(peerAddress, x509CommonName); + } + throw new IllegalStateException( + String.format("Unable to extract sender identity : unexpected type of Principal %s [%s]", + senderIdentity.getClass(), senderIdentity.toString())); + } + return null; + } + + @Override + public EndpointContext createEndpointContext(Identity identity, boolean allowConnectionInitiation) { + Principal peerIdentity = null; + if (identity != null) { + if (identity.isPSK()) { + peerIdentity = new PreSharedKeyIdentity(identity.getPskIdentity()); + } else if (identity.isRPK()) { + peerIdentity = new RawPublicKeyIdentity(identity.getRawPublicKey()); + } else if (identity.isX509()) { + /* simplify distinguished name to CN= part */ + peerIdentity = new X500Principal("CN=" + identity.getX509CommonName()); + } + } + if (peerIdentity != null && allowConnectionInitiation) { + return new MapBasedEndpointContext(identity.getPeerAddress(), peerIdentity, new Attributes() + .add(DtlsEndpointContext.KEY_HANDSHAKE_MODE, DtlsEndpointContext.HANDSHAKE_MODE_AUTO)); + } + return new AddressEndpointContext(identity.getPeerAddress(), peerIdentity); + } + }; + } + + @Override + public CaliforniumConnectionController createConnectionController() { + return new CaliforniumConnectionController() { + @Override + public void forceReconnection(Endpoint endpoint, ServerIdentity server, boolean resume) { + Connector connector = ((CoapEndpoint) endpoint).getConnector(); + if (connector instanceof DTLSConnector) { + if (resume) { + LOG.info("Clear DTLS session for resumption for server {}", server.getUri()); + ((DTLSConnector) connector).forceResumeAllSessions(); + } else { + LOG.info("Clear DTLS session for server {}", server.getUri()); + ((DTLSConnector) connector).clearConnectionState(); + } + } + } + }; + } + + @Override + public ExceptionTranslator createExceptionTranslator() { + return new DefaultExceptionTranslator() { + @Override + public Exception translate(Request coapRequest, Throwable error) { + if (error instanceof DtlsHandshakeTimeoutException) { + return new TimeoutException(Type.DTLS_HANDSHAKE_TIMEOUT, error, + "Request %s timeout : dtls handshake timeout", coapRequest.getURI()); + } else { + return super.translate(coapRequest, error); + } + } + }; + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coaps/CoapsClientProtocolProvider.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coaps/CoapsClientProtocolProvider.java new file mode 100644 index 0000000000..5a1b68b028 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/endpoint/coaps/CoapsClientProtocolProvider.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium.endpoint.coaps; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.californium.core.config.CoapConfig; +import org.eclipse.californium.core.config.CoapConfig.TrackerMode; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.elements.config.Configuration.ModuleDefinitionsProvider; +import org.eclipse.californium.elements.config.SystemConfig; +import org.eclipse.californium.elements.config.UdpConfig; +import org.eclipse.californium.scandium.config.DtlsConfig; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointFactory; +import org.eclipse.leshan.client.californium.endpoint.ClientProtocolProvider; +import org.eclipse.leshan.core.endpoint.Protocol; + +public class CoapsClientProtocolProvider implements ClientProtocolProvider { + + @Override + public Protocol getProtocol() { + return Protocol.COAPS; + } + + @Override + public void applyDefaultValue(Configuration configuration) { + configuration.set(CoapConfig.MID_TRACKER, TrackerMode.NULL); + configuration.set(CoapConfig.MAX_ACTIVE_PEERS, 10); + configuration.set(CoapConfig.PROTOCOL_STAGE_THREAD_COUNT, 1); + configuration.set(DtlsConfig.DTLS_MAX_CONNECTIONS, 10); + configuration.set(DtlsConfig.DTLS_RECEIVER_THREAD_COUNT, 1); + configuration.set(DtlsConfig.DTLS_CONNECTOR_THREAD_COUNT, 1); + // currently not supported by leshan's CertificateVerifier + configuration.setTransient(DtlsConfig.DTLS_VERIFY_SERVER_CERTIFICATES_SUBJECT); + // Set it to null to allow automatic mode + // See org.eclipse.leshan.client.californium.endpoint.CoapsEndpointFactory + configuration.set(DtlsConfig.DTLS_ROLE, null); + } + + @Override + public List getModuleDefinitionsProviders() { + return Arrays.asList(SystemConfig.DEFINITIONS, CoapConfig.DEFINITIONS, UdpConfig.DEFINITIONS, + DtlsConfig.DEFINITIONS); + } + + @Override + public CaliforniumClientEndpointFactory createDefaultEndpointFactory() { + return new CoapsClientEndpointFactory(); + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java index 7739dc5123..2361a20ce1 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java @@ -30,18 +30,16 @@ import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; -import org.eclipse.leshan.client.californium.CaliforniumEndpointsManager; import org.eclipse.leshan.client.californium.LwM2mClientCoapResource; -import org.eclipse.leshan.client.engine.RegistrationEngine; +import org.eclipse.leshan.client.californium.endpoint.ServerIdentityExtractor; +import org.eclipse.leshan.client.endpoint.ClientEndpointToolbox; +import org.eclipse.leshan.client.request.DownlinkRequestReceiver; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.listener.ObjectListener; import org.eclipse.leshan.client.servers.ServerIdentity; -import org.eclipse.leshan.core.link.LinkSerializer; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; import org.eclipse.leshan.core.link.attributes.InvalidAttributeException; -import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeParser; import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; -import org.eclipse.leshan.core.model.LwM2mModel; -import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.node.InvalidLwM2mPathException; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mObject; @@ -49,8 +47,6 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.codec.CodecException; -import org.eclipse.leshan.core.node.codec.LwM2mDecoder; -import org.eclipse.leshan.core.node.codec.LwM2mEncoder; import org.eclipse.leshan.core.request.BootstrapDeleteRequest; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; import org.eclipse.leshan.core.request.BootstrapReadRequest; @@ -85,55 +81,49 @@ */ public class ObjectResource extends LwM2mClientCoapResource implements ObjectListener { - protected final LwM2mObjectEnabler nodeEnabler; - protected final LwM2mEncoder encoder; - protected final LwM2mDecoder decoder; - protected final LinkSerializer linkSerializer; - protected final LwM2mAttributeParser attributeParser; - - public ObjectResource(LwM2mObjectEnabler nodeEnabler, RegistrationEngine registrationEngine, - CaliforniumEndpointsManager endpointsManager, LwM2mEncoder encoder, LwM2mDecoder decoder, - LinkSerializer linkSerializer, LwM2mAttributeParser attributeParser) { - super(Integer.toString(nodeEnabler.getId()), registrationEngine, endpointsManager); - this.nodeEnabler = nodeEnabler; - this.linkSerializer = linkSerializer; - this.nodeEnabler.addListener(this); - this.encoder = encoder; - this.decoder = decoder; - this.attributeParser = attributeParser; + protected DownlinkRequestReceiver requestReceiver; + protected ClientEndpointToolbox toolbox; + + public ObjectResource(int objectId, IdentityHandlerProvider identityHandlerProvider, + ServerIdentityExtractor serverIdentityExtractor, DownlinkRequestReceiver requestReceiver, + ClientEndpointToolbox toolbox) { + super(Integer.toString(objectId), identityHandlerProvider, serverIdentityExtractor); + this.requestReceiver = requestReceiver; + this.toolbox = toolbox; setObservable(true); } @Override public void handleGET(CoapExchange exchange) { - ServerIdentity identity = getServerOrRejectRequest(exchange); + Request coapRequest = exchange.advanced().getRequest(); + ServerIdentity identity = getServerOrRejectRequest(exchange, coapRequest); if (identity == null) return; String URI = exchange.getRequestOptions().getUriPathString(); - Request coapRequest = exchange.advanced().getRequest(); if (exchange.getRequestOptions().getAccept() == MediaTypeRegistry.APPLICATION_LINK_FORMAT) { if (identity.isLwm2mBootstrapServer()) { // Manage Bootstrap Discover Request - BootstrapDiscoverResponse response = nodeEnabler.discover(identity, - new BootstrapDiscoverRequest(URI, coapRequest)); + BootstrapDiscoverResponse response = requestReceiver + .requestReceived(identity, new BootstrapDiscoverRequest(URI, coapRequest)).getResponse(); if (response.getCode().isError()) { exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { exchange.respond(toCoapResponseCode(response.getCode()), - linkSerializer.serializeCoreLinkFormat(response.getObjectLinks()), + toolbox.getLinkSerializer().serializeCoreLinkFormat(response.getObjectLinks()), MediaTypeRegistry.APPLICATION_LINK_FORMAT); } return; } else { // Manage Discover Request - DiscoverResponse response = nodeEnabler.discover(identity, new DiscoverRequest(URI, coapRequest)); + DiscoverResponse response = requestReceiver + .requestReceived(identity, new DiscoverRequest(URI, coapRequest)).getResponse(); if (response.getCode().isError()) { exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { exchange.respond(toCoapResponseCode(response.getCode()), - linkSerializer.serializeCoreLinkFormat(response.getObjectLinks()), + toolbox.getLinkSerializer().serializeCoreLinkFormat(response.getObjectLinks()), MediaTypeRegistry.APPLICATION_LINK_FORMAT); } return; @@ -144,7 +134,7 @@ public void handleGET(CoapExchange exchange) { if (exchange.getRequestOptions().hasAccept()) { // If an request ask for a specific content format, use it (if we support it) requestedContentFormat = ContentFormat.fromCode(exchange.getRequestOptions().getAccept()); - if (!encoder.isSupported(requestedContentFormat)) { + if (!toolbox.getEncoder().isSupported(requestedContentFormat)) { exchange.respond(ResponseCode.NOT_ACCEPTABLE); return; } @@ -153,14 +143,13 @@ public void handleGET(CoapExchange exchange) { // Manage Observe Request if (exchange.getRequestOptions().hasObserve()) { ObserveRequest observeRequest = new ObserveRequest(requestedContentFormat, URI, coapRequest); - ObserveResponse response = nodeEnabler.observe(identity, observeRequest); + ObserveResponse response = requestReceiver.requestReceived(identity, observeRequest).getResponse(); if (response.getCode() == org.eclipse.leshan.core.ResponseCode.CONTENT) { LwM2mPath path = getPath(URI); LwM2mNode content = response.getContent(); - LwM2mModel model = new StaticModel(nodeEnabler.getObjectModel()); ContentFormat format = getContentFormat(observeRequest, requestedContentFormat); - exchange.respond(ResponseCode.CONTENT, encoder.encode(content, format, path, model), - format.getCode()); + exchange.respond(ResponseCode.CONTENT, + toolbox.getEncoder().encode(content, format, path, toolbox.getModel()), format.getCode()); return; } else { exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); @@ -171,13 +160,14 @@ public void handleGET(CoapExchange exchange) { // Manage Bootstrap Read Request BootstrapReadRequest readRequest = new BootstrapReadRequest(requestedContentFormat, URI, coapRequest); - BootstrapReadResponse response = nodeEnabler.read(identity, readRequest); + BootstrapReadResponse response = requestReceiver.requestReceived(identity, readRequest) + .getResponse(); if (response.getCode() == org.eclipse.leshan.core.ResponseCode.CONTENT) { LwM2mPath path = getPath(URI); LwM2mNode content = response.getContent(); - LwM2mModel model = new StaticModel(nodeEnabler.getObjectModel()); ContentFormat format = getContentFormat(readRequest, requestedContentFormat); - exchange.respond(ResponseCode.CONTENT, encoder.encode(content, format, path, model), + exchange.respond(ResponseCode.CONTENT, + toolbox.getEncoder().encode(content, format, path, toolbox.getModel()), format.getCode()); return; } else { @@ -187,13 +177,13 @@ public void handleGET(CoapExchange exchange) { } else { // Manage Read Request ReadRequest readRequest = new ReadRequest(requestedContentFormat, URI, coapRequest); - ReadResponse response = nodeEnabler.read(identity, readRequest); + ReadResponse response = requestReceiver.requestReceived(identity, readRequest).getResponse(); if (response.getCode() == org.eclipse.leshan.core.ResponseCode.CONTENT) { LwM2mPath path = getPath(URI); LwM2mNode content = response.getContent(); - LwM2mModel model = new StaticModel(nodeEnabler.getObjectModel()); ContentFormat format = getContentFormat(readRequest, requestedContentFormat); - exchange.respond(ResponseCode.CONTENT, encoder.encode(content, format, path, model), + exchange.respond(ResponseCode.CONTENT, + toolbox.getEncoder().encode(content, format, path, toolbox.getModel()), format.getCode()); return; } else { @@ -211,25 +201,28 @@ protected ContentFormat getContentFormat(DownlinkRequest request, ContentForm return requestedContentFormat; } - ContentFormat format = nodeEnabler.getDefaultEncodingFormat(request); - return format == null ? ContentFormat.DEFAULT : format; + // TODO TL : should we keep this feature ? + // ContentFormat format = nodeEnabler.getDefaultEncodingFormat(request); + // return format == null ? ContentFormat.DEFAULT : format; + + return ContentFormat.DEFAULT; } @Override public void handlePUT(CoapExchange coapExchange) { - ServerIdentity identity = getServerOrRejectRequest(coapExchange); + Request coapRequest = coapExchange.advanced().getRequest(); + ServerIdentity identity = getServerOrRejectRequest(coapExchange, coapRequest); if (identity == null) return; String URI = coapExchange.getRequestOptions().getUriPathString(); - Request coapRequest = coapExchange.advanced().getRequest(); // get Observe Spec LwM2mAttributeSet attributes = null; if (coapRequest.getOptions().getURIQueryCount() != 0) { List uriQueries = coapRequest.getOptions().getUriQuery(); try { - attributes = new LwM2mAttributeSet(attributeParser.parseQueryParams(uriQueries)); + attributes = new LwM2mAttributeSet(toolbox.getAttributeParser().parseQueryParams(uriQueries)); } catch (InvalidAttributeException e) { handleInvalidRequest(coapExchange.advanced(), "Unable to parse Attributes", e); } @@ -237,8 +230,8 @@ public void handlePUT(CoapExchange coapExchange) { // Manage Write Attributes Request if (attributes != null) { - WriteAttributesResponse response = nodeEnabler.writeAttributes(identity, - new WriteAttributesRequest(URI, attributes, coapRequest)); + WriteAttributesResponse response = requestReceiver + .requestReceived(identity, new WriteAttributesRequest(URI, attributes, coapRequest)).getResponse(); if (response.getCode().isError()) { coapExchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { @@ -256,25 +249,29 @@ public void handlePUT(CoapExchange coapExchange) { } ContentFormat contentFormat = ContentFormat.fromCode(coapExchange.getRequestOptions().getContentFormat()); - if (!decoder.isSupported(contentFormat)) { + if (!toolbox.getDecoder().isSupported(contentFormat)) { coapExchange.respond(ResponseCode.UNSUPPORTED_CONTENT_FORMAT); return; } LwM2mNode lwM2mNode; try { - LwM2mModel model = new StaticModel(nodeEnabler.getObjectModel()); - lwM2mNode = decoder.decode(coapExchange.getRequestPayload(), contentFormat, path, model); + lwM2mNode = toolbox.getDecoder().decode(coapExchange.getRequestPayload(), contentFormat, path, + toolbox.getModel()); if (identity.isLwm2mBootstrapServer()) { - BootstrapWriteResponse response = nodeEnabler.write(identity, - new BootstrapWriteRequest(path, lwM2mNode, contentFormat, coapRequest)); + BootstrapWriteResponse response = requestReceiver + .requestReceived(identity, + new BootstrapWriteRequest(path, lwM2mNode, contentFormat, coapRequest)) + .getResponse(); if (response.getCode().isError()) { coapExchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { coapExchange.respond(toCoapResponseCode(response.getCode())); } } else { - WriteResponse response = nodeEnabler.write(identity, - new WriteRequest(Mode.REPLACE, contentFormat, URI, lwM2mNode, coapRequest)); + WriteResponse response = requestReceiver + .requestReceived(identity, + new WriteRequest(Mode.REPLACE, contentFormat, URI, lwM2mNode, coapRequest)) + .getResponse(); if (response.getCode().isError()) { coapExchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { @@ -293,13 +290,12 @@ public void handlePUT(CoapExchange coapExchange) { @Override public void handlePOST(CoapExchange exchange) { - ServerIdentity identity = getServerOrRejectRequest(exchange); + Request coapRequest = exchange.advanced().getRequest(); + ServerIdentity identity = getServerOrRejectRequest(exchange, coapRequest); if (identity == null) return; String URI = exchange.getRequestOptions().getUriPathString(); - Request coapRequest = exchange.advanced().getRequest(); - LwM2mPath path = getPath(URI); // Manage Execute Request @@ -309,8 +305,10 @@ public void handlePOST(CoapExchange exchange) { || ContentFormat.fromCode(exchange.getRequestOptions().getContentFormat()) == ContentFormat.TEXT) { byte[] payload = exchange.getRequestPayload(); - ExecuteResponse response = nodeEnabler.execute(identity, - new ExecuteRequest(URI, payload != null ? new String(payload) : null, coapRequest)); + ExecuteResponse response = requestReceiver + .requestReceived(identity, + new ExecuteRequest(URI, payload != null ? new String(payload) : null, coapRequest)) + .getResponse(); if (response.getCode().isError()) { exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { @@ -327,18 +325,20 @@ public void handlePOST(CoapExchange exchange) { } ContentFormat contentFormat = ContentFormat.fromCode(exchange.getRequestOptions().getContentFormat()); - if (!decoder.isSupported(contentFormat)) { + if (!toolbox.getDecoder().isSupported(contentFormat)) { exchange.respond(ResponseCode.UNSUPPORTED_CONTENT_FORMAT); return; } - LwM2mModel model = new StaticModel(nodeEnabler.getObjectModel()); // manage partial update of multi-instance resource if (path.isResource()) { try { - LwM2mNode lwM2mNode = decoder.decode(exchange.getRequestPayload(), contentFormat, path, model); - WriteResponse response = nodeEnabler.write(identity, - new WriteRequest(Mode.UPDATE, contentFormat, URI, lwM2mNode, coapRequest)); + LwM2mNode lwM2mNode = toolbox.getDecoder().decode(exchange.getRequestPayload(), contentFormat, path, + toolbox.getModel()); + WriteResponse response = requestReceiver + .requestReceived(identity, + new WriteRequest(Mode.UPDATE, contentFormat, URI, lwM2mNode, coapRequest)) + .getResponse(); if (response.getCode().isError()) { exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { @@ -352,9 +352,12 @@ public void handlePOST(CoapExchange exchange) { // Manage Update Instance if (path.isObjectInstance()) { try { - LwM2mNode lwM2mNode = decoder.decode(exchange.getRequestPayload(), contentFormat, path, model); - WriteResponse response = nodeEnabler.write(identity, - new WriteRequest(Mode.UPDATE, contentFormat, URI, lwM2mNode, coapRequest)); + LwM2mNode lwM2mNode = toolbox.getDecoder().decode(exchange.getRequestPayload(), contentFormat, path, + toolbox.getModel()); + WriteResponse response = requestReceiver + .requestReceived(identity, + new WriteRequest(Mode.UPDATE, contentFormat, URI, lwM2mNode, coapRequest)) + .getResponse(); if (response.getCode().isError()) { exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { @@ -369,8 +372,8 @@ public void handlePOST(CoapExchange exchange) { // Manage Create Request try { // decode the payload as an instance - LwM2mObject object = decoder.decode(exchange.getRequestPayload(), contentFormat, - new LwM2mPath(path.getObjectId()), model, LwM2mObject.class); + LwM2mObject object = toolbox.getDecoder().decode(exchange.getRequestPayload(), contentFormat, + new LwM2mPath(path.getObjectId()), toolbox.getModel(), LwM2mObject.class); CreateRequest createRequest; // check if this is the "special" case where instance ID is not defined ... @@ -388,7 +391,7 @@ public void handlePOST(CoapExchange exchange) { .toArray(new LwM2mObjectInstance[object.getInstances().values().size()])); } - CreateResponse response = nodeEnabler.create(identity, createRequest); + CreateResponse response = requestReceiver.requestReceived(identity, createRequest).getResponse(); if (response.getCode() == org.eclipse.leshan.core.ResponseCode.CREATED) { if (response.getLocation() != null) exchange.setLocationPath(response.getLocation()); @@ -409,20 +412,21 @@ public void handleDELETE(CoapExchange coapExchange) { // Manage Delete Request String URI = coapExchange.getRequestOptions().getUriPathString(); Request coapRequest = coapExchange.advanced().getRequest(); - ServerIdentity identity = getServerOrRejectRequest(coapExchange); + ServerIdentity identity = getServerOrRejectRequest(coapExchange, coapRequest); if (identity == null) return; if (identity.isLwm2mBootstrapServer()) { - BootstrapDeleteResponse response = nodeEnabler.delete(identity, - new BootstrapDeleteRequest(URI, coapRequest)); + BootstrapDeleteResponse response = requestReceiver + .requestReceived(identity, new BootstrapDeleteRequest(URI, coapRequest)).getResponse(); if (response.getCode().isError()) { coapExchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { coapExchange.respond(toCoapResponseCode(response.getCode())); } } else { - DeleteResponse response = nodeEnabler.delete(identity, new DeleteRequest(URI, coapRequest)); + DeleteResponse response = requestReceiver.requestReceived(identity, new DeleteRequest(URI, coapRequest)) + .getResponse(); if (response.getCode().isError()) { coapExchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage()); } else { diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java deleted file mode 100644 index 9e0b4b2472..0000000000 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java +++ /dev/null @@ -1,137 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2013-2015 Sierra Wireless and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v2.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v20.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.html. - * - * Contributors: - * Zebra Technologies - initial API and implementation - * Michał Wadowski (Orange) - Improved compliance with rfc6690 - *******************************************************************************/ -package org.eclipse.leshan.client.californium.request; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import org.eclipse.californium.core.coap.MessageObserver; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.eclipse.leshan.client.californium.CaliforniumEndpointsManager; -import org.eclipse.leshan.client.request.LwM2mRequestSender; -import org.eclipse.leshan.client.servers.ServerIdentity; -import org.eclipse.leshan.core.californium.AsyncRequestObserver; -import org.eclipse.leshan.core.californium.SyncRequestObserver; -import org.eclipse.leshan.core.link.LinkSerializer; -import org.eclipse.leshan.core.model.LwM2mModel; -import org.eclipse.leshan.core.node.codec.LwM2mEncoder; -import org.eclipse.leshan.core.request.UplinkRequest; -import org.eclipse.leshan.core.response.ErrorCallback; -import org.eclipse.leshan.core.response.LwM2mResponse; -import org.eclipse.leshan.core.response.ResponseCallback; -import org.eclipse.leshan.core.util.NamedThreadFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A {@link LwM2mRequestSender} based on Californium(CoAP implementation). - */ -public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender { - - private static final Logger LOG = LoggerFactory.getLogger(CaliforniumLwM2mRequestSender.class); - - private final ScheduledExecutorService executor; - private final boolean attached; - private final CaliforniumEndpointsManager endpointsManager; - private final LwM2mEncoder encoder; - private final LwM2mModel model; - private final LinkSerializer linkSerializer; - - public CaliforniumLwM2mRequestSender(CaliforniumEndpointsManager endpointsManager, - ScheduledExecutorService sharedExecutor, LwM2mEncoder encoder, LwM2mModel model, - LinkSerializer linkSerializer) { - this.endpointsManager = endpointsManager; - this.linkSerializer = linkSerializer; - if (sharedExecutor == null) { - this.executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Leshan Async Request timeout")); - this.attached = false; - } else { - this.executor = sharedExecutor; - this.attached = false; - } - this.model = model; - this.encoder = encoder; - } - - @Override - public T send(ServerIdentity server, final UplinkRequest request, long timeout) - throws InterruptedException { - // Create the CoAP request from LwM2m request - CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(server.getIdentity(), encoder, model, - linkSerializer); - request.accept(coapClientRequestBuilder); - Request coapRequest = coapClientRequestBuilder.getRequest(); - - // Send CoAP request synchronously - SyncRequestObserver syncMessageObserver = new SyncRequestObserver(coapRequest, timeout) { - @Override - public T buildResponse(Response coapResponse) { - // Build LwM2m response - LwM2mClientResponseBuilder lwm2mResponseBuilder = new LwM2mClientResponseBuilder<>(coapResponse); - request.accept(lwm2mResponseBuilder); - return lwm2mResponseBuilder.getResponse(); - } - }; - coapRequest.addMessageObserver(syncMessageObserver); - - // Send CoAP request asynchronously - endpointsManager.getEndpoint(server).sendRequest(coapRequest); - - // Wait for response, then return it - return syncMessageObserver.waitForResponse(); - } - - @Override - public void send(ServerIdentity server, final UplinkRequest request, long timeout, - ResponseCallback responseCallback, ErrorCallback errorCallback) { - // Create the CoAP request from LwM2m request - CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(server.getIdentity(), encoder, model, - linkSerializer); - request.accept(coapClientRequestBuilder); - Request coapRequest = coapClientRequestBuilder.getRequest(); - - // Add CoAP request callback - MessageObserver obs = new AsyncRequestObserver(coapRequest, responseCallback, errorCallback, timeout, - executor) { - @Override - public T buildResponse(Response coapResponse) { - // Build LwM2m response - LwM2mClientResponseBuilder lwm2mResponseBuilder = new LwM2mClientResponseBuilder<>(coapResponse); - request.accept(lwm2mResponseBuilder); - return lwm2mResponseBuilder.getResponse(); - } - }; - coapRequest.addMessageObserver(obs); - - // Send CoAP request asynchronously - endpointsManager.getEndpoint(server).sendRequest(coapRequest); - } - - @Override - public void destroy() { - if (attached) { - executor.shutdownNow(); - try { - executor.awaitTermination(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - LOG.warn("Destroying RequestSender was interrupted.", e); - } - } - } -} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java index 84399ed608..93a996996f 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java @@ -23,7 +23,7 @@ import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.elements.EndpointContext; import org.eclipse.californium.elements.util.Bytes; -import org.eclipse.leshan.core.californium.EndpointContextUtil; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.link.LinkSerializer; import org.eclipse.leshan.core.model.LwM2mModel; @@ -51,12 +51,15 @@ public class CoapRequestBuilder implements UplinkRequestVisitor { protected final LwM2mEncoder encoder; protected final LwM2mModel model; protected final LinkSerializer linkSerializer; + protected final IdentityHandler identityHandler; - public CoapRequestBuilder(Identity server, LwM2mEncoder encoder, LwM2mModel model, LinkSerializer linkSerializer) { + public CoapRequestBuilder(Identity server, LwM2mEncoder encoder, LwM2mModel model, LinkSerializer linkSerializer, + IdentityHandler identityHandler) { this.server = server; this.encoder = encoder; this.model = model; this.linkSerializer = linkSerializer; + this.identityHandler = identityHandler; } @Override @@ -171,7 +174,7 @@ public Request getRequest() { } protected void buildRequestSettings() { - EndpointContext context = EndpointContextUtil.extractContext(server, true); + EndpointContext context = identityHandler.createEndpointContext(server, true); coapRequest.setDestinationContext(context); if (server.isOSCORE()) { diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/LwM2mClientResponseBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/LwM2mResponseBuilder.java similarity index 97% rename from leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/LwM2mClientResponseBuilder.java rename to leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/LwM2mResponseBuilder.java index 13e2296924..34f4a6a33e 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/LwM2mClientResponseBuilder.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/LwM2mResponseBuilder.java @@ -40,13 +40,13 @@ * * @param the type of the response to build. */ -public class LwM2mClientResponseBuilder implements UplinkRequestVisitor { +public class LwM2mResponseBuilder implements UplinkRequestVisitor { protected final Response coapResponse; protected LwM2mResponse lwM2mresponse; - public LwM2mClientResponseBuilder(Response coapResponse) { + public LwM2mResponseBuilder(Response coapResponse) { this.coapResponse = coapResponse; } diff --git a/leshan-client-cf/src/test/java/org/eclipse/leshan/client/californium/LeshanBuilderTest.java b/leshan-client-cf/src/test/java/org/eclipse/leshan/client/californium/LeshanBuilderTest.java deleted file mode 100644 index 7b1642613d..0000000000 --- a/leshan-client-cf/src/test/java/org/eclipse/leshan/client/californium/LeshanBuilderTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2020 Sierra Wireless and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v2.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v20.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.html. - * - * Contributors: - * Sierra Wireless - initial API and implementation - *******************************************************************************/ -package org.eclipse.leshan.client.californium; - -import java.util.ArrayList; -import java.util.HashMap; - -import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; -import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; -import org.eclipse.leshan.client.resource.ObjectEnabler; -import org.eclipse.leshan.core.request.ContentFormat; -import org.junit.Test; - -public class LeshanBuilderTest { - - @Test(expected = IllegalArgumentException.class) - public void fail_to_create_client_with_same_object_twice() { - ObjectEnabler objectEnabler = new ObjectEnabler(1, null, new HashMap(), null, - ContentFormat.DEFAULT); - ObjectEnabler objectEnabler2 = new ObjectEnabler(1, null, new HashMap(), null, - ContentFormat.DEFAULT); - ArrayList objects = new ArrayList<>(); - objects.add(objectEnabler); - objects.add(objectEnabler2); - new LeshanClientBuilder("test").setObjects(objects).build(); - } -} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/LeshanClient.java similarity index 53% rename from leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java rename to leshan-client-core/src/main/java/org/eclipse/leshan/client/LeshanClient.java index cc3f1051d4..814c27206a 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/LeshanClient.java @@ -14,9 +14,8 @@ * Zebra Technologies - initial API and implementation * Michał Wadowski (Orange) - Improved compliance with rfc6690 *******************************************************************************/ -package org.eclipse.leshan.client.californium; +package org.eclipse.leshan.client; -import java.net.InetSocketAddress; import java.security.cert.Certificate; import java.util.HashMap; import java.util.List; @@ -25,42 +24,31 @@ import java.util.TreeSet; import java.util.concurrent.ScheduledExecutorService; -import org.eclipse.californium.core.CoapResource; -import org.eclipse.californium.core.CoapServer; -import org.eclipse.californium.core.config.CoapConfig; -import org.eclipse.californium.core.network.CoapEndpoint; -import org.eclipse.californium.core.server.resources.Resource; -import org.eclipse.californium.elements.config.Configuration; -import org.eclipse.californium.elements.util.ExecutorsUtil; -import org.eclipse.californium.elements.util.NamedThreadFactory; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder; -import org.eclipse.leshan.client.LwM2mClient; -import org.eclipse.leshan.client.RegistrationUpdateHandler; import org.eclipse.leshan.client.bootstrap.BootstrapConsistencyChecker; import org.eclipse.leshan.client.bootstrap.BootstrapHandler; -import org.eclipse.leshan.client.californium.bootstrap.BootstrapResource; -import org.eclipse.leshan.client.californium.object.ObjectResource; -import org.eclipse.leshan.client.californium.request.CaliforniumLwM2mRequestSender; +import org.eclipse.leshan.client.endpoint.ClientEndpointToolbox; +import org.eclipse.leshan.client.endpoint.DefaultEndpointsManager; +import org.eclipse.leshan.client.endpoint.LwM2mClientEndpoint; +import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider; import org.eclipse.leshan.client.engine.RegistrationEngine; import org.eclipse.leshan.client.engine.RegistrationEngineFactory; import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.observer.LwM2mClientObserverAdapter; import org.eclipse.leshan.client.observer.LwM2mClientObserverDispatcher; -import org.eclipse.leshan.client.request.LwM2mRequestSender; +import org.eclipse.leshan.client.request.DefaultDownlinkReceiver; +import org.eclipse.leshan.client.request.DefaultUplinkRequestSender; +import org.eclipse.leshan.client.request.DownlinkRequestReceiver; +import org.eclipse.leshan.client.request.UplinkRequestSender; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.LwM2mObjectTree; import org.eclipse.leshan.client.resource.LwM2mRootEnabler; import org.eclipse.leshan.client.resource.RootEnabler; -import org.eclipse.leshan.client.resource.listener.ObjectListener; -import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter; import org.eclipse.leshan.client.send.DataSender; import org.eclipse.leshan.client.send.DataSenderManager; import org.eclipse.leshan.client.send.SendService; import org.eclipse.leshan.client.servers.ServerIdentity; -import org.eclipse.leshan.core.californium.EndpointFactory; import org.eclipse.leshan.core.link.LinkSerializer; import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeParser; -import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.codec.LwM2mDecoder; @@ -81,57 +69,49 @@ public class LeshanClient implements LwM2mClient { private static final Logger LOG = LoggerFactory.getLogger(LeshanClient.class); - private final CoapAPI coapApi; - private final CoapServer coapServer; - private final CaliforniumLwM2mRequestSender requestSender; - private final CaliforniumEndpointsManager endpointsManager; + private final LwM2mClientEndpointsProvider endpointsProvider; + private final EndpointsManager endpointsManager; + private final UplinkRequestSender requestSender; - private LwM2mObjectTree objectTree; + private final LwM2mObjectTree objectTree; private final BootstrapHandler bootstrapHandler; private final LwM2mRootEnabler rootEnabler; - private final LwM2mEncoder encoder; - private final LwM2mDecoder decoder; private final RegistrationEngine engine; private final LwM2mClientObserverDispatcher observers; - private final LinkSerializer linkSerializer; private final DataSenderManager dataSenderManager; - public LeshanClient(String endpoint, InetSocketAddress localAddress, - List objectEnablers, List dataSenders, Configuration coapConfig, - Builder dtlsConfigBuilder, List trustStore, EndpointFactory endpointFactory, - RegistrationEngineFactory engineFactory, BootstrapConsistencyChecker checker, - Map additionalAttributes, Map bsAdditionalAttributes, LwM2mEncoder encoder, - LwM2mDecoder decoder, ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, - LwM2mAttributeParser attributeParser) { + public LeshanClient(String endpoint, List objectEnablers, + List dataSenders, List trustStore, RegistrationEngineFactory engineFactory, + BootstrapConsistencyChecker checker, Map additionalAttributes, + Map bsAdditionalAttributes, LwM2mEncoder encoder, LwM2mDecoder decoder, + ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, + LwM2mAttributeParser attributeParser, LwM2mClientEndpointsProvider endpointsProvider) { Validate.notNull(endpoint); Validate.notEmpty(objectEnablers); - Validate.notNull(coapConfig); + + this.endpointsProvider = endpointsProvider; objectTree = createObjectTree(objectEnablers); rootEnabler = createRootEnabler(objectTree); - this.decoder = decoder; - this.encoder = encoder; - this.linkSerializer = linkSerializer; observers = createClientObserverDispatcher(); - bootstrapHandler = createBootstrapHandler(objectTree, checker); - endpointsManager = createEndpointsManager(localAddress, coapConfig, dtlsConfigBuilder, trustStore, - endpointFactory); - requestSender = createRequestSender(endpointsManager, sharedExecutor, encoder, objectTree.getModel(), - linkSerializer); + bootstrapHandler = createBoostrapHandler(objectTree, checker); + + ClientEndpointToolbox toolbox = new ClientEndpointToolbox(decoder, encoder, linkSerializer, + objectTree.getModel(), attributeParser); + endpointsManager = createEndpointsManager(this.endpointsProvider, toolbox, trustStore); + requestSender = createRequestSender(this.endpointsProvider); dataSenderManager = createDataSenderManager(dataSenders, rootEnabler, requestSender); + engine = engineFactory.createRegistratioEngine(endpoint, objectTree, endpointsManager, requestSender, bootstrapHandler, observers, additionalAttributes, bsAdditionalAttributes, getSupportedContentFormat(decoder, encoder), sharedExecutor); - coapServer = createCoapServer(coapConfig, sharedExecutor); - coapServer.add(createBootstrapResource(engine, endpointsManager, bootstrapHandler)); - endpointsManager.setCoapServer(coapServer); - linkObjectTreeToCoapServer(coapServer, engine, endpointsManager, objectTree, encoder, decoder, linkSerializer, - attributeParser); + DownlinkRequestReceiver requestReceiver = createRequestReceiver(bootstrapHandler, rootEnabler, objectTree, + engine); createRegistrationUpdateHandler(engine, endpointsManager, bootstrapHandler, objectTree); - coapApi = new CoapAPI(); + endpointsProvider.init(objectTree, requestReceiver, toolbox); } protected LwM2mRootEnabler createRootEnabler(LwM2mObjectTree tree) { @@ -143,7 +123,7 @@ protected LwM2mObjectTree createObjectTree(List ob } protected DataSenderManager createDataSenderManager(List dataSenders, LwM2mRootEnabler rootEnabler, - LwM2mRequestSender requestSender) { + UplinkRequestSender requestSender) { Map dataSenderMap = new HashMap<>(); for (DataSender dataSender : dataSenders) { dataSenderMap.put(dataSender.getName(), dataSender); @@ -162,92 +142,26 @@ public void onUnexpectedError(Throwable unexpectedError) { return observer; } - protected BootstrapHandler createBootstrapHandler(LwM2mObjectTree objectTree, BootstrapConsistencyChecker checker) { + protected BootstrapHandler createBoostrapHandler(LwM2mObjectTree objectTree, BootstrapConsistencyChecker checker) { return new BootstrapHandler(objectTree.getObjectEnablers(), checker); } - protected CoapServer createCoapServer(Configuration coapConfig, ScheduledExecutorService sharedExecutor) { - // create coap server - CoapServer coapServer = new CoapServer(coapConfig) { - @Override - protected Resource createRoot() { - // Use to handle Delete on "/" - return new org.eclipse.leshan.client.californium.RootResource(engine, endpointsManager, - bootstrapHandler, this, rootEnabler, encoder, decoder, linkSerializer); - } - }; - - // configure executors - if (sharedExecutor != null) { - coapServer.setExecutors(sharedExecutor, sharedExecutor, true); - } else { - // use same executor as main and secondary one. - ScheduledExecutorService executor = ExecutorsUtil.newScheduledThreadPool( - coapConfig.get(CoapConfig.PROTOCOL_STAGE_THREAD_COUNT), - new NamedThreadFactory("CoapServer(main)#")); - coapServer.setExecutors(executor, executor, false); - } - return coapServer; + protected DownlinkRequestReceiver createRequestReceiver(BootstrapHandler bootstrapHandler, + LwM2mRootEnabler rootEnabler, LwM2mObjectTree objectTree, RegistrationEngine registrationEngine) { + return new DefaultDownlinkReceiver(bootstrapHandler, rootEnabler, objectTree, registrationEngine); } - protected void linkObjectTreeToCoapServer(final CoapServer coapServer, final RegistrationEngine registrationEngine, - final CaliforniumEndpointsManager endpointsManager, LwM2mObjectTree objectTree, final LwM2mEncoder encoder, - final LwM2mDecoder decoder, LinkSerializer linkSerializer, LwM2mAttributeParser attributeParser) { - - // Create CoAP resources for each lwm2m Objects. - for (LwM2mObjectEnabler enabler : objectTree.getObjectEnablers().values()) { - CoapResource clientObject = createObjectResource(enabler, registrationEngine, endpointsManager, encoder, - decoder, linkSerializer, attributeParser); - coapServer.add(clientObject); - } - - // listen object tree - objectTree.addListener(new ObjectsListenerAdapter() { - @Override - public void objectAdded(LwM2mObjectEnabler object) { - CoapResource clientObject = createObjectResource(object, registrationEngine, endpointsManager, encoder, - decoder, linkSerializer, attributeParser); - coapServer.add(clientObject); - } - - @Override - public void objectRemoved(LwM2mObjectEnabler object) { - Resource resource = coapServer.getRoot().getChild(Integer.toString(object.getId())); - if (resource instanceof ObjectListener) { - object.removeListener((ObjectListener) (resource)); - } - coapServer.remove(resource); - } - }); + protected EndpointsManager createEndpointsManager(LwM2mClientEndpointsProvider endpointProvider, + ClientEndpointToolbox toolbox, List trustStore) { + return new DefaultEndpointsManager(endpointProvider, toolbox, trustStore); } - protected CoapResource createObjectResource(LwM2mObjectEnabler enabler, RegistrationEngine registrationEngine, - CaliforniumEndpointsManager endpointsManager, LwM2mEncoder encoder, LwM2mDecoder decoder, - LinkSerializer linkSerializer, LwM2mAttributeParser attributeParser) { - return new ObjectResource(enabler, registrationEngine, endpointsManager, encoder, decoder, linkSerializer, - attributeParser); - } - - protected CoapResource createBootstrapResource(RegistrationEngine registrationEngine, - CaliforniumEndpointsManager endpointsManager, BootstrapHandler bootstrapHandler) { - return new BootstrapResource(registrationEngine, endpointsManager, bootstrapHandler); - } - - protected CaliforniumEndpointsManager createEndpointsManager(InetSocketAddress localAddress, - Configuration coapConfig, Builder dtlsConfigBuilder, List trustStore, - EndpointFactory endpointFactory) { - return new CaliforniumEndpointsManager(localAddress, coapConfig, dtlsConfigBuilder, trustStore, - endpointFactory); - } - - protected CaliforniumLwM2mRequestSender createRequestSender(CaliforniumEndpointsManager endpointsManager, - ScheduledExecutorService executor, LwM2mEncoder encoder, LwM2mModel model, LinkSerializer linkSerializer) { - return new CaliforniumLwM2mRequestSender(endpointsManager, executor, encoder, model, linkSerializer); + protected UplinkRequestSender createRequestSender(LwM2mClientEndpointsProvider endpointsProvider) { + return new DefaultUplinkRequestSender(endpointsProvider); } protected RegistrationUpdateHandler createRegistrationUpdateHandler(RegistrationEngine engine, - CaliforniumEndpointsManager endpointsManager, BootstrapHandler bootstrapHandler, - LwM2mObjectTree objectTree) { + EndpointsManager endpointsManager, BootstrapHandler bootstrapHandler, LwM2mObjectTree objectTree) { RegistrationUpdateHandler registrationUpdateHandler = new RegistrationUpdateHandler(engine, bootstrapHandler); registrationUpdateHandler.listen(objectTree); return registrationUpdateHandler; @@ -290,7 +204,7 @@ public void destroy(boolean deregister) { dataSenderManager.destroy(); engine.destroy(deregister); endpointsManager.destroy(); - requestSender.destroy(); + endpointsProvider.destroy(); objectTree.destroy(); LOG.info("Leshan client destroyed."); @@ -357,31 +271,6 @@ public T getDataSender(String senderName, Class sender }; } - /** - * A CoAP API, generally needed if you want to access to underlying CoAP layer. - */ - public CoapAPI coap() { - return coapApi; - } - - public class CoapAPI { - /** - * @return the underlying {@link CoapServer} - */ - public CoapServer getServer() { - return coapServer; - } - - /** - * Returns the current {@link CoapEndpoint} used to communicate with the given server. - * - * @return the {@link CoapEndpoint} used to communicate to LWM2M server. - */ - public CoapEndpoint getEndpoint(ServerIdentity server) { - return (CoapEndpoint) endpointsManager.getEndpoint(server); - } - } - /** * Add listener to observe client lifecycle (bootstrap, register, update, deregister). */ @@ -412,12 +301,7 @@ public Map getRegisteredServers() { return engine.getRegisteredServers(); } - /** - * Returns the current {@link InetSocketAddress} use to communicate with the given server. - * - * @return the address used to connect to the server or null if the client is not started. - */ - public InetSocketAddress getAddress(ServerIdentity server) { - return endpointsManager.getEndpoint(server).getAddress(); + public LwM2mClientEndpoint getEndpoint(ServerIdentity server) { + return endpointsProvider.getEndpoint(server); } } diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/LeshanClientBuilder.java similarity index 65% rename from leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java rename to leshan-client-core/src/main/java/org/eclipse/leshan/client/LeshanClientBuilder.java index 9187d4aac0..7926e31680 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/LeshanClientBuilder.java @@ -14,9 +14,8 @@ * Sierra Wireless - initial API and implementation * Michał Wadowski (Orange) - Improved compliance with rfc6690 *******************************************************************************/ -package org.eclipse.leshan.client.californium; +package org.eclipse.leshan.client; -import java.net.InetSocketAddress; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; @@ -25,21 +24,9 @@ import java.util.Map; import java.util.concurrent.ScheduledExecutorService; -import org.eclipse.californium.core.CoapServer; -import org.eclipse.californium.core.config.CoapConfig; -import org.eclipse.californium.core.config.CoapConfig.TrackerMode; -import org.eclipse.californium.core.network.CoapEndpoint; -import org.eclipse.californium.elements.Connector; -import org.eclipse.californium.elements.UDPConnector; -import org.eclipse.californium.elements.config.Configuration; -import org.eclipse.californium.elements.config.SystemConfig; -import org.eclipse.californium.elements.config.UdpConfig; -import org.eclipse.californium.scandium.DTLSConnector; -import org.eclipse.californium.scandium.config.DtlsConfig; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder; import org.eclipse.leshan.client.bootstrap.BootstrapConsistencyChecker; -import org.eclipse.leshan.client.californium.bootstrap.DefaultBootstrapConsistencyChecker; +import org.eclipse.leshan.client.bootstrap.DefaultBootstrapConsistencyChecker; +import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.engine.RegistrationEngine; import org.eclipse.leshan.client.engine.RegistrationEngineFactory; @@ -50,8 +37,6 @@ import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.client.send.DataSender; import org.eclipse.leshan.core.LwM2mId; -import org.eclipse.leshan.core.californium.DefaultEndpointFactory; -import org.eclipse.leshan.core.californium.EndpointFactory; import org.eclipse.leshan.core.link.DefaultLinkSerializer; import org.eclipse.leshan.core.link.LinkSerializer; import org.eclipse.leshan.core.link.lwm2m.attributes.DefaultLwM2mAttributeParser; @@ -74,12 +59,9 @@ public class LeshanClientBuilder { private final String endpoint; - private InetSocketAddress localAddress; private List objectEnablers; private List dataSenders; - private Configuration coapConfig; - private Builder dtlsConfigBuilder; private List trustStore; private LwM2mEncoder encoder; @@ -87,7 +69,6 @@ public class LeshanClientBuilder { private LinkSerializer linkSerializer; private LwM2mAttributeParser attributeParser; - private EndpointFactory endpointFactory; private RegistrationEngineFactory engineFactory; private Map additionalAttributes; private Map bsAdditionalAttributes; @@ -96,6 +77,8 @@ public class LeshanClientBuilder { private ScheduledExecutorService executor; + private LwM2mClientEndpointsProvider endpointsProvider; + /** * Creates a new instance for setting the configuration options for a {@link LeshanClient} instance. * @@ -119,18 +102,6 @@ public LeshanClientBuilder(String endpoint) { this.endpoint = endpoint; } - /** - * Sets the local address to use. - */ - public LeshanClientBuilder setLocalAddress(String hostname, int port) { - if (hostname == null) { - this.localAddress = new InetSocketAddress(port); - } else { - this.localAddress = new InetSocketAddress(hostname, port); - } - return this; - } - /** * Sets the list of objects enablers. *

@@ -207,25 +178,6 @@ public LeshanClientBuilder setAttributeParser(LwM2mAttributeParser attributePars return this; } - /** - * Set the Californium/CoAP {@link Configuration}. - *

- * This is strongly recommended to create the {@link Configuration} with {@link #createDefaultCoapConfiguration()} - * before to modify it. - */ - public LeshanClientBuilder setCoapConfig(Configuration config) { - this.coapConfig = config; - return this; - } - - /** - * Set the Scandium/DTLS Configuration : {@link DtlsConnectorConfig}. - */ - public LeshanClientBuilder setDtlsConfig(DtlsConnectorConfig.Builder config) { - this.dtlsConfigBuilder = config; - return this; - } - /** * Set optional trust store for verifying X.509 server certificates. * @@ -236,20 +188,6 @@ public LeshanClientBuilder setTrustStore(List trustStore) { return this; } - /** - * Advanced setter used to create custom CoAP endpoint. - *

- * An {@link UDPConnector} is expected for unsecured endpoint and a {@link DTLSConnector} is expected for secured - * endpoint. - * - * @param endpointFactory An {@link EndpointFactory}, you can extends {@link DefaultEndpointFactory}. - * @return the builder for fluent client creation. - */ - public LeshanClientBuilder setEndpointFactory(EndpointFactory endpointFactory) { - this.endpointFactory = endpointFactory; - return this; - } - /** * Set the {@link RegistrationEngineFactory} which is responsible to create the {@link RegistrationEngine}. *

@@ -315,31 +253,18 @@ public LeshanClientBuilder setSharedExecutor(ScheduledExecutorService executor) return this; } - public static Configuration createDefaultCoapConfiguration() { - Configuration networkConfig = new Configuration(CoapConfig.DEFINITIONS, DtlsConfig.DEFINITIONS, - UdpConfig.DEFINITIONS, SystemConfig.DEFINITIONS); - networkConfig.set(CoapConfig.MID_TRACKER, TrackerMode.NULL); - networkConfig.set(CoapConfig.MAX_ACTIVE_PEERS, 10); - networkConfig.set(CoapConfig.PROTOCOL_STAGE_THREAD_COUNT, 1); - networkConfig.set(DtlsConfig.DTLS_MAX_CONNECTIONS, 10); - networkConfig.set(DtlsConfig.DTLS_RECEIVER_THREAD_COUNT, 1); - networkConfig.set(DtlsConfig.DTLS_CONNECTOR_THREAD_COUNT, 1); - // currently not supported by leshan's CertificateVerifier - networkConfig.setTransient(DtlsConfig.DTLS_VERIFY_SERVER_CERTIFICATES_SUBJECT); - // Set it to null to allow automatic mode - // See org.eclipse.leshan.client.californium.CaliforniumEndpointsManager - networkConfig.set(DtlsConfig.DTLS_ROLE, null); - - return networkConfig; + /** + * @return the builder for fluent client creation. + */ + public LeshanClientBuilder setEndpointsProvider(LwM2mClientEndpointsProvider endpointsProvider) { + this.endpointsProvider = endpointsProvider; + return this; } /** * Creates an instance of {@link LeshanClient} based on the properties set on this builder. */ public LeshanClient build() { - if (localAddress == null) { - localAddress = new InetSocketAddress(0); - } if (objectEnablers == null) { ObjectsInitializer initializer = new ObjectsInitializer(); initializer.setInstancesForObject(LwM2mId.SECURITY, @@ -359,49 +284,16 @@ public LeshanClient build() { linkSerializer = new DefaultLinkSerializer(); if (attributeParser == null) attributeParser = new DefaultLwM2mAttributeParser(); - if (coapConfig == null) { - coapConfig = createDefaultCoapConfiguration(); - } if (engineFactory == null) { engineFactory = new DefaultRegistrationEngineFactory(); } - if (endpointFactory == null) { - endpointFactory = new DefaultEndpointFactory("LWM2M Client", true) { - @Override - protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) { - DTLSConnector dtlsConnector = new DTLSConnector(dtlsConfig); - if (executor != null) { - dtlsConnector.setExecutor(executor); - } - return dtlsConnector; - } - }; - } if (bootstrapConsistencyChecker == null) { bootstrapConsistencyChecker = new DefaultBootstrapConsistencyChecker(); } - // handle dtlsConfig - if (dtlsConfigBuilder == null) { - dtlsConfigBuilder = DtlsConnectorConfig.builder(coapConfig); - } - DtlsConnectorConfig incompleteConfig = dtlsConfigBuilder.getIncompleteConfig(); - - // Handle secure address - if (incompleteConfig.getAddress() == null) { - if (localAddress == null) { - localAddress = new InetSocketAddress(0); - } - dtlsConfigBuilder.setAddress(localAddress); - } else if (localAddress != null && !localAddress.equals(incompleteConfig.getAddress())) { - throw new IllegalStateException(String.format( - "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for address: %s != %s", - localAddress, incompleteConfig.getAddress())); - } - - return createLeshanClient(endpoint, localAddress, objectEnablers, dataSenders, coapConfig, dtlsConfigBuilder, - this.trustStore, endpointFactory, engineFactory, bootstrapConsistencyChecker, additionalAttributes, - bsAdditionalAttributes, encoder, decoder, executor, linkSerializer, attributeParser); + return createLeshanClient(endpoint, objectEnablers, dataSenders, this.trustStore, engineFactory, + bootstrapConsistencyChecker, additionalAttributes, bsAdditionalAttributes, encoder, decoder, executor, + linkSerializer, attributeParser, endpointsProvider); } /** @@ -413,14 +305,9 @@ protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) { * See all the setters of this builder for more documentation about parameters. * * @param endpoint The endpoint name for this client. - * @param localAddress The local address used for unsecured connection. * @param objectEnablers The list of object enablers. An enabler adds to support for a given LWM2M object to the * client. - * @param coapConfig The coap config used to create {@link CoapEndpoint} and {@link CoapServer}. - * @param dtlsConfigBuilder The dtls config used to create the {@link DTLSConnector}. * @param trustStore The optional trust store for verifying X.509 server certificates. - * @param endpointFactory The factory which will create the {@link CoapEndpoint}. - * @param engineFactory The factory which will create the {@link RegistrationEngine}. * @param checker Used to check if client state is consistent after a bootstrap session. * @param additionalAttributes Some extra (out-of-spec) attributes to add to the register request. * @param bsAdditionalAttributes Some extra (out-of-spec) attributes to add to the bootstrap request. @@ -433,15 +320,14 @@ protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) { * * @return the new {@link LeshanClient} */ - protected LeshanClient createLeshanClient(String endpoint, InetSocketAddress localAddress, - List objectEnablers, List dataSenders, Configuration coapConfig, - Builder dtlsConfigBuilder, List trustStore, EndpointFactory endpointFactory, - RegistrationEngineFactory engineFactory, BootstrapConsistencyChecker checker, - Map additionalAttributes, Map bsAdditionalAttributes, LwM2mEncoder encoder, - LwM2mDecoder decoder, ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, - LwM2mAttributeParser attributeParser) { - return new LeshanClient(endpoint, localAddress, objectEnablers, dataSenders, coapConfig, dtlsConfigBuilder, - trustStore, endpointFactory, engineFactory, checker, additionalAttributes, bsAdditionalAttributes, - encoder, decoder, sharedExecutor, linkSerializer, attributeParser); + protected LeshanClient createLeshanClient(String endpoint, List objectEnablers, + List dataSenders, List trustStore, RegistrationEngineFactory engineFactory, + BootstrapConsistencyChecker checker, Map additionalAttributes, + Map bsAdditionalAttributes, LwM2mEncoder encoder, LwM2mDecoder decoder, + ScheduledExecutorService sharedExecutor, LinkSerializer linkSerializer, + LwM2mAttributeParser attributeParser, LwM2mClientEndpointsProvider endpointsProvider) { + return new LeshanClient(endpoint, objectEnablers, dataSenders, trustStore, engineFactory, checker, + additionalAttributes, bsAdditionalAttributes, encoder, decoder, sharedExecutor, linkSerializer, + attributeParser, endpointsProvider); } } diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/CertPathUtil.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/CertPathUtil.java new file mode 100644 index 0000000000..4664312065 --- /dev/null +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/CertPathUtil.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2019 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Bosch Software Innovations - initial creation + ******************************************************************************/ +package org.eclipse.leshan.client.bootstrap; + +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO TL : this is copy / paste from californium need to see if there is a better way to handle this. + +/** + * Certificate Path Utility. + *

+ * Generates certificate path, check intended certificates usage and verify certificate paths. + * + * This implementation considers the below listed RFC's by: + *

+ *
self-signed top-level certificate
+ *
Self-signed top-level certificate are removed before validation. This is done before sending such a certificate + * path and before validating a received certificate path in order to support peers, which doesn't remove it.
+ *
intermediate authorities certificate
+ *
Intermediate authorities certificate are removed before validation. This is done before sending such a + * certificate path, when a certificate authorities list was received before, and before validating a received + * certificate path in order to support peers, which doesn't remove them.
+ *
+ * + * References: RFC5246, Section 7.4.2, + * Server Certificate + *

+ * "Because certificate validation requires that root keys be distributed independently, the self-signed certificate + * that specifies the root certificate authority MAY be omitted from the chain, under the assumption that the remote end + * must already possess it in order to validate it in any case." + *

+ * + * RFC5246, Section 7.4.6, Client + * Certificate + *

+ * "If the certificate_authorities list in the certificate request message was non-empty, one of the certificates in the + * certificate chain SHOULD be issued by one of the listed CAs." + *

+ * + * RFC5280, Section 6, Certification Path + * Validation + *

+ * "Valid paths begin with certificates issued by a trust anchor." ... "The procedure performed to obtain this sequence + * of certificates is outside the scope of this specification". + *

+ * + * @since 2.1 + */ +public class CertPathUtil { + + private static final Logger LOGGER = LoggerFactory.getLogger(CertPathUtil.class); + + /** + * OID for server authentication in extended key. + */ + private static final String SERVER_AUTHENTICATION = "1.3.6.1.5.5.7.3.1"; + + /** + * OID for client authentication in extended key. + */ + private static final String CLIENT_AUTHENTICATION = "1.3.6.1.5.5.7.3.2"; + + /** + * Bit for digital signature in key usage. + */ + private static final int KEY_USAGE_SIGNATURE = 0; + + /** + * Bit for certificate signing in key usage. + */ + private static final int KEY_USAGE_CERTIFICATE_SIGNING = 5; + + /** + * Check, if certificate is intended to be used to verify a signature of an other certificate. + * + * @param cert certificate to check. + * @return {@code true}, if certificate is intended to be used to verify a signature of an other certificate, + * {@code false}, otherwise. + */ + public static boolean canBeUsedToVerifySignature(X509Certificate cert) { + + if (cert.getBasicConstraints() < 0) { + LOGGER.debug("certificate: {}, not for CA!", cert.getSubjectX500Principal()); + return false; + } + if ((cert.getKeyUsage() != null && !cert.getKeyUsage()[KEY_USAGE_CERTIFICATE_SIGNING])) { + LOGGER.debug("certificate: {}, not for certificate signing!", cert.getSubjectX500Principal()); + return false; + } + return true; + } + + /** + * Check, if certificate is intended to be used for client or server authentication. + * + * @param cert certificate to check. + * @param client {@code true} for client authentication, {@code false} for server authentication. + * @return {@code true}, if certificate is intended to be used for client or server authentication, {@code false}, + * otherwise. + */ + public static boolean canBeUsedForAuthentication(X509Certificate cert, boolean client) { + + // KeyUsage is an optional extension which may be used to restrict + // the way the key can be used. + // https://tools.ietf.org/html/rfc5280#section-4.2.1.3 + // If this extension is used, we check if digitalsignature usage is + // present. + // (For more details see: + // https://github.com/eclipse/californium/issues/748) + if ((cert.getKeyUsage() != null && !cert.getKeyUsage()[KEY_USAGE_SIGNATURE])) { + LOGGER.debug("certificate: {}, not for signing!", cert.getSubjectX500Principal()); + return false; + } + try { + List list = cert.getExtendedKeyUsage(); + if (list != null && !list.isEmpty()) { + LOGGER.trace("certificate: {}", cert.getSubjectX500Principal()); + final String authentication = client ? CLIENT_AUTHENTICATION : SERVER_AUTHENTICATION; + boolean foundUsage = false; + for (String extension : list) { + LOGGER.trace(" extkeyusage {}", extension); + if (authentication.equals(extension)) { + foundUsage = true; + } + } + if (!foundUsage) { + LOGGER.debug("certificate: {}, not for {}!", cert.getSubjectX500Principal(), + client ? "client" : "server"); + return false; + } + } else { + LOGGER.debug("certificate: {}, no extkeyusage!", cert.getSubjectX500Principal()); + } + } catch (CertificateParsingException e) { + LOGGER.warn("x509 certificate:", e); + } + return true; + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/DefaultBootstrapConsistencyChecker.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/DefaultBootstrapConsistencyChecker.java similarity index 88% rename from leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/DefaultBootstrapConsistencyChecker.java rename to leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/DefaultBootstrapConsistencyChecker.java index fad30df292..5d5a60fb04 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/DefaultBootstrapConsistencyChecker.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/DefaultBootstrapConsistencyChecker.java @@ -13,14 +13,11 @@ * Contributors: * Sierra Wireless - initial API and implementation *******************************************************************************/ -package org.eclipse.leshan.client.californium.bootstrap; +package org.eclipse.leshan.client.bootstrap; import java.security.cert.X509Certificate; import java.util.List; -import org.eclipse.californium.elements.util.CertPathUtil; -import org.eclipse.leshan.client.bootstrap.BaseBootstrapConsistencyChecker; -import org.eclipse.leshan.client.bootstrap.BootstrapConsistencyChecker; import org.eclipse.leshan.client.servers.DmServerInfo; import org.eclipse.leshan.client.servers.ServerInfo; import org.eclipse.leshan.core.SecurityMode; @@ -32,7 +29,7 @@ */ public class DefaultBootstrapConsistencyChecker extends BaseBootstrapConsistencyChecker { - private OscoreValidator oscoreValidator = new OscoreValidator(); + private final OscoreValidator oscoreValidator = new OscoreValidator(); @Override protected void checkBootstrapServerInfo(ServerInfo bootstrapServerInfo, List errors) { diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/ClientEndpointToolbox.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/ClientEndpointToolbox.java new file mode 100644 index 0000000000..4c31fd581d --- /dev/null +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/ClientEndpointToolbox.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.endpoint; + +import org.eclipse.leshan.core.link.LinkSerializer; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeParser; +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.node.codec.LwM2mDecoder; +import org.eclipse.leshan.core.node.codec.LwM2mEncoder; + +public class ClientEndpointToolbox { + + private final LwM2mDecoder decoder; + private final LwM2mEncoder encoder; + private final LinkSerializer linkSerializer; + private final LwM2mModel model; + private final LwM2mAttributeParser attributeParser; + + public ClientEndpointToolbox(LwM2mDecoder decoder, LwM2mEncoder encoder, LinkSerializer linkSerializer, + LwM2mModel model, LwM2mAttributeParser attributeParser) { + this.decoder = decoder; + this.encoder = encoder; + this.linkSerializer = linkSerializer; + this.model = model; + this.attributeParser = attributeParser; + } + + public LwM2mDecoder getDecoder() { + return decoder; + } + + public LwM2mEncoder getEncoder() { + return encoder; + } + + public LinkSerializer getLinkSerializer() { + return linkSerializer; + } + + public LwM2mModel getModel() { + return model; + } + + public LwM2mAttributeParser getAttributeParser() { + return attributeParser; + } +} diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/DefaultEndpointsManager.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/DefaultEndpointsManager.java new file mode 100644 index 0000000000..0923b131d8 --- /dev/null +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/DefaultEndpointsManager.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2017 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE + *******************************************************************************/ +package org.eclipse.leshan.client.endpoint; + +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.leshan.client.EndpointsManager; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.client.servers.ServerInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An {@link EndpointsManager} which supports only 1 server. + */ +public class DefaultEndpointsManager implements EndpointsManager { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultEndpointsManager.class); + + // utility + protected LwM2mClientEndpointsProvider endpointProvider; + protected ClientEndpointToolbox toolbox; + protected List trustStore; + + // state + protected boolean started = false; + + public DefaultEndpointsManager(LwM2mClientEndpointsProvider endpointProvider, ClientEndpointToolbox toolbox, + List trustStore) { + this.endpointProvider = endpointProvider; + this.toolbox = toolbox; + this.trustStore = trustStore; + } + + @Override + public synchronized ServerIdentity createEndpoint(ServerInfo serverInfo, boolean clientInitiatedOnly) { + // Clear previous endpoint + endpointProvider.destroyEndpoints(); + + // Create new endpoint + return endpointProvider.createEndpoint(serverInfo, clientInitiatedOnly, trustStore, toolbox); + } + + @Override + public synchronized Collection createEndpoints(Collection serverInfo, + boolean clientInitiatedOnly) { + if (serverInfo == null || serverInfo.isEmpty()) + return null; + else { + // TODO support multi server + if (serverInfo.size() > 1) { + LOG.warn( + "CaliforniumEndpointsManager support only connection to 1 LWM2M server, first server will be used from the server list of {}", + serverInfo.size()); + } + ServerInfo firstServer = serverInfo.iterator().next(); + Collection servers = new ArrayList<>(1); + servers.add(createEndpoint(firstServer, clientInitiatedOnly)); + return servers; + } + } + + @Override + public long getMaxCommunicationPeriodFor(ServerIdentity server, long lifetimeInMs) { + LwM2mClientEndpoint endpoint = getEndpoint(server); + if (endpoint != null) { + return endpoint.getMaxCommunicationPeriodFor(lifetimeInMs); + } + // TODO TL : we should better handle this case + return lifetimeInMs; + } + + @Override + public synchronized void forceReconnection(ServerIdentity server, boolean resume) { + LwM2mClientEndpoint endpoint = getEndpoint(server); + if (endpoint != null) { + endpoint.forceReconnection(server, resume); + } + } + + public synchronized LwM2mClientEndpoint getEndpoint(ServerIdentity server) { + return endpointProvider.getEndpoint(server); + } + + @Override + public synchronized void start() { + endpointProvider.start(); + } + + @Override + public synchronized void stop() { + endpointProvider.stop(); + + } + + @Override + public synchronized void destroy() { + endpointProvider.destroy(); + } +} diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/LwM2mClientEndpoint.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/LwM2mClientEndpoint.java new file mode 100644 index 0000000000..ed274b5a76 --- /dev/null +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/LwM2mClientEndpoint.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.endpoint; + +import java.net.URI; + +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.request.UplinkRequest; +import org.eclipse.leshan.core.response.ErrorCallback; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.ResponseCallback; + +public interface LwM2mClientEndpoint { + + Protocol getProtocol(); + + URI getURI(); + + void forceReconnection(ServerIdentity server, boolean resume); + + long getMaxCommunicationPeriodFor(long lifetimeInMs); + + T send(ServerIdentity server, UplinkRequest request, long timeoutInMs) + throws InterruptedException; + + void send(ServerIdentity server, UplinkRequest request, + ResponseCallback responseCallback, ErrorCallback errorCallback, long timeoutInMs); + +} diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/LwM2mClientEndpointsProvider.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/LwM2mClientEndpointsProvider.java new file mode 100644 index 0000000000..76ecc3c113 --- /dev/null +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/endpoint/LwM2mClientEndpointsProvider.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.endpoint; + +import java.security.cert.Certificate; +import java.util.Collection; +import java.util.List; + +import org.eclipse.leshan.client.request.DownlinkRequestReceiver; +import org.eclipse.leshan.client.resource.LwM2mObjectTree; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.client.servers.ServerInfo; + +public interface LwM2mClientEndpointsProvider { + + void init(LwM2mObjectTree objectTree, DownlinkRequestReceiver requestReceiver, ClientEndpointToolbox toolbox); + + ServerIdentity createEndpoint(ServerInfo serverInfo, boolean clientInitiatedOnly, List trustStore, + ClientEndpointToolbox toolbox); + + Collection createEndpoints(Collection serverInfo, boolean clientInitiatedOnly, + List trustStore, ClientEndpointToolbox toolbox); + + void destroyEndpoints(); + + void start(); + + List getEndpoints(); + + LwM2mClientEndpoint getEndpoint(ServerIdentity server); + + void stop(); + + void destroy(); + +} diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java index 0e6b016fb7..c6a91a21b3 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java @@ -36,7 +36,7 @@ import org.eclipse.leshan.client.bootstrap.BootstrapHandler; import org.eclipse.leshan.client.bootstrap.InvalidStateException; import org.eclipse.leshan.client.observer.LwM2mClientObserver; -import org.eclipse.leshan.client.request.LwM2mRequestSender; +import org.eclipse.leshan.client.request.UplinkRequestSender; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.LwM2mObjectTree; import org.eclipse.leshan.client.servers.DmServerInfo; @@ -79,7 +79,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine { private static final Logger LOG = LoggerFactory.getLogger(DefaultRegistrationEngine.class); private static final long NOW = 0; - private static final ServerIdentity ALL = new ServerIdentity(null, null); + private static final ServerIdentity ALL = new ServerIdentity(null, null, null); // Timeout for bootstrap/register/update request private final long requestTimeoutInMs; @@ -90,11 +90,11 @@ public class DefaultRegistrationEngine implements RegistrationEngine { // Time between bootstrap retry should incremental private final int retryWaitingTimeInMs; // Time between 2 update requests (used only if it is smaller than the lifetime) - private Integer communicationPeriodInMs; + private final Integer communicationPeriodInMs; // True if client should re-initiate a connection (DTLS) on registration update - private boolean reconnectOnUpdate; + private final boolean reconnectOnUpdate; // True if client should try to resume connection if possible. - private boolean resumeOnConnect; + private final boolean resumeOnConnect; // True if client use queueMode : for now this just add Q parameter on register request. private final boolean queueMode; @@ -114,7 +114,7 @@ private static enum Status { private final AtomicReference currentBootstrapServer; // helpers - private final LwM2mRequestSender sender; + private final UplinkRequestSender sender; private final BootstrapHandler bootstrapHandler; private final EndpointsManager endpointsManager; private final LwM2mClientObserver observer; @@ -124,12 +124,12 @@ private static enum Status { private Future bootstrapFuture; private Future registerFuture; private Future updateFuture; - private Object taskLock = new Object(); // a lock to avoid several task to be executed at the same time + private final Object taskLock = new Object(); // a lock to avoid several task to be executed at the same time private final ScheduledExecutorService schedExecutor; private final boolean attachedExecutor; public DefaultRegistrationEngine(String endpoint, LwM2mObjectTree objectTree, EndpointsManager endpointsManager, - LwM2mRequestSender requestSender, BootstrapHandler bootstrapState, LwM2mClientObserver observer, + UplinkRequestSender requestSender, BootstrapHandler bootstrapState, LwM2mClientObserver observer, Map additionalAttributes, Map bsAdditionalAttributes, ScheduledExecutorService executor, long requestTimeoutInMs, long deregistrationTimeoutInMs, int bootstrapSessionTimeoutInSec, int retryWaitingTimeInMs, Integer communicationPeriodInMs, @@ -705,8 +705,8 @@ public void destroy(boolean deregister) { private class QueueUpdateTask implements Runnable { - private RegistrationUpdate registrationUpdate; - private ServerIdentity server; + private final RegistrationUpdate registrationUpdate; + private final ServerIdentity server; public QueueUpdateTask(ServerIdentity server, RegistrationUpdate registrationUpdate) { this.registrationUpdate = registrationUpdate; diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java index 2a9e8702b9..a441626518 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java @@ -22,7 +22,7 @@ import org.eclipse.leshan.client.EndpointsManager; import org.eclipse.leshan.client.bootstrap.BootstrapHandler; import org.eclipse.leshan.client.observer.LwM2mClientObserver; -import org.eclipse.leshan.client.request.LwM2mRequestSender; +import org.eclipse.leshan.client.request.UplinkRequestSender; import org.eclipse.leshan.client.resource.LwM2mObjectTree; import org.eclipse.leshan.core.request.ContentFormat; @@ -48,7 +48,7 @@ public DefaultRegistrationEngineFactory() { @Override public RegistrationEngine createRegistratioEngine(String endpoint, LwM2mObjectTree objectTree, - EndpointsManager endpointsManager, LwM2mRequestSender requestSender, BootstrapHandler bootstrapState, + EndpointsManager endpointsManager, UplinkRequestSender requestSender, BootstrapHandler bootstrapState, LwM2mClientObserver observer, Map additionalAttributes, Map bsAdditionalAttributes, Set supportedContentFormat, ScheduledExecutorService sharedExecutor) { diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java index 45ea9ce794..2f54163e19 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java @@ -22,7 +22,7 @@ import org.eclipse.leshan.client.EndpointsManager; import org.eclipse.leshan.client.bootstrap.BootstrapHandler; import org.eclipse.leshan.client.observer.LwM2mClientObserver; -import org.eclipse.leshan.client.request.LwM2mRequestSender; +import org.eclipse.leshan.client.request.UplinkRequestSender; import org.eclipse.leshan.client.resource.LwM2mObjectTree; import org.eclipse.leshan.core.request.ContentFormat; @@ -32,7 +32,7 @@ public interface RegistrationEngineFactory { RegistrationEngine createRegistratioEngine(String endpoint, LwM2mObjectTree objectTree, - EndpointsManager endpointsManager, LwM2mRequestSender requestSender, BootstrapHandler bootstrapState, + EndpointsManager endpointsManager, UplinkRequestSender requestSender, BootstrapHandler bootstrapState, LwM2mClientObserver observer, Map additionalAttributes, Map bsAdditionalAttributes, Set supportedContentFormat, ScheduledExecutorService sharedExecutor); diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DefaultDownlinkReceiver.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DefaultDownlinkReceiver.java new file mode 100644 index 0000000000..e388f600b3 --- /dev/null +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DefaultDownlinkReceiver.java @@ -0,0 +1,403 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.request; + +import org.eclipse.leshan.client.bootstrap.BootstrapHandler; +import org.eclipse.leshan.client.engine.RegistrationEngine; +import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; +import org.eclipse.leshan.client.resource.LwM2mObjectTree; +import org.eclipse.leshan.client.resource.LwM2mRootEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.request.BootstrapDeleteRequest; +import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; +import org.eclipse.leshan.core.request.BootstrapFinishRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; +import org.eclipse.leshan.core.request.BootstrapWriteRequest; +import org.eclipse.leshan.core.request.CancelCompositeObservationRequest; +import org.eclipse.leshan.core.request.CancelObservationRequest; +import org.eclipse.leshan.core.request.CreateRequest; +import org.eclipse.leshan.core.request.DeleteRequest; +import org.eclipse.leshan.core.request.DiscoverRequest; +import org.eclipse.leshan.core.request.DownlinkRequest; +import org.eclipse.leshan.core.request.DownlinkRequestVisitor; +import org.eclipse.leshan.core.request.ExecuteRequest; +import org.eclipse.leshan.core.request.LwM2mRequest; +import org.eclipse.leshan.core.request.ObserveCompositeRequest; +import org.eclipse.leshan.core.request.ObserveRequest; +import org.eclipse.leshan.core.request.ReadCompositeRequest; +import org.eclipse.leshan.core.request.ReadRequest; +import org.eclipse.leshan.core.request.SimpleDownlinkRequest; +import org.eclipse.leshan.core.request.WriteAttributesRequest; +import org.eclipse.leshan.core.request.WriteCompositeRequest; +import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.response.BootstrapDeleteResponse; +import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; +import org.eclipse.leshan.core.response.BootstrapFinishResponse; +import org.eclipse.leshan.core.response.BootstrapReadResponse; +import org.eclipse.leshan.core.response.BootstrapWriteResponse; +import org.eclipse.leshan.core.response.CancelCompositeObservationResponse; +import org.eclipse.leshan.core.response.CancelObservationResponse; +import org.eclipse.leshan.core.response.CreateResponse; +import org.eclipse.leshan.core.response.DeleteResponse; +import org.eclipse.leshan.core.response.DiscoverResponse; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.ObserveCompositeResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.core.response.ReadCompositeResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.SendableResponse; +import org.eclipse.leshan.core.response.WriteAttributesResponse; +import org.eclipse.leshan.core.response.WriteCompositeResponse; +import org.eclipse.leshan.core.response.WriteResponse; + +public class DefaultDownlinkReceiver implements DownlinkRequestReceiver { + + private final RegistrationEngine registrationEngine; + private final BootstrapHandler bootstrapHandler; + private final LwM2mRootEnabler rootEnabler; + private final LwM2mObjectTree objectTree; + + public DefaultDownlinkReceiver(BootstrapHandler bootstrapHandler, LwM2mRootEnabler rootEnabler, + LwM2mObjectTree objectTree, RegistrationEngine registrationEngine) { + this.bootstrapHandler = bootstrapHandler; + this.rootEnabler = rootEnabler; + this.objectTree = objectTree; + this.registrationEngine = registrationEngine; + } + + @Override + public SendableResponse requestReceived(ServerIdentity server, + DownlinkRequest request) { + + // Check if this is a well-known server + if (!registrationEngine.isAllowedToCommunicate(server)) { + ErrorResponseCreator errorResponseCreator = new ErrorResponseCreator<>( + ResponseCode.INTERNAL_SERVER_ERROR, "server not allow to communicate"); + request.accept(errorResponseCreator); + return errorResponseCreator.getResponse(); + } + // Handle the request + RequestHandler requestHandler = new RequestHandler(server); + request.accept(requestHandler); + return requestHandler.getResponse(); + } + + @Override + public void onError(ServerIdentity identity, Exception e, + Class> requestType) { + } + + public class RequestHandler implements DownlinkRequestVisitor { + + private final ServerIdentity sender; + private SendableResponse response; + + public RequestHandler(ServerIdentity identity) { + this.sender = identity; + } + + @Override + public void visit(ReadRequest request) { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + response = toSendableResponse(ReadResponse.notFound()); + } else { + response = toSendableResponse(objectEnabler.read(sender, request)); + } + } + + @Override + public void visit(DiscoverRequest request) { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + response = toSendableResponse(DiscoverResponse.notFound()); + } else { + response = toSendableResponse(objectEnabler.discover(sender, request)); + } + } + + @Override + public void visit(WriteRequest request) { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + response = toSendableResponse(WriteResponse.notFound()); + } else { + response = toSendableResponse(objectEnabler.write(sender, request)); + } + } + + @Override + public void visit(WriteAttributesRequest request) { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + response = toSendableResponse(WriteAttributesResponse.notFound()); + } else { + response = toSendableResponse(objectEnabler.writeAttributes(sender, request)); + } + + } + + @Override + public void visit(ExecuteRequest request) { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + response = toSendableResponse(ExecuteResponse.notFound()); + } else { + response = toSendableResponse(objectEnabler.execute(sender, request)); + } + + } + + @Override + public void visit(CreateRequest request) { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + response = toSendableResponse(CreateResponse.notFound()); + } else { + response = toSendableResponse(objectEnabler.create(sender, request)); + } + + } + + @Override + public void visit(DeleteRequest request) { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + response = toSendableResponse(DeleteResponse.notFound()); + } else { + response = toSendableResponse(objectEnabler.delete(sender, request)); + } + + } + + @Override + public void visit(ObserveRequest request) { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + response = toSendableResponse(ObserveResponse.notFound()); + } else { + response = toSendableResponse(objectEnabler.observe(sender, request)); + } + } + + @Override + public void visit(CancelObservationRequest request) { + // TODO TL : check if there is something to call here + } + + @Override + public void visit(ReadCompositeRequest request) { + response = toSendableResponse(rootEnabler.read(sender, request)); + } + + @Override + public void visit(ObserveCompositeRequest request) { + response = toSendableResponse(rootEnabler.observe(sender, request)); + } + + @Override + public void visit(CancelCompositeObservationRequest request) { + // TODO TL : check if there is something to call here + } + + @Override + public void visit(WriteCompositeRequest request) { + response = toSendableResponse(rootEnabler.write(sender, request)); + } + + @Override + public void visit(BootstrapDiscoverRequest request) { + if (request.getPath().isRoot()) { + response = toSendableResponse(bootstrapHandler.discover(sender, request)); + } else { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + response = toSendableResponse(DeleteResponse.notFound()); + } else { + response = toSendableResponse(objectEnabler.discover(sender, request)); + } + } + } + + @Override + public void visit(BootstrapWriteRequest request) { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + // NOT_FOUND is not defined for Bootstrap Write ... and no other code match, + // The spec says : + // + // If any operation cannot be completed in the client and the reason cannot + // be described by a more specific response code, then a generic response code + // of "5.00 Internal Server Error" MUST be returned. + // + // See : + // http://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Transport-V1_1_1-20190617-A.html#6-7-0-67-Response-Codes + response = toSendableResponse(BootstrapWriteResponse.internalServerError("object not supported")); + } else { + response = toSendableResponse(objectEnabler.write(sender, request)); + } + } + + @Override + public void visit(BootstrapReadRequest request) { + LwM2mObjectEnabler objectEnabler = getObjectEnabler(request); + if (objectEnabler == null) { + response = toSendableResponse(BootstrapReadResponse.notFound()); + } else { + response = toSendableResponse(objectEnabler.read(sender, request)); + } + } + + @Override + public void visit(BootstrapDeleteRequest request) { + response = toSendableResponse(bootstrapHandler.delete(sender, request)); + } + + @Override + public void visit(BootstrapFinishRequest request) { + response = bootstrapHandler.finished(sender, request); + + } + + private SendableResponse toSendableResponse(R response) { + return new SendableResponse(response); + } + + private LwM2mObjectEnabler getObjectEnabler(SimpleDownlinkRequest request) { + // TODO TL : handle path has no object id + LwM2mPath path = request.getPath(); + return objectTree.getObjectEnabler(path.getObjectId()); + } + + @SuppressWarnings("unchecked") + public SendableResponse getResponse() { + return (SendableResponse) response; + } + } + + public class ErrorResponseCreator implements DownlinkRequestVisitor { + + private final ResponseCode code; + private final String errorMessage; + private LwM2mResponse response; + + public ErrorResponseCreator(ResponseCode code, String errorMessage) { + this.code = code; + this.errorMessage = errorMessage; + } + + @Override + public void visit(ReadRequest request) { + response = new ReadResponse(code, null, errorMessage); + } + + @Override + public void visit(DiscoverRequest request) { + response = new DiscoverResponse(code, null, errorMessage); + } + + @Override + public void visit(WriteRequest request) { + response = new WriteResponse(code, errorMessage); + } + + @Override + public void visit(WriteAttributesRequest request) { + response = new WriteAttributesResponse(code, errorMessage); + } + + @Override + public void visit(ExecuteRequest request) { + response = new ExecuteResponse(code, errorMessage); + } + + @Override + public void visit(CreateRequest request) { + response = new CreateResponse(code, null, errorMessage); + } + + @Override + public void visit(DeleteRequest request) { + response = new DeleteResponse(code, errorMessage); + } + + @Override + public void visit(ObserveRequest request) { + response = new ObserveResponse(code, null, null, null, errorMessage); + } + + @Override + public void visit(CancelObservationRequest request) { + // TODO TL :we should check if this is really handle + response = new CancelObservationResponse(code, null, null, null, errorMessage); + } + + @Override + public void visit(ReadCompositeRequest request) { + response = new ReadCompositeResponse(code, null, errorMessage, null); + } + + @Override + public void visit(ObserveCompositeRequest request) { + response = new ObserveCompositeResponse(code, null, errorMessage, null, null); + } + + @Override + public void visit(CancelCompositeObservationRequest request) { + // TODO TL : we should check if this is really handle + response = new CancelCompositeObservationResponse(code, null, errorMessage, null, null); + } + + @Override + public void visit(WriteCompositeRequest request) { + response = new WriteCompositeResponse(code, errorMessage, null); + } + + @Override + public void visit(BootstrapDiscoverRequest request) { + response = new BootstrapDiscoverResponse(code, null, errorMessage); + } + + @Override + public void visit(BootstrapWriteRequest request) { + response = new BootstrapWriteResponse(code, errorMessage); + } + + @Override + public void visit(BootstrapReadRequest request) { + response = new BootstrapReadResponse(code, null, errorMessage); + } + + @Override + public void visit(BootstrapDeleteRequest request) { + response = new BootstrapDeleteResponse(code, errorMessage); + } + + @Override + public void visit(BootstrapFinishRequest request) { + response = new BootstrapFinishResponse(code, errorMessage); + + } + + @SuppressWarnings("unchecked") + public SendableResponse getResponse() { + return (SendableResponse) response; + } + } +} diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DefaultUplinkRequestSender.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DefaultUplinkRequestSender.java new file mode 100644 index 0000000000..ecd99c98b6 --- /dev/null +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DefaultUplinkRequestSender.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.request; + +import org.eclipse.leshan.client.endpoint.LwM2mClientEndpoint; +import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.request.UplinkRequest; +import org.eclipse.leshan.core.response.ErrorCallback; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.ResponseCallback; + +public class DefaultUplinkRequestSender implements UplinkRequestSender { + + private final LwM2mClientEndpointsProvider endpointsProvider; + + public DefaultUplinkRequestSender(LwM2mClientEndpointsProvider endpointsProvider) { + this.endpointsProvider = endpointsProvider; + } + + @Override + public T send(ServerIdentity server, UplinkRequest request, long timeoutInMs) + throws InterruptedException { + LwM2mClientEndpoint endpoint = endpointsProvider.getEndpoint(server); + return endpoint.send(server, request, timeoutInMs); + } + + @Override + public void send(ServerIdentity server, UplinkRequest request, long timeoutInMs, + ResponseCallback responseCallback, ErrorCallback errorCallback) { + LwM2mClientEndpoint endpoint = endpointsProvider.getEndpoint(server); + endpoint.send(server, request, responseCallback, errorCallback, timeoutInMs); + } +} diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DownlinkRequestReceiver.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DownlinkRequestReceiver.java new file mode 100644 index 0000000000..3d1d766f4f --- /dev/null +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/DownlinkRequestReceiver.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.request; + +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.request.DownlinkRequest; +import org.eclipse.leshan.core.request.LwM2mRequest; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.SendableResponse; + +public interface DownlinkRequestReceiver { + + SendableResponse requestReceived(ServerIdentity identity, DownlinkRequest request); + + void onError(ServerIdentity identity, Exception e, + Class> requestType); +} diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/LwM2mRequestSender.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/UplinkRequestSender.java similarity index 94% rename from leshan-client-core/src/main/java/org/eclipse/leshan/client/request/LwM2mRequestSender.java rename to leshan-client-core/src/main/java/org/eclipse/leshan/client/request/UplinkRequestSender.java index 5d3fd4e792..f6356a82ad 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/LwM2mRequestSender.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/UplinkRequestSender.java @@ -30,9 +30,9 @@ import org.eclipse.leshan.core.response.ResponseCallback; /** - * A {@link LwM2mRequestSender} is responsible to send LWM2M {@link UplinkRequest} for a given {@link ServerIdentity}. + * A {@link UplinkRequestSender} is responsible to send LWM2M {@link UplinkRequest} for a given {@link ServerIdentity}. */ -public interface LwM2mRequestSender { +public interface UplinkRequestSender { /** * Send a Lightweight M2M request synchronously. Will block until a response is received from the remote server. @@ -90,8 +90,4 @@ T send(ServerIdentity server, UplinkRequest request void send(ServerIdentity server, UplinkRequest request, long timeoutInMs, ResponseCallback responseCallback, ErrorCallback errorCallback); - /** - * Destroy the sender. Free all resources. Sender can not be used anymore. - */ - void destroy(); } diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/send/DataSenderManager.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/send/DataSenderManager.java index 8a107dd2d0..3f312c4910 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/send/DataSenderManager.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/send/DataSenderManager.java @@ -18,7 +18,7 @@ import java.util.List; import java.util.Map; -import org.eclipse.leshan.client.request.LwM2mRequestSender; +import org.eclipse.leshan.client.request.UplinkRequestSender; import org.eclipse.leshan.client.resource.LwM2mRootEnabler; import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.Destroyable; @@ -38,10 +38,10 @@ public class DataSenderManager implements Startable, Stoppable, Destroyable { private final Map dataSenders; private final LwM2mRootEnabler rootEnabler; - private final LwM2mRequestSender requestSender; + private final UplinkRequestSender requestSender; public DataSenderManager(Map dataSenders, LwM2mRootEnabler rootEnabler, - LwM2mRequestSender requestSender) { + UplinkRequestSender requestSender) { this.rootEnabler = rootEnabler; this.requestSender = requestSender; this.dataSenders = dataSenders != null ? dataSenders : new HashMap<>(); diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerIdentity.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerIdentity.java index 3c7cafee1c..65b6070df3 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerIdentity.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerIdentity.java @@ -16,6 +16,7 @@ package org.eclipse.leshan.client.servers; import java.net.InetSocketAddress; +import java.net.URI; import org.eclipse.leshan.core.request.Identity; @@ -28,7 +29,7 @@ public class ServerIdentity { * Identity for system calls. */ public final static ServerIdentity SYSTEM = new ServerIdentity( - Identity.unsecure(InetSocketAddress.createUnresolved(Role.SYSTEM.toString(), 1)), null, Role.SYSTEM); + Identity.unsecure(InetSocketAddress.createUnresolved(Role.SYSTEM.toString(), 1)), null, Role.SYSTEM, null); public enum Role { /** @@ -49,15 +50,17 @@ public enum Role { private final Identity identity; private final Long id; private final Role role; + private final URI uri; - public ServerIdentity(Identity identity, Long id) { - this(identity, id, Role.LWM2M_SERVER); + public ServerIdentity(Identity identity, Long id, URI uri) { + this(identity, id, Role.LWM2M_SERVER, uri); } - public ServerIdentity(Identity identity, Long id, Role role) { + public ServerIdentity(Identity identity, Long id, Role role, URI uri) { this.identity = identity; this.id = id; this.role = role; + this.uri = uri; } public Identity getIdentity() { @@ -105,14 +108,6 @@ public boolean isSystem() { } public String getUri() { - StringBuilder uri = new StringBuilder(); - if (identity.isSecure()) - uri.append("coaps://"); - else - uri.append("coap://"); - uri.append(identity.getPeerAddress().getHostString()); - uri.append(":"); - uri.append(identity.getPeerAddress().getPort()); return uri.toString(); } diff --git a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ServerTest.java b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ServerTest.java deleted file mode 100644 index d19cf2e602..0000000000 --- a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ServerTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2019 Sierra Wireless and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v2.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v20.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.html. - * - * Contributors: - * Sierra Wireless - initial API and implementation - *******************************************************************************/ -package org.eclipse.leshan.client.util; - -import static org.junit.Assert.assertEquals; - -import java.net.InetSocketAddress; - -import org.eclipse.leshan.client.servers.ServerIdentity; -import org.eclipse.leshan.core.request.Identity; -import org.junit.Test; - -public class ServerTest { - - @Test - public void test_server_getUri_with_localhost_hostname() { - ServerIdentity server = new ServerIdentity(Identity.unsecure(new InetSocketAddress("localhost", 5683)), 123l); - assertEquals("coap://localhost:5683", server.getUri()); - } - - @Test - public void test_server_getUri_with_unknown_hostname() { - ServerIdentity server = new ServerIdentity(Identity.unsecure(new InetSocketAddress("unknownhost", 5683)), 123l); - assertEquals("coap://unknownhost:5683", server.getUri()); - } - - @Test - public void test_server_getUri_with_ipaddress() { - ServerIdentity server = new ServerIdentity(Identity.unsecure(new InetSocketAddress("50.0.0.1", 5683)), 123l); - assertEquals("coap://50.0.0.1:5683", server.getUri()); - } -} diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/DtlsSessionLogger.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/DtlsSessionLogger.java new file mode 100644 index 0000000000..22870f9e2c --- /dev/null +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/DtlsSessionLogger.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.demo; + +import org.eclipse.californium.scandium.dtls.ClientHandshaker; +import org.eclipse.californium.scandium.dtls.DTLSContext; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.californium.scandium.dtls.Handshaker; +import org.eclipse.californium.scandium.dtls.ResumingClientHandshaker; +import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker; +import org.eclipse.californium.scandium.dtls.ServerHandshaker; +import org.eclipse.californium.scandium.dtls.SessionAdapter; +import org.eclipse.californium.scandium.dtls.SessionId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DtlsSessionLogger extends SessionAdapter { + private static final Logger LOG = LoggerFactory.getLogger(DtlsSessionLogger.class); + + private SessionId sessionIdentifier = null; + + @Override + public void handshakeStarted(Handshaker handshaker) throws HandshakeException { + if (handshaker instanceof ResumingServerHandshaker) { + LOG.info("DTLS abbreviated Handshake initiated by server : STARTED ..."); + } else if (handshaker instanceof ServerHandshaker) { + LOG.info("DTLS Full Handshake initiated by server : STARTED ..."); + } else if (handshaker instanceof ResumingClientHandshaker) { + sessionIdentifier = handshaker.getSession().getSessionIdentifier(); + LOG.info("DTLS abbreviated Handshake initiated by client : STARTED ..."); + } else if (handshaker instanceof ClientHandshaker) { + LOG.info("DTLS Full Handshake initiated by client : STARTED ..."); + } + } + + @Override + public void contextEstablished(Handshaker handshaker, DTLSContext establishedContext) throws HandshakeException { + if (handshaker instanceof ResumingServerHandshaker) { + LOG.info("DTLS abbreviated Handshake initiated by server : SUCCEED"); + } else if (handshaker instanceof ServerHandshaker) { + LOG.info("DTLS Full Handshake initiated by server : SUCCEED"); + } else if (handshaker instanceof ResumingClientHandshaker) { + if (sessionIdentifier != null && sessionIdentifier.equals(handshaker.getSession().getSessionIdentifier())) { + LOG.info("DTLS abbreviated Handshake initiated by client : SUCCEED"); + } else { + LOG.info("DTLS abbreviated turns into Full Handshake initiated by client : SUCCEED"); + } + } else if (handshaker instanceof ClientHandshaker) { + LOG.info("DTLS Full Handshake initiated by client : SUCCEED"); + } + } + + @Override + public void handshakeFailed(Handshaker handshaker, Throwable error) { + // get cause + String cause; + if (error != null) { + if (error.getMessage() != null) { + cause = error.getMessage(); + } else { + cause = error.getClass().getName(); + } + } else { + cause = "unknown cause"; + } + + if (handshaker instanceof ResumingServerHandshaker) { + LOG.info("DTLS abbreviated Handshake initiated by server : FAILED ({})", cause); + } else if (handshaker instanceof ServerHandshaker) { + LOG.info("DTLS Full Handshake initiated by server : FAILED ({})", cause); + } else if (handshaker instanceof ResumingClientHandshaker) { + LOG.info("DTLS abbreviated Handshake initiated by client : FAILED ({})", cause); + } else if (handshaker instanceof ClientHandshaker) { + LOG.info("DTLS Full Handshake initiated by client : FAILED ({})", cause); + } + } +} diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java index d521b28aa9..bfb9385de5 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java @@ -33,22 +33,20 @@ import java.io.File; import java.io.PrintWriter; +import java.net.InetAddress; import java.util.List; import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.scandium.config.DtlsConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; -import org.eclipse.californium.scandium.dtls.ClientHandshaker; -import org.eclipse.californium.scandium.dtls.DTLSContext; -import org.eclipse.californium.scandium.dtls.HandshakeException; -import org.eclipse.californium.scandium.dtls.Handshaker; -import org.eclipse.californium.scandium.dtls.ResumingClientHandshaker; -import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker; -import org.eclipse.californium.scandium.dtls.ServerHandshaker; -import org.eclipse.californium.scandium.dtls.SessionAdapter; -import org.eclipse.californium.scandium.dtls.SessionId; -import org.eclipse.leshan.client.californium.LeshanClient; -import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder; +import org.eclipse.leshan.client.LeshanClient; +import org.eclipse.leshan.client.LeshanClientBuilder; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointFactory; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; +import org.eclipse.leshan.client.californium.endpoint.coap.CoapOscoreProtocolProvider; +import org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientEndpointFactory; +import org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientProtocolProvider; import org.eclipse.leshan.client.demo.cli.LeshanClientDemoCLI; import org.eclipse.leshan.client.demo.cli.interactive.InteractiveCommands; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; @@ -227,27 +225,6 @@ public static LeshanClient createClient(LeshanClientDemoCLI cli, LwM2mModelRepos List enablers = initializer.createAll(); - // Create CoAP Config - File configFile = new File(CF_CONFIGURATION_FILENAME); - Configuration coapConfig = LeshanClientBuilder.createDefaultCoapConfiguration(); - // these configuration values are always overwritten by CLI - // therefore set them to transient. - coapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); - coapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); - if (configFile.isFile()) { - coapConfig.load(configFile); - } else { - coapConfig.store(configFile, CF_CONFIGURATION_HEADER); - } - - // Create DTLS Config - DtlsConnectorConfig.Builder dtlsConfig = DtlsConnectorConfig.builder(coapConfig); - dtlsConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !cli.dtls.supportDeprecatedCiphers); - dtlsConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cli.dtls.cid); - if (cli.dtls.ciphers != null) { - dtlsConfig.set(DtlsConfig.DTLS_CIPHER_SUITES, cli.dtls.ciphers); - } - // Configure Registration Engine DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory(); if (cli.main.comPeriodInSec != null) @@ -256,79 +233,62 @@ public static LeshanClient createClient(LeshanClientDemoCLI cli, LwM2mModelRepos engineFactory.setResumeOnConnect(!cli.dtls.forceFullhandshake); engineFactory.setQueueMode(cli.main.queueMode); - // Log Session lifecycle - dtlsConfig.setSessionListener(new SessionAdapter() { - - private SessionId sessionIdentifier = null; + // Create Californium Endpoints Provider: + // -------------------------------------- + // Define custom CoapsProtocolProvider + CoapsClientProtocolProvider customCoapsProtocolProvider = new CoapsClientProtocolProvider() { @Override - public void handshakeStarted(Handshaker handshaker) throws HandshakeException { - if (handshaker instanceof ResumingServerHandshaker) { - LOG.info("DTLS abbreviated Handshake initiated by server : STARTED ..."); - } else if (handshaker instanceof ServerHandshaker) { - LOG.info("DTLS Full Handshake initiated by server : STARTED ..."); - } else if (handshaker instanceof ResumingClientHandshaker) { - sessionIdentifier = handshaker.getSession().getSessionIdentifier(); - LOG.info("DTLS abbreviated Handshake initiated by client : STARTED ..."); - } else if (handshaker instanceof ClientHandshaker) { - LOG.info("DTLS Full Handshake initiated by client : STARTED ..."); - } + public CaliforniumClientEndpointFactory createDefaultEndpointFactory() { + return new CoapsClientEndpointFactory() { + @Override + protected DtlsConnectorConfig.Builder createDtlsConnectorConfigBuilder( + Configuration configuration) { + Builder builder = super.createDtlsConnectorConfigBuilder(configuration); + // Add DTLS Session lifecycle logger + builder.setSessionListener(new DtlsSessionLogger()); + return builder; + }; + }; } + }; - @Override - public void contextEstablished(Handshaker handshaker, DTLSContext establishedContext) - throws HandshakeException { - if (handshaker instanceof ResumingServerHandshaker) { - LOG.info("DTLS abbreviated Handshake initiated by server : SUCCEED"); - } else if (handshaker instanceof ServerHandshaker) { - LOG.info("DTLS Full Handshake initiated by server : SUCCEED"); - } else if (handshaker instanceof ResumingClientHandshaker) { - if (sessionIdentifier != null - && sessionIdentifier.equals(handshaker.getSession().getSessionIdentifier())) { - LOG.info("DTLS abbreviated Handshake initiated by client : SUCCEED"); - } else { - LOG.info("DTLS abbreviated turns into Full Handshake initiated by client : SUCCEED"); - } - } else if (handshaker instanceof ClientHandshaker) { - LOG.info("DTLS Full Handshake initiated by client : SUCCEED"); - } - } + CaliforniumClientEndpointsProvider.Builder endpointsBuilder = new CaliforniumClientEndpointsProvider.Builder( + new CoapOscoreProtocolProvider(), customCoapsProtocolProvider); - @Override - public void handshakeFailed(Handshaker handshaker, Throwable error) { - // get cause - String cause; - if (error != null) { - if (error.getMessage() != null) { - cause = error.getMessage(); - } else { - cause = error.getClass().getName(); - } - } else { - cause = "unknown cause"; - } + // Create Californium Configuration + Configuration clientCoapConfig = endpointsBuilder.createDefaultConfiguration(); - if (handshaker instanceof ResumingServerHandshaker) { - LOG.info("DTLS abbreviated Handshake initiated by server : FAILED ({})", cause); - } else if (handshaker instanceof ServerHandshaker) { - LOG.info("DTLS Full Handshake initiated by server : FAILED ({})", cause); - } else if (handshaker instanceof ResumingClientHandshaker) { - LOG.info("DTLS abbreviated Handshake initiated by client : FAILED ({})", cause); - } else if (handshaker instanceof ClientHandshaker) { - LOG.info("DTLS Full Handshake initiated by client : FAILED ({})", cause); - } - } - }); + // Set some DTLS stuff + // These configuration values are always overwritten by CLI therefore set them to transient. + clientCoapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); + clientCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); + clientCoapConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !cli.dtls.supportDeprecatedCiphers); + clientCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cli.dtls.cid); + if (cli.dtls.ciphers != null) { + clientCoapConfig.set(DtlsConfig.DTLS_CIPHER_SUITES, cli.dtls.ciphers); + } + + // Persist configuration + File configFile = new File(CF_CONFIGURATION_FILENAME); + if (configFile.isFile()) { + clientCoapConfig.load(configFile); + } else { + clientCoapConfig.store(configFile, CF_CONFIGURATION_HEADER); + } + + // Set Californium Configuration + endpointsBuilder.setConfiguration(clientCoapConfig); + + endpointsBuilder.setClientAddress(InetAddress.getByName(cli.main.localAddress)); // Create client LeshanClientBuilder builder = new LeshanClientBuilder(cli.main.endpoint); - builder.setLocalAddress(cli.main.localAddress, cli.main.localPort); builder.setObjects(enablers); + builder.setEndpointsProvider(endpointsBuilder.build()); builder.setDataSenders(new ManualDataSender()); - builder.setCoapConfig(coapConfig); if (cli.identity.isx509()) builder.setTrustStore(cli.identity.getX509().trustStore); - builder.setDtlsConfig(dtlsConfig); builder.setRegistrationEngineFactory(engineFactory); if (cli.main.supportOldFormat) { builder.setDecoder(new DefaultLwM2mDecoder(true)); diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/LeshanClientDemoCLI.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/LeshanClientDemoCLI.java index db9c2bb8c8..8e15ef3fdd 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/LeshanClientDemoCLI.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/LeshanClientDemoCLI.java @@ -28,7 +28,6 @@ import org.eclipse.leshan.core.demo.cli.StandardHelpOptions; import org.eclipse.leshan.core.demo.cli.VersionProvider; import org.eclipse.leshan.core.demo.cli.converters.CIDConverter; -import org.eclipse.leshan.core.demo.cli.converters.PortConverter; import org.eclipse.leshan.core.demo.cli.converters.StrictlyPositiveIntegerConverter; import org.eclipse.leshan.core.util.StringUtils; @@ -121,14 +120,6 @@ public static class GeneralSection { "Default: any local address." }) public String localAddress; - @Option(names = { "-lp", "--local-port" }, - defaultValue = "0", - description = { // - "Set the local CoAP port of the Client.", // - "Default: A valid unsused port value between 0 and 65535." }, - converter = PortConverter.class) - public Integer localPort; - @Option(names = { "-m", "--models-folder" }, description = { // "A folder which contains object models in OMA DDF(xml)format." }) diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java index 9662a764ef..e8b186a91f 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java @@ -17,7 +17,7 @@ import java.util.List; import java.util.Map; -import org.eclipse.leshan.client.californium.LeshanClient; +import org.eclipse.leshan.client.LeshanClient; import org.eclipse.leshan.client.demo.MyLocation; import org.eclipse.leshan.client.demo.cli.interactive.InteractiveCommands.CollectCommand; import org.eclipse.leshan.client.demo.cli.interactive.InteractiveCommands.CreateCommand; diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandlerProvider.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandlerProvider.java index 354259d119..2ff00ff68e 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandlerProvider.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandlerProvider.java @@ -27,6 +27,10 @@ public void addIdentityHandler(Endpoint endpoint, IdentityHandler identityHandle identityHandlers.put(endpoint, identityHandler); } + public void clear() { + identityHandlers.clear(); + } + public IdentityHandler getIdentityHandler(Endpoint endpoint) { return identityHandlers.get(endpoint); } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java index a7eaf0646d..cbc50ae414 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java @@ -44,8 +44,6 @@ import org.eclipse.californium.core.coap.CoAP.Type; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Token; -import org.eclipse.californium.core.network.CoapEndpoint; -import org.eclipse.californium.core.network.Endpoint; import org.eclipse.californium.core.network.serialization.UdpDataSerializer; import org.eclipse.californium.elements.AddressEndpointContext; import org.eclipse.californium.elements.EndpointContext; @@ -246,15 +244,11 @@ public void dont_sent_request_if_identity_change() // Create new session with new credentials at client side. // Get connector - Endpoint endpoint = helper.client.coap().getServer() - .getEndpoint(helper.client.getAddress(helper.getCurrentRegisteredServer())); - DTLSConnector connector = (DTLSConnector) ((CoapEndpoint) endpoint).getConnector(); + DTLSConnector connector = (DTLSConnector) helper.getClientConnector(helper.getCurrentRegisteredServer()); // Clear DTLS session to force new handshake connector.clearConnectionState(); // Change PSK id helper.setNewPsk("anotherPSK", GOOD_PSK_KEY); - // restart connector - connector.start(); // send and empty message to force a new handshake with new credentials SimpleMessageCallback callback = new SimpleMessageCallback(); // create a ping message diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepLwM2mClient.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepLwM2mClient.java index 594ff74726..87fe17a6b1 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepLwM2mClient.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepLwM2mClient.java @@ -32,6 +32,7 @@ import org.eclipse.californium.elements.config.SystemConfig; import org.eclipse.californium.elements.config.UdpConfig; import org.eclipse.leshan.client.californium.request.CoapRequestBuilder; +import org.eclipse.leshan.core.californium.identity.DefaultCoapIdentityHandler; import org.eclipse.leshan.core.endpoint.EndpointUriUtil; import org.eclipse.leshan.core.link.DefaultLinkSerializer; import org.eclipse.leshan.core.link.LinkSerializer; @@ -70,7 +71,7 @@ public LockStepLwM2mClient(final InetSocketAddress destination) { public Request createCoapRequest(UplinkRequest lwm2mReq) { // create CoAP request CoapRequestBuilder coapRequestBuilder = new CoapRequestBuilder(Identity.unsecure(destination), encoder, model, - linkSerializer); + linkSerializer, new DefaultCoapIdentityHandler()); lwm2mReq.accept(coapRequestBuilder); Request coapReq = coapRequestBuilder.getRequest(); byte[] token = new byte[8]; diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java index b1d65bb214..8ed6e57d3d 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java @@ -46,7 +46,10 @@ import java.util.concurrent.TimeoutException; import org.eclipse.californium.core.coap.Request; -import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.LeshanClientBuilder; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; +import org.eclipse.leshan.client.californium.endpoint.coap.CoapOscoreProtocolProvider; +import org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientProtocolProvider; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Device; import org.eclipse.leshan.client.object.Oscore; @@ -352,6 +355,12 @@ public boolean isSupported(ContentFormat format) { } }); } + + // create endpoint provider + CaliforniumClientEndpointsProvider.Builder endpointProviderBuilder = new CaliforniumClientEndpointsProvider.Builder( + new CoapOscoreProtocolProvider(), new CoapsClientProtocolProvider()); + endpointProviderBuilder.setClientAddress(InetAddress.getLoopbackAddress()); + builder.setEndpointsProvider(endpointProviderBuilder.build()); client = builder.build(); setupClientMonitoring(); } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java index a04163fb58..8d097ab174 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java @@ -37,8 +37,10 @@ import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.elements.Connector; import org.eclipse.californium.scandium.DTLSConnector; -import org.eclipse.leshan.client.californium.LeshanClient; -import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.LeshanClient; +import org.eclipse.leshan.client.LeshanClientBuilder; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpoint; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; import org.eclipse.leshan.client.object.Device; import org.eclipse.leshan.client.object.LwM2mTestObject; import org.eclipse.leshan.client.object.Security; @@ -172,6 +174,7 @@ public void createClient(Map additionalAttributes) { builder.setDataSenders(new ManualDataSender()); builder.setAdditionalAttributes(additionalAttributes); builder.setObjects(objects); + builder.setEndpointsProvider(new CaliforniumClientEndpointsProvider()); client = builder.build(); setupClientMonitoring(); } @@ -369,8 +372,8 @@ public Registration getLastRegistration() { } public Connector getClientConnector(ServerIdentity server) { - CoapEndpoint endpoint = (CoapEndpoint) client.coap().getServer().getEndpoint(client.getAddress(server)); - return endpoint.getConnector(); + CaliforniumClientEndpoint endpoint = (CaliforniumClientEndpoint) client.getEndpoint(server); + return ((CoapEndpoint) endpoint.getCoapEndpoint()).getConnector(); } public DTLSConnector getServerDTLSConnector() { diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/QueueModeIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/QueueModeIntegrationTestHelper.java index b1cad9a4fe..3dc35e3524 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/QueueModeIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/QueueModeIntegrationTestHelper.java @@ -26,7 +26,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.LeshanClientBuilder; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; @@ -81,6 +82,7 @@ public void createClient() { LeshanClientBuilder builder = new LeshanClientBuilder(currentEndpointIdentifier.get()); builder.setRegistrationEngineFactory(new DefaultRegistrationEngineFactory().setQueueMode(true)); builder.setObjects(objects); + builder.setEndpointsProvider(new CaliforniumClientEndpointsProvider()); client = builder.build(); setupClientMonitoring(); } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java index 2332c07493..37064dd20c 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java @@ -49,11 +49,7 @@ import javax.crypto.SecretKey; import javax.security.auth.x500.X500Principal; -import org.eclipse.californium.core.network.CoapEndpoint; -import org.eclipse.californium.core.observe.ObservationStore; import org.eclipse.californium.elements.config.Configuration; -import org.eclipse.californium.oscore.OSCoreCtxDB; -import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.config.DtlsConfig; import org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; @@ -62,8 +58,13 @@ import org.eclipse.californium.scandium.dtls.PskPublicInformation; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; import org.eclipse.californium.scandium.dtls.pskstore.AdvancedPskStore; -import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.LeshanClientBuilder; import org.eclipse.leshan.client.californium.X509Util; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointFactory; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; +import org.eclipse.leshan.client.californium.endpoint.coap.CoapClientProtocolProvider; +import org.eclipse.leshan.client.californium.endpoint.coap.CoapOscoreProtocolProvider; +import org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientProtocolProvider; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Device; import org.eclipse.leshan.client.object.Oscore; @@ -72,9 +73,9 @@ import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.ObjectsInitializer; +import org.eclipse.leshan.client.servers.ServerInfo; import org.eclipse.leshan.core.CertificateUsage; import org.eclipse.leshan.core.LwM2mId; -import org.eclipse.leshan.core.californium.EndpointFactory; import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.oscore.AeadAlgorithm; import org.eclipse.leshan.core.oscore.HkdfAlgorithm; @@ -258,48 +259,48 @@ public void createPSKClient(boolean queueMode) { initializer.setDummyInstancesForObject(LwM2mId.ACCESS_CONTROL); List objects = initializer.createAll(); - InetSocketAddress clientAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); - Configuration configuration = LeshanClientBuilder.createDefaultCoapConfiguration(); LeshanClientBuilder builder = new LeshanClientBuilder(getCurrentEndpoint()); builder.setRegistrationEngineFactory(new DefaultRegistrationEngineFactory().setQueueMode(queueMode)); - builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort()); builder.setObjects(objects); - builder.setDtlsConfig(DtlsConnectorConfig.builder(configuration).setAsList(DtlsConfig.DTLS_CIPHER_SUITES, - CipherSuite.TLS_PSK_WITH_AES_128_CCM_8)); - - // set an editable PSK store for tests - builder.setEndpointFactory(new EndpointFactory() { + // configure endpoints provider + CoapsClientProtocolProvider coapsProtocolProvider = new CoapsClientProtocolProvider() { @Override - public CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, Configuration coapConfig, - ObservationStore store, OSCoreCtxDB db) { - CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); - builder.setInetSocketAddress(address); - builder.setConfiguration(coapConfig); - return builder.build(); + public CaliforniumClientEndpointFactory createDefaultEndpointFactory() { + return new org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientEndpointFactory() { + @Override + protected DtlsConnectorConfig.Builder setUpDtlsConfig(InetSocketAddress addr, ServerInfo serverInfo, + Builder dtlsConfigBuilder, Configuration coapConfig, boolean clientInitiatedOnly, + List trustStore) { + + // create config + DtlsConnectorConfig.Builder newBuilder = super.setUpDtlsConfig(addr, serverInfo, + dtlsConfigBuilder, coapConfig, clientInitiatedOnly, trustStore); + + // tricks to be able to change psk information on the fly + // DtlsConnectorConfig.Builder newBuilder = DtlsConnectorConfig.builder(dtlsConfig); + AdvancedPskStore pskStore = newBuilder.getIncompleteConfig().getAdvancedPskStore(); + if (pskStore != null) { + PskPublicInformation identity = pskStore.getIdentity(null, null); + SecretKey key = pskStore + .requestPskSecretResult(ConnectionId.EMPTY, null, identity, null, null, null, false) + .getSecret(); + singlePSKStore = new SinglePSKStore(identity, key); + newBuilder.setAdvancedPskStore(singlePSKStore); + } + return newBuilder; + } + }; } + }; - @Override - public CoapEndpoint createSecuredEndpoint(DtlsConnectorConfig dtlsConfig, Configuration coapConfig, - ObservationStore store, OSCoreCtxDB db) { - CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); - Builder dtlsConfigBuilder = DtlsConnectorConfig.builder(dtlsConfig); - - // tricks to be able to change psk information on the fly - AdvancedPskStore pskStore = dtlsConfig.getAdvancedPskStore(); - if (pskStore != null) { - PskPublicInformation identity = pskStore.getIdentity(null, null); - SecretKey key = pskStore - .requestPskSecretResult(ConnectionId.EMPTY, null, identity, null, null, null, false) - .getSecret(); - singlePSKStore = new SinglePSKStore(identity, key); - dtlsConfigBuilder.setAdvancedPskStore(singlePSKStore); - } - builder.setConnector(new DTLSConnector(dtlsConfigBuilder.build())); - builder.setConfiguration(coapConfig); - return builder.build(); - } - }); + CaliforniumClientEndpointsProvider.Builder endpointProviderBuilder = new CaliforniumClientEndpointsProvider.Builder( + new CoapClientProtocolProvider(), coapsProtocolProvider); + Configuration configuration = endpointProviderBuilder.createDefaultConfiguration(); + configuration.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, CipherSuite.TLS_PSK_WITH_AES_128_CCM_8); + endpointProviderBuilder.setConfiguration(configuration); + endpointProviderBuilder.setClientAddress(InetAddress.getLoopbackAddress()); + builder.setEndpointsProvider(endpointProviderBuilder.build()); // create client; client = builder.build(); @@ -324,10 +325,15 @@ public void createRPKClient(boolean useServerCertificate) { initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); List objects = initializer.createAll(); - InetSocketAddress clientAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); LeshanClientBuilder builder = new LeshanClientBuilder(getCurrentEndpoint()); - builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort()); builder.setObjects(objects); + + // configure endpoints provider + CaliforniumClientEndpointsProvider.Builder endpointProviderBuilder = new CaliforniumClientEndpointsProvider.Builder( + new CoapClientProtocolProvider(), new CoapsClientProtocolProvider()); + endpointProviderBuilder.setClientAddress(InetAddress.getLoopbackAddress()); + builder.setEndpointsProvider(endpointProviderBuilder.build()); + client = builder.build(); setupClientMonitoring(); } @@ -366,14 +372,16 @@ public void createX509CertClient(Certificate clientCertificate, PrivateKey priva initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); List objects = initializer.createAll(); - InetSocketAddress clientAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); - Configuration configuration = LeshanClientBuilder.createDefaultCoapConfiguration(); LeshanClientBuilder builder = new LeshanClientBuilder(getCurrentEndpoint()); - builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort()); - Builder dtlsConfig = DtlsConnectorConfig.builder(configuration); - dtlsConfig.set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY); - builder.setDtlsConfig(dtlsConfig); + // configure endpoints provider + CaliforniumClientEndpointsProvider.Builder endpointProviderBuilder = new CaliforniumClientEndpointsProvider.Builder( + new CoapClientProtocolProvider(), new CoapsClientProtocolProvider()); + Configuration configuration = endpointProviderBuilder.createDefaultConfiguration(); + configuration.set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY); + endpointProviderBuilder.setConfiguration(configuration); + endpointProviderBuilder.setClientAddress(InetAddress.getLoopbackAddress()); + builder.setEndpointsProvider(endpointProviderBuilder.build()); builder.setObjects(objects); client = builder.build(); @@ -396,15 +404,17 @@ public void createX509CertClient(X509Certificate[] clientCertificate, PrivateKey initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); List objects = initializer.createAll(); - InetSocketAddress clientAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); - Configuration configuration = LeshanClientBuilder.createDefaultCoapConfiguration(); LeshanClientBuilder builder = new LeshanClientBuilder(getCurrentEndpoint()); - builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort()); builder.setTrustStore(clientTrustStore); - Builder dtlsConfig = DtlsConnectorConfig.builder(configuration); - dtlsConfig.set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY); - builder.setDtlsConfig(dtlsConfig); + // configure endpoints provider + CaliforniumClientEndpointsProvider.Builder endpointProviderBuilder = new CaliforniumClientEndpointsProvider.Builder( + new CoapClientProtocolProvider(), new CoapsClientProtocolProvider()); + Configuration configuration = endpointProviderBuilder.createDefaultConfiguration(); + configuration.set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY); + endpointProviderBuilder.setConfiguration(configuration); + endpointProviderBuilder.setClientAddress(InetAddress.getLoopbackAddress()); + builder.setEndpointsProvider(endpointProviderBuilder.build()); builder.setObjects(objects); client = builder.build(); @@ -489,11 +499,15 @@ public void createOscoreClient() { initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345")); List objects = initializer.createAll(); - InetSocketAddress clientAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); LeshanClientBuilder builder = new LeshanClientBuilder(getCurrentEndpoint()); - builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort()); - builder.setObjects(objects); + + // configure endpoints provider + CaliforniumClientEndpointsProvider.Builder endpointProviderBuilder = new CaliforniumClientEndpointsProvider.Builder( + new CoapOscoreProtocolProvider(), new CoapsClientProtocolProvider()); + endpointProviderBuilder.setClientAddress(InetAddress.getLoopbackAddress()); + builder.setEndpointsProvider(endpointProviderBuilder.build()); + client = builder.build(); setupClientMonitoring(); }