From 6091fc3bab6efaaec4bc68449fd7ce6a4cf294c8 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Wed, 19 Oct 2022 18:09:57 +0200 Subject: [PATCH] Add Transport Layer abstraction at Bootstrap server side. --- .../demo/LeshanBootstrapServerDemo.java | 129 ++-- .../bootstrap/demo/servlet/EventServlet.java | 2 +- .../bootstrap/demo/servlet/ServerServlet.java | 23 +- .../integration/tests/BootstrapTest.java | 12 +- .../util/BootstrapIntegrationTestHelper.java | 72 +- .../bootstrap/BootstrapResource.java | 20 +- .../bootstrap/LeshanBootstrapServer.java | 231 ------ .../LeshanBootstrapServerBuilder.java | 655 ------------------ .../BootstrapServerCoapMessageTranslator.java | 58 ++ .../BootstrapServerProtocolProvider.java | 36 + .../CaliforniumBootstrapServerEndpoint.java | 224 ++++++ ...forniumBootstrapServerEndpointFactory.java | 40 ++ ...rniumBootstrapServerEndpointsProvider.java | 253 +++++++ .../CoapBootstrapServerEndpointFactory.java | 114 +++ .../CoapBootstrapServerProtocolProvider.java | 61 ++ ...pOscoreBootstrapServerEndpointFactory.java | 115 +++ .../CoapsBootstrapServerEndpointFactory.java | 308 ++++++++ .../CoapsBootstrapServerProtocolProvider.java | 65 ++ .../bootstrap/request/CoapRequestBuilder.java | 212 ++++++ .../request/LwM2mResponseBuilder.java | 276 ++++++++ .../LeshanBootstrapServerBuilderTest.java | 76 +- .../bootstrap/LeshanBootstrapServerTest.java | 23 +- .../server/bootstrap/BootstrapHandler.java | 4 +- .../bootstrap/BootstrapHandlerFactory.java | 4 +- .../server/bootstrap/BootstrapSession.java | 5 +- .../bootstrap/BootstrapSessionManager.java | 8 +- .../bootstrap/DefaultBootstrapHandler.java | 16 +- .../bootstrap/DefaultBootstrapSession.java | 18 +- .../DefaultBootstrapSessionManager.java | 5 +- .../bootstrap/LeshanBootstrapServer.java | 160 +++++ .../LeshanBootstrapServerBuilder.java | 314 +++++++++ .../BootstrapServerEndpointToolbox.java | 45 ++ .../LwM2mBootstrapServerEndpoint.java | 40 ++ ...LwM2mBootstrapServerEndpointsProvider.java | 40 ++ .../bootstrap/profile/ClientProfile.java | 59 ++ .../profile/ClientProfileProvider.java | 23 + .../profile/DefaultClientProfileProvider.java | 41 ++ .../BootstrapDownlinkRequestSender.java | 103 +++ .../BootstrapUplinkRequestReceiver.java | 32 + ...DefaultBootstrapDownlinkRequestSender.java | 76 ++ ...DefaultBootstrapUplinkRequestReceiver.java | 93 +++ .../bootstrap/BootstrapHandlerTest.java | 65 +- .../redis/serialization/IdentitySerDes.java | 5 - 43 files changed, 3081 insertions(+), 1080 deletions(-) delete mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java delete mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/BootstrapServerCoapMessageTranslator.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/BootstrapServerProtocolProvider.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpoint.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpointFactory.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpointsProvider.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapBootstrapServerEndpointFactory.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapBootstrapServerProtocolProvider.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapOscoreBootstrapServerEndpointFactory.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coaps/CoapsBootstrapServerEndpointFactory.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coaps/CoapsBootstrapServerProtocolProvider.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/request/CoapRequestBuilder.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/request/LwM2mResponseBuilder.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LeshanBootstrapServer.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LeshanBootstrapServerBuilder.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/BootstrapServerEndpointToolbox.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/LwM2mBootstrapServerEndpoint.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/LwM2mBootstrapServerEndpointsProvider.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/ClientProfile.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/ClientProfileProvider.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/DefaultClientProfileProvider.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/BootstrapDownlinkRequestSender.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/BootstrapUplinkRequestReceiver.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/DefaultBootstrapDownlinkRequestSender.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/DefaultBootstrapUplinkRequestReceiver.java diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java index 230ebc55c8..20d5f11035 100755 --- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java @@ -21,27 +21,37 @@ import java.io.File; import java.io.PrintWriter; import java.net.InetSocketAddress; +import java.net.URI; import java.security.cert.Certificate; import java.util.List; import org.eclipse.californium.core.config.CoapConfig; 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.config.DtlsConnectorConfig.Builder; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.leshan.core.californium.PrincipalMdcConnectionListener; import org.eclipse.leshan.core.demo.cli.ShortErrorMessageHandler; +import org.eclipse.leshan.core.endpoint.EndpointUriUtil; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServerBuilder; import org.eclipse.leshan.server.bootstrap.demo.cli.LeshanBsServerDemoCLI; import org.eclipse.leshan.server.bootstrap.demo.servlet.BootstrapServlet; import org.eclipse.leshan.server.bootstrap.demo.servlet.EventServlet; import org.eclipse.leshan.server.bootstrap.demo.servlet.ServerServlet; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.BootstrapServerProtocolProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointFactory; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointsProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coap.CoapBootstrapServerProtocolProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coap.CoapOscoreBootstrapServerEndpointFactory; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coaps.CoapsBootstrapServerEndpointFactory; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coaps.CoapsBootstrapServerProtocolProvider; import org.eclipse.leshan.server.core.demo.json.servlet.SecurityServlet; import org.eclipse.leshan.server.model.VersionedBootstrapModelProvider; import org.eclipse.leshan.server.security.BootstrapSecurityStoreAdapter; @@ -113,37 +123,15 @@ public static LeshanBootstrapServer createBsLeshanServer(LeshanBsServerDemoCLI c // Prepare LWM2M server LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); - // Create CoAP Config - File configFile = new File(CF_CONFIGURATION_FILENAME); - Configuration coapConfig = LeshanBootstrapServerBuilder.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); - } - builder.setCoapConfig(coapConfig); - - // ports from CoAP Config if needed - builder.setLocalAddress(cli.main.localAddress, - cli.main.localPort == null ? coapConfig.get(CoapConfig.COAP_PORT) : cli.main.localPort); - builder.setLocalSecureAddress(cli.main.secureLocalAddress, - cli.main.secureLocalPort == null ? coapConfig.get(CoapConfig.COAP_SECURE_PORT) - : cli.main.secureLocalPort); - - // Create DTLS Config - DtlsConnectorConfig.Builder dtlsConfig = DtlsConnectorConfig.builder(coapConfig); - dtlsConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !cli.dtls.supportDeprecatedCiphers); - if (cli.dtls.cid != null) { - dtlsConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cli.dtls.cid); - } - // Add MDC for connection logs - if (cli.helpsOptions.getVerboseLevel() > 0) { - dtlsConfig.setConnectionListener(new PrincipalMdcConnectionListener()); + // Create Models + List models = ObjectLoader.loadDefault(); + if (cli.main.modelsFolder != null) { + models.addAll(ObjectLoader.loadObjectsFromDir(cli.main.modelsFolder, true)); } + builder.setObjectModelProvider(new VersionedBootstrapModelProvider(models)); + + builder.setConfigStore(bsConfigStore); + builder.setSecurityStore(new BootstrapSecurityStoreAdapter(securityStore)); if (cli.identity.isx509()) { // use X.509 mode (+ RPK) @@ -159,24 +147,75 @@ public static LeshanBootstrapServer createBsLeshanServer(LeshanBsServerDemoCLI c builder.setPrivateKey(cli.identity.getPrivateKey()); } - // Set DTLS Config - builder.setDtlsConfig(dtlsConfig); + // Create Californium Endpoints Provider: + // ------------------ + // Create Custom CoAPS protocol provider to add MDC logger : + BootstrapServerProtocolProvider coapsProtocolProvider = new CoapsBootstrapServerProtocolProvider() { + @Override + public CaliforniumBootstrapServerEndpointFactory createDefaultEndpointFactory(URI uri) { + return new CoapsBootstrapServerEndpointFactory(uri) { + + @Override + protected Builder createDtlsConnectorConfigBuilder(Configuration configuration) { + Builder dtlsConfigBuilder = super.createDtlsConnectorConfigBuilder(configuration); + + // Add MDC for connection logs + if (cli.helpsOptions.getVerboseLevel() > 0) + dtlsConfigBuilder.setConnectionListener(new PrincipalMdcConnectionListener()); + + return dtlsConfigBuilder; + } + }; + } + }; + + // Create Bootstrap Server Endpoints Provider + CaliforniumBootstrapServerEndpointsProvider.Builder endpointsBuilder = new CaliforniumBootstrapServerEndpointsProvider.Builder( + new CoapBootstrapServerProtocolProvider(), coapsProtocolProvider); + + // Create Californium Configuration + Configuration serverCoapConfig = endpointsBuilder.createDefaultConfiguration(); + + // Set some DTLS stuff + // These configuration values are always overwritten by CLI therefore set them to transient. + serverCoapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); + serverCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); + serverCoapConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !cli.dtls.supportDeprecatedCiphers); + if (cli.dtls.cid != null) { + serverCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cli.dtls.cid); + } - // Create Models - List models = ObjectLoader.loadDefault(); - if (cli.main.modelsFolder != null) { - models.addAll(ObjectLoader.loadObjectsFromDir(cli.main.modelsFolder, true)); + // Persist configuration + File configFile = new File(CF_CONFIGURATION_FILENAME); + if (configFile.isFile()) { + serverCoapConfig.load(configFile); + } else { + serverCoapConfig.store(configFile, CF_CONFIGURATION_HEADER); } - builder.setObjectModelProvider(new VersionedBootstrapModelProvider(models)); - builder.setConfigStore(bsConfigStore); - builder.setSecurityStore(new BootstrapSecurityStoreAdapter(securityStore)); + // Set Californium Configuration + endpointsBuilder.setConfiguration(serverCoapConfig); - // TODO OSCORE Temporary cli option to deactivate OSCORE - if (!cli.main.disableOscore) { - builder.setEnableOscore(true); + // Create CoAP endpoint + int coapPort = cli.main.localPort == null ? serverCoapConfig.get(CoapConfig.COAP_PORT) : cli.main.localPort; + InetSocketAddress coapAddr = cli.main.localAddress == null ? new InetSocketAddress(coapPort) + : new InetSocketAddress(cli.main.localAddress, coapPort); + if (cli.main.disableOscore) { + endpointsBuilder.addEndpoint(coapAddr, Protocol.COAP); + } else { + endpointsBuilder.addEndpoint(new CoapOscoreBootstrapServerEndpointFactory( + EndpointUriUtil.createUri(Protocol.COAP.getUriScheme(), coapAddr))); } + // Create CoAP over DTLS endpoint + int coapsPort = cli.main.secureLocalPort == null ? serverCoapConfig.get(CoapConfig.COAP_SECURE_PORT) + : cli.main.secureLocalPort; + InetSocketAddress coapsAddr = cli.main.secureLocalAddress == null ? new InetSocketAddress(coapsPort) + : new InetSocketAddress(cli.main.secureLocalAddress, coapsPort); + endpointsBuilder.addEndpoint(coapsAddr, Protocol.COAPS); + + // Create LWM2M server + builder.setEndpointsProvider(endpointsBuilder.build()); return builder.build(); } diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/EventServlet.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/EventServlet.java index 5552739e12..01ae79f500 100644 --- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/EventServlet.java +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/EventServlet.java @@ -36,7 +36,7 @@ import org.eclipse.leshan.server.bootstrap.BootstrapFailureCause; import org.eclipse.leshan.server.bootstrap.BootstrapSession; import org.eclipse.leshan.server.bootstrap.BootstrapSessionListener; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/ServerServlet.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/ServerServlet.java index 2687e4c360..351d580dfe 100644 --- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/ServerServlet.java +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/ServerServlet.java @@ -27,7 +27,9 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.bootstrap.endpoint.LwM2mBootstrapServerEndpoint; import org.eclipse.leshan.server.core.demo.json.PublicKeySerDes; import org.eclipse.leshan.server.core.demo.json.X509CertificateSerDes; @@ -83,14 +85,23 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se return; } + // search coap and coaps port + Integer coapPort = null; + Integer coapsPort = null; + for (LwM2mBootstrapServerEndpoint endpoint : server.getEndpoints()) { + if (endpoint.getProtocol().equals(Protocol.COAP)) { + coapPort = endpoint.getURI().getPort(); + } else if (endpoint.getProtocol().equals(Protocol.COAPS)) { + coapsPort = endpoint.getURI().getPort(); + } + } + if ("endpoint".equals(path[0])) { resp.setStatus(HttpServletResponse.SC_OK); resp.setContentType("application/json"); - resp.getOutputStream() - .write(String - .format("{ \"securedEndpointPort\":\"%s\", \"unsecuredEndpointPort\":\"%s\"}", - server.getSecuredAddress().getPort(), server.getUnsecuredAddress().getPort()) - .getBytes(StandardCharsets.UTF_8)); + resp.getOutputStream().write(String + .format("{ \"securedEndpointPort\":\"%s\", \"unsecuredEndpointPort\":\"%s\"}", coapsPort, coapPort) + .getBytes(StandardCharsets.UTF_8)); return; } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java index d0a5349837..099a60d439 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java @@ -238,8 +238,8 @@ public void bootstrapWithDiscoverOnRoot() { assertEquals(ResponseCode.CONTENT, lastDiscoverAnswer.getCode()); assertEquals(String.format( ";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ver=1.1,;ver=1.1,,;ver=2.0", - helper.bootstrapServer.getUnsecuredAddress().getHostString(), - helper.bootstrapServer.getUnsecuredAddress().getPort()), + helper.bootstrapServer.getEndpoint(Protocol.COAP).getURI().getHost(), + helper.bootstrapServer.getEndpoint(Protocol.COAP).getURI().getPort()), linkSerializer.serializeCoreLinkFormat(lastDiscoverAnswer.getObjectLinks())); } @@ -269,8 +269,8 @@ public void bootstrapWithDiscoverOnRootThenRebootstrap() throws InvalidRequestEx assertEquals(ResponseCode.CONTENT, lastDiscoverAnswer.getCode()); assertEquals(String.format( ";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ver=1.1,;ver=1.1,,;ver=2.0", - helper.bootstrapServer.getUnsecuredAddress().getHostString(), - helper.bootstrapServer.getUnsecuredAddress().getPort()), + helper.bootstrapServer.getEndpoint(Protocol.COAP).getURI().getHost(), + helper.bootstrapServer.getEndpoint(Protocol.COAP).getURI().getPort()), linkSerializer.serializeCoreLinkFormat(lastDiscoverAnswer.getObjectLinks())); // re-bootstrap @@ -292,8 +292,8 @@ public void bootstrapWithDiscoverOnRootThenRebootstrap() throws InvalidRequestEx assertEquals(ResponseCode.CONTENT, lastDiscoverAnswer.getCode()); assertEquals(String.format( ";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ssid=2222;uri=\"coap://%s:%d\",;ver=1.1,;ssid=2222,;ver=1.1,,;ver=2.0", - helper.bootstrapServer.getUnsecuredAddress().getHostString(), - helper.bootstrapServer.getUnsecuredAddress().getPort(), + helper.bootstrapServer.getEndpoint(Protocol.COAP).getURI().getHost(), + helper.bootstrapServer.getEndpoint(Protocol.COAP).getURI().getPort(), helper.server.getEndpoint(Protocol.COAP).getURI().getHost(), helper.server.getEndpoint(Protocol.COAP).getURI().getPort()), linkSerializer.serializeCoreLinkFormat(lastDiscoverAnswer.getObjectLinks())); 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 8ed6e57d3d..0d6198d95e 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 @@ -24,6 +24,7 @@ import java.math.BigInteger; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; @@ -58,6 +59,7 @@ import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.endpoint.EndpointUriUtil; import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; @@ -81,8 +83,13 @@ import org.eclipse.leshan.server.bootstrap.DefaultBootstrapAuthorizer; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServerBuilder; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointsProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointsProvider.Builder; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coap.CoapBootstrapServerProtocolProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coap.CoapOscoreBootstrapServerEndpointFactory; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coaps.CoapsBootstrapServerProtocolProvider; import org.eclipse.leshan.server.model.StandardBootstrapModelProvider; import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.EditableSecurityStore; @@ -118,9 +125,9 @@ public TestBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, Boots } @Override - public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { + public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity, URI endpointUsed) { assertThat(request.getCoapRequest(), instanceOf(Request.class)); - return super.begin(request, clientIdentity); + return super.begin(request, clientIdentity, endpointUsed); } } @@ -158,16 +165,23 @@ public BootstrapIntegrationTestHelper() { private LeshanBootstrapServerBuilder createBootstrapBuilder(BootstrapSecurityStore securityStore, BootstrapConfigStore configStore) { LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); - builder.setLocalAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); - builder.setLocalSecureAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + + Builder endpointsBuilder = new CaliforniumBootstrapServerEndpointsProvider.Builder( + new CoapBootstrapServerProtocolProvider(), new CoapsBootstrapServerProtocolProvider()); + endpointsBuilder.addEndpoint( + EndpointUriUtil.createUri("coap", new InetSocketAddress(InetAddress.getLoopbackAddress(), 0))); + endpointsBuilder.addEndpoint( + EndpointUriUtil.createUri("coaps", new InetSocketAddress(InetAddress.getLoopbackAddress(), 0))); + builder.setEndpointsProvider(endpointsBuilder.build()); + builder.setPrivateKey(bootstrapServerPrivateKey); builder.setPublicKey(bootstrapServerPublicKey); builder.setSessionManager(new DefaultBootstrapSessionManager(securityStore, configStore) { @Override - public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { + public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity, URI endpointUsed) { assertThat(request.getCoapRequest(), instanceOf(Request.class)); - return super.begin(request, clientIdentity); + return super.begin(request, clientIdentity, endpointUsed); } @Override @@ -180,7 +194,14 @@ public void end(BootstrapSession bsSession) { public void createOscoreBootstrapServer(BootstrapSecurityStore securityStore, BootstrapConfigStore bootstrapStore) { LeshanBootstrapServerBuilder builder = createBootstrapBuilder(securityStore, bootstrapStore); - builder.setEnableOscore(true); + Builder endpointsBuilder = new CaliforniumBootstrapServerEndpointsProvider.Builder( + new CoapBootstrapServerProtocolProvider(), new CoapsBootstrapServerProtocolProvider()); + + endpointsBuilder.addEndpoint(new CoapOscoreBootstrapServerEndpointFactory( + EndpointUriUtil.createUri("coap", new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)))); + endpointsBuilder.addEndpoint( + EndpointUriUtil.createUri("coaps", new InetSocketAddress(InetAddress.getLoopbackAddress(), 0))); + builder.setEndpointsProvider(endpointsBuilder.build()); if (bootstrapStore == null) { bootstrapStore = unsecuredBootstrapStore(); } @@ -254,8 +275,7 @@ public Security withoutSecurity() { public Security withoutSecurityAndInstanceId(Integer id) { // Create Security Object (with bootstrap server only) - String bsUrl = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" - + bootstrapServer.getUnsecuredAddress().getPort(); + String bsUrl = bootstrapServer.getEndpoint(Protocol.COAP).getURI().toString(); Security sec = Security.noSecBootstrap(bsUrl); if (id != null) sec.setId(id); @@ -267,8 +287,7 @@ protected void setupBootstrapServerMonitoring() { } public void createOscoreOnlyBootstrapClient() { - String bsServerUri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" - + bootstrapServer.getUnsecuredAddress().getPort(); + String bsServerUri = bootstrapServer.getEndpoint(Protocol.COAP).getURI().toString(); Oscore oscoreObject = new Oscore(12345, getBootstrapClientOscoreSetting()); ObjectsInitializer initializer = new TestObjectsInitializer(); @@ -287,8 +306,7 @@ public void createClient(ContentFormat preferredContentFormat, final ContentForm public void createPSKClient(String pskIdentity, byte[] pskKey) { // Create Security Object (with bootstrap server only) - String bsUrl = "coaps://" + bootstrapServer.getSecuredAddress().getHostString() + ":" - + bootstrapServer.getSecuredAddress().getPort(); + String bsUrl = bootstrapServer.getEndpoint(Protocol.COAPS).getURI().toString(); byte[] pskId = pskIdentity.getBytes(StandardCharsets.UTF_8); Security security = Security.pskBootstrap(bsUrl, pskId, pskKey); @@ -297,8 +315,7 @@ public void createPSKClient(String pskIdentity, byte[] pskKey) { @Override public void createRPKClient() { - String bsUrl = "coaps://" + bootstrapServer.getSecuredAddress().getHostString() + ":" - + bootstrapServer.getSecuredAddress().getPort(); + String bsUrl = bootstrapServer.getEndpoint(Protocol.COAPS).getURI().toString(); Security security = Security.rpkBootstrap(bsUrl, clientPublicKey.getEncoded(), clientPrivateKey.getEncoded(), bootstrapServerPublicKey.getEncoded()); @@ -493,8 +510,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for BS server ServerSecurity bsSecurity = new ServerSecurity(); bsSecurity.bootstrapServer = true; - bsSecurity.uri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" - + bootstrapServer.getUnsecuredAddress().getPort(); + bsSecurity.uri = bootstrapServer.getEndpoint(Protocol.COAP).getURI().toString(); bsSecurity.securityMode = SecurityMode.NO_SEC; bsConfig.security.put(bsInstanceId, bsSecurity); @@ -548,8 +564,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for BS server ServerSecurity bsSecurity = new ServerSecurity(); bsSecurity.bootstrapServer = true; - bsSecurity.uri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" - + bootstrapServer.getUnsecuredAddress().getPort(); + bsSecurity.uri = bootstrapServer.getEndpoint(Protocol.COAP).getURI().toString(); bsSecurity.securityMode = SecurityMode.NO_SEC; bsConfig.security.put(0, bsSecurity); @@ -597,8 +612,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for BS server ServerSecurity bsSecurity = new ServerSecurity(); bsSecurity.bootstrapServer = true; - bsSecurity.uri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" - + bootstrapServer.getUnsecuredAddress().getPort(); + bsSecurity.uri = bootstrapServer.getEndpoint(Protocol.COAP).getURI().toString(); bsSecurity.securityMode = SecurityMode.NO_SEC; bsConfig.security.put(0, bsSecurity); @@ -632,8 +646,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for BS server ServerSecurity bsSecurity = new ServerSecurity(); bsSecurity.bootstrapServer = true; - bsSecurity.uri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" - + bootstrapServer.getUnsecuredAddress().getPort(); + bsSecurity.uri = bootstrapServer.getEndpoint(Protocol.COAP).getURI().toString(); bsSecurity.securityMode = SecurityMode.NO_SEC; bsConfig.security.put(0, bsSecurity); @@ -692,8 +705,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for BS server ServerSecurity bsSecurity = new ServerSecurity(); bsSecurity.bootstrapServer = true; - bsSecurity.uri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" - + bootstrapServer.getUnsecuredAddress().getPort(); + bsSecurity.uri = bootstrapServer.getEndpoint(Protocol.COAP).getURI().toString(); bsSecurity.securityMode = SecurityMode.NO_SEC; bsConfig.security.put(0, bsSecurity); @@ -728,8 +740,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for BS server ServerSecurity bsSecurity = new ServerSecurity(); bsSecurity.bootstrapServer = true; - bsSecurity.uri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" - + bootstrapServer.getUnsecuredAddress().getPort(); + bsSecurity.uri = bootstrapServer.getEndpoint(Protocol.COAP).getURI().toString(); bsSecurity.securityMode = SecurityMode.NO_SEC; bsSecurity.oscoreSecurityMode = 1; bsConfig.security.put(0, bsSecurity); @@ -766,8 +777,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for BS server ServerSecurity bsSecurity = new ServerSecurity(); bsSecurity.bootstrapServer = true; - bsSecurity.uri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" - + bootstrapServer.getUnsecuredAddress().getPort(); + bsSecurity.uri = bootstrapServer.getEndpoint(Protocol.COAP).getURI().toString(); bsSecurity.securityMode = SecurityMode.NO_SEC; bsSecurity.oscoreSecurityMode = 1; bsConfig.security.put(0, bsSecurity); diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java index 389a3424f3..a02d324a0d 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java @@ -25,17 +25,18 @@ import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.leshan.core.californium.LwM2mCoapResource; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.response.BootstrapResponse; import org.eclipse.leshan.core.response.SendableResponse; -import org.eclipse.leshan.server.bootstrap.BootstrapHandler; +import org.eclipse.leshan.server.bootstrap.request.BootstrapUplinkRequestReceiver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The {@link CoapResource} used to handle /bs request sent to {@link LeshanBootstrapServer}. + * The {@link CoapResource} used to handle /bs request. */ public class BootstrapResource extends LwM2mCoapResource { @@ -43,11 +44,11 @@ public class BootstrapResource extends LwM2mCoapResource { private static final String QUERY_PARAM_ENDPOINT = "ep="; private static final String QUERY_PARAM_PREFERRED_CONTENT_FORMAT = "pct="; - private final BootstrapHandler bootstrapHandler; + private final BootstrapUplinkRequestReceiver receiver; - public BootstrapResource(BootstrapHandler handler) { - super("bs", null); - bootstrapHandler = handler; + public BootstrapResource(BootstrapUplinkRequestReceiver receiver, IdentityHandlerProvider identityHandlerProvider) { + super("bs", identityHandlerProvider); + this.receiver = receiver; } @Override @@ -87,12 +88,13 @@ public void handlePOST(CoapExchange exchange) { } // Extract client identity - Identity clientIdentity = extractIdentity(request.getSourceContext()); + Identity clientIdentity = getForeignPeerIdentity(exchange.advanced(), request); // handle bootstrap request Request coapRequest = exchange.advanced().getRequest(); - SendableResponse sendableResponse = bootstrapHandler.bootstrap(clientIdentity, - new BootstrapRequest(endpoint, preferredContentFomart, additionalParams, coapRequest)); + SendableResponse sendableResponse = receiver.requestReceived(clientIdentity, + new BootstrapRequest(endpoint, preferredContentFomart, additionalParams, coapRequest), + exchange.advanced().getEndpoint().getUri()); BootstrapResponse response = sendableResponse.getResponse(); if (response.isSuccess()) { exchange.respond(toCoapResponseCode(response.getCode())); diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java deleted file mode 100644 index 63c76a02ec..0000000000 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java +++ /dev/null @@ -1,231 +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: - * Sierra Wireless - initial API and implementation - * Michał Wadowski (Orange) - Improved compliance with rfc6690 - *******************************************************************************/ -package org.eclipse.leshan.server.californium.bootstrap; - -import java.net.InetSocketAddress; - -import org.eclipse.californium.core.CoapResource; -import org.eclipse.californium.core.CoapServer; -import org.eclipse.californium.core.network.CoapEndpoint; -import org.eclipse.californium.core.network.Endpoint; -import org.eclipse.californium.core.server.resources.Resource; -import org.eclipse.californium.elements.config.Configuration; -import org.eclipse.leshan.core.Destroyable; -import org.eclipse.leshan.core.Startable; -import org.eclipse.leshan.core.Stoppable; -import org.eclipse.leshan.core.link.lwm2m.LwM2mLinkParser; -import org.eclipse.leshan.core.node.codec.LwM2mDecoder; -import org.eclipse.leshan.core.node.codec.LwM2mEncoder; -import org.eclipse.leshan.core.util.Validate; -import org.eclipse.leshan.server.bootstrap.BootstrapHandler; -import org.eclipse.leshan.server.bootstrap.BootstrapHandlerFactory; -import org.eclipse.leshan.server.bootstrap.BootstrapSessionDispatcher; -import org.eclipse.leshan.server.bootstrap.BootstrapSessionListener; -import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; -import org.eclipse.leshan.server.bootstrap.LwM2mBootstrapRequestSender; -import org.eclipse.leshan.server.californium.RootResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A Lightweight M2M server, serving bootstrap information on /bs. - */ -public class LeshanBootstrapServer { - - private final static Logger LOG = LoggerFactory.getLogger(LeshanBootstrapServer.class); - - // CoAP/Californium attributes - private final CoapAPI coapApi; - private final CoapServer coapServer; - private final CoapEndpoint unsecuredEndpoint; - private final CoapEndpoint securedEndpoint; - private final BootstrapSessionDispatcher dispatcher = new BootstrapSessionDispatcher(); - - private final LwM2mBootstrapRequestSender requestSender; - private final LwM2mLinkParser linkParser; - - /** - * /** Initialize a server which will bind to the specified address and port. - *

- * {@link LeshanBootstrapServerBuilder} is the priviledged way to create a {@link LeshanBootstrapServer}. - * - * @param unsecuredEndpoint CoAP endpoint used for coap:// communication. - * @param securedEndpoint CoAP endpoint used for coaps:// communication. - * @param bsSessionManager manages life cycle of a bootstrap process - * @param bsHandlerFactory responsible to create the {@link BootstrapHandler} - * @param coapConfig the CoAP {@link Configuration}. - * @param encoder encode used to encode request payload. - * @param decoder decoder used to decode response payload. - * @param linkParser a parser {@link LwM2mLinkParser} used to parse a CoRE Link. - */ - public LeshanBootstrapServer(CoapEndpoint unsecuredEndpoint, CoapEndpoint securedEndpoint, - BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, - Configuration coapConfig, LwM2mEncoder encoder, LwM2mDecoder decoder, LwM2mLinkParser linkParser) { - this.linkParser = linkParser; - - Validate.notNull(bsSessionManager, "session manager must not be null"); - Validate.notNull(bsHandlerFactory, "BootstrapHandler factory must not be null"); - Validate.notNull(coapConfig, "coapConfig must not be null"); - - this.coapApi = new CoapAPI(); - - // init CoAP server - coapServer = createCoapServer(coapConfig); - this.unsecuredEndpoint = unsecuredEndpoint; - if (unsecuredEndpoint != null) - coapServer.addEndpoint(unsecuredEndpoint); - - // init DTLS server - this.securedEndpoint = securedEndpoint; - if (securedEndpoint != null) - coapServer.addEndpoint(securedEndpoint); - - // create request sender - requestSender = createRequestSender(securedEndpoint, unsecuredEndpoint, encoder, decoder); - - // create bootstrap resource - CoapResource bsResource = createBootstrapResource( - bsHandlerFactory.create(requestSender, bsSessionManager, dispatcher)); - coapServer.add(bsResource); - } - - protected CoapServer createCoapServer(Configuration coapConfig) { - return new CoapServer(coapConfig) { - @Override - protected Resource createRoot() { - return new RootResource(); - } - }; - } - - protected LwM2mBootstrapRequestSender createRequestSender(Endpoint securedEndpoint, Endpoint unsecuredEndpoint, - LwM2mEncoder encoder, LwM2mDecoder decoder) { - return new CaliforniumLwM2mBootstrapRequestSender(securedEndpoint, unsecuredEndpoint, encoder, decoder, - linkParser); - } - - protected CoapResource createBootstrapResource(BootstrapHandler handler) { - return new BootstrapResource(handler); - } - - /** - * Starts the server and binds it to the specified port. - */ - public void start() { - if (requestSender instanceof Startable) { - ((Startable) requestSender).start(); - } - coapServer.start(); - - if (LOG.isInfoEnabled()) { - LOG.info("Bootstrap server started at {} {}", - getUnsecuredAddress() == null ? "" : "coap://" + getUnsecuredAddress(), - getSecuredAddress() == null ? "" : "coaps://" + getSecuredAddress()); - } - } - - /** - * Stops the server and unbinds it from assigned ports (can be restarted). - */ - public void stop() { - coapServer.stop(); - if (requestSender instanceof Stoppable) { - ((Stoppable) requestSender).stop(); - } - LOG.info("Bootstrap server stopped."); - } - - /** - * Destroys the server, unbinds from all ports and frees all system resources. - *

- * Server can not be restarted anymore. - */ - public void destroy() { - coapServer.destroy(); - - if (requestSender instanceof Destroyable) { - ((Destroyable) requestSender).destroy(); - } else if (requestSender instanceof Stoppable) { - ((Stoppable) requestSender).stop(); - } - LOG.info("Bootstrap server destroyed."); - } - - /** - * @return the {@link InetSocketAddress} used for coap:// - */ - public InetSocketAddress getUnsecuredAddress() { - if (unsecuredEndpoint != null) { - return unsecuredEndpoint.getAddress(); - } else { - return null; - } - } - - /** - * @return the {@link InetSocketAddress} used for coaps:// - */ - public InetSocketAddress getSecuredAddress() { - if (securedEndpoint != null) { - return securedEndpoint.getAddress(); - } else { - return null; - } - } - - public void addListener(BootstrapSessionListener listener) { - dispatcher.addListener(listener); - } - - public void removeListener(BootstrapSessionListener listener) { - dispatcher.removeListener(listener); - } - - /** - *

- * A CoAP API, generally needed when need to access to underlying CoAP protocol. - *

- * e.g. for CoAP monitoring or to directly use underlying {@link CoapServer}. - */ - public CoapAPI coap() { - return coapApi; - } - - public class CoapAPI { - - /** - * @return the underlying {@link CoapServer} - */ - public CoapServer getServer() { - return coapServer; - } - - /** - * @return the {@link CoapEndpoint} used for secured CoAP communication (coaps://) - */ - public CoapEndpoint getSecuredEndpoint() { - return securedEndpoint; - } - - /** - * @return the {@link CoapEndpoint} used for unsecured CoAP communication (coap://) - */ - public CoapEndpoint getUnsecuredEndpoint() { - return unsecuredEndpoint; - } - } -} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java deleted file mode 100644 index cefcdafc8a..0000000000 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java +++ /dev/null @@ -1,655 +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 - * Achim Kraus (Bosch Software Innovations GmbH) - use CoapEndpointBuilder - * Michał Wadowski (Orange) - Improved compliance with rfc6690 - *******************************************************************************/ -package org.eclipse.leshan.server.californium.bootstrap; - -import java.net.InetSocketAddress; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.List; - -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.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.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; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder; -import org.eclipse.californium.scandium.dtls.CertificateType; -import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; -import org.eclipse.californium.scandium.dtls.x509.SingleCertificateProvider; -import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; -import org.eclipse.leshan.core.LwM2m; -import org.eclipse.leshan.core.californium.DefaultEndpointFactory; -import org.eclipse.leshan.core.californium.EndpointFactory; -import org.eclipse.leshan.core.californium.oscore.cf.InMemoryOscoreContextDB; -import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; -import org.eclipse.leshan.core.link.lwm2m.LwM2mLinkParser; -import org.eclipse.leshan.core.node.LwM2mNode; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; -import org.eclipse.leshan.core.node.codec.LwM2mDecoder; -import org.eclipse.leshan.core.node.codec.LwM2mEncoder; -import org.eclipse.leshan.server.bootstrap.BootstrapConfig; -import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; -import org.eclipse.leshan.server.bootstrap.BootstrapConfigStoreTaskProvider; -import org.eclipse.leshan.server.bootstrap.BootstrapHandler; -import org.eclipse.leshan.server.bootstrap.BootstrapHandlerFactory; -import org.eclipse.leshan.server.bootstrap.BootstrapSessionListener; -import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; -import org.eclipse.leshan.server.bootstrap.DefaultBootstrapAuthorizer; -import org.eclipse.leshan.server.bootstrap.DefaultBootstrapHandler; -import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager; -import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore; -import org.eclipse.leshan.server.bootstrap.LwM2mBootstrapRequestSender; -import org.eclipse.leshan.server.model.LwM2mBootstrapModelProvider; -import org.eclipse.leshan.server.model.StandardBootstrapModelProvider; -import org.eclipse.leshan.server.security.BootstrapAuthorizer; -import org.eclipse.leshan.server.security.BootstrapSecurityStore; -import org.eclipse.leshan.server.security.SecurityChecker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Class helping you to build and configure a Californium based Leshan Bootstrap Lightweight M2M server. - *

- * Usage: create it, call the different setters for changing the configuration and then call the {@link #build()} method - * for creating the {@link LeshanBootstrapServer} ready to operate. - */ -public class LeshanBootstrapServerBuilder { - - private static final Logger LOG = LoggerFactory.getLogger(LeshanBootstrapServerBuilder.class); - - private InetSocketAddress localAddress; - private InetSocketAddress localAddressSecure; - private BootstrapConfigStore configStore; - private BootstrapSecurityStore securityStore; - private BootstrapSessionManager sessionManager; - private BootstrapHandlerFactory bootstrapHandlerFactory; - - private LwM2mBootstrapModelProvider modelProvider; - private Configuration coapConfig; - private Builder dtlsConfigBuilder; - - private LwM2mEncoder encoder; - private LwM2mDecoder decoder; - - private PublicKey publicKey; - private PrivateKey privateKey; - private X509Certificate[] certificateChain; - private Certificate[] trustedCertificates; - - private EndpointFactory endpointFactory; - private boolean noSecuredEndpoint; - private boolean noUnsecuredEndpoint; - - private LwM2mLinkParser linkParser; - - private boolean enableOscore = false; - - private BootstrapAuthorizer authorizer; - - /** - * Set the address/port for unsecured CoAP communication (coap://). - *

- * By default a wildcard address and the default CoAP port(5683) is used. - * - * @param hostname The address to bind. If null wildcard address is used. - * @param port A valid port value is between 0 and 65535. A port number of zero will let the system pick up an - * ephemeral port in a bind operation. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setLocalAddress(String hostname, int port) { - if (hostname == null) { - this.localAddress = new InetSocketAddress(port); - } else { - this.localAddress = new InetSocketAddress(hostname, port); - } - return this; - } - - /** - * Set the address for unsecured CoAP communication (coap://). - *

- * By default a wildcard address and the default CoAP port(5683) is used. - * - * @param localAddress the socket address for coap://. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setLocalAddress(InetSocketAddress localAddress) { - this.localAddress = localAddress; - return this; - } - - /** - * Set the address/port for secured CoAP over DTLS communication (coaps://). - *

- * By default a wildcard address and the default CoAPs port(5684) is used. - * - * @param hostname The address to bind. If null wildcard address is used. - * @param port A valid port value is between 0 and 65535. A port number of zero will let the system pick up an - * ephemeral port in a bind operation. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setLocalSecureAddress(String hostname, int port) { - if (hostname == null) { - this.localAddressSecure = new InetSocketAddress(port); - } else { - this.localAddressSecure = new InetSocketAddress(hostname, port); - } - return this; - } - - /** - * Set the address for secured CoAP over DTLS communication Server (coaps://). - *

- * By default a wildcard address and the default CoAP port(5684) is used. - * - * @param localSecureAddress the socket address for coaps://. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setLocalSecureAddress(InetSocketAddress localSecureAddress) { - this.localAddressSecure = localSecureAddress; - return this; - } - - /** - * Set the {@link PublicKey} of the server which will be used for Raw Public Key DTLS authentication. - *

- * This should be used for RPK support only. - *

- * Setting publicKey and privateKey will enable RawPublicKey DTLS authentication, see also - * {@link LeshanBootstrapServerBuilder#setPrivateKey(PrivateKey)}. - * - * @param publicKey the Raw Public Key of the bootstrap server. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setPublicKey(PublicKey publicKey) { - this.publicKey = publicKey; - return this; - } - - /** - * Set the CertificateChain of the server which will be used for X.509 DTLS authentication. - *

- * Setting publicKey and privateKey will enable RPK and X.509 DTLS authentication, see - * also {@link LeshanBootstrapServerBuilder#setPrivateKey(PrivateKey)}. - *

- * For RPK the public key will be extracted from the first X.509 certificate of the certificate chain. If you only - * need RPK support, use {@link LeshanBootstrapServerBuilder#setPublicKey(PublicKey)} instead. - *

- * If you want to deactivate RPK mode, look at - * {@link LeshanBootstrapServerBuilder#setDtlsConfig(org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder)} - * and - * {@link Builder#setCertificateIdentityProvider(org.eclipse.californium.scandium.dtls.x509.CertificateProvider)}. - * - * @param certificateChain the certificate chain of the bootstrap server. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setCertificateChain(T[] certificateChain) { - this.certificateChain = certificateChain; - return this; - } - - /** - * Set the {@link PrivateKey} of the server which will be used for RawPublicKey(RPK) and/or X.509 DTLS - * authentication. - * - * @param privateKey the Private Key of the bootstrap server. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setPrivateKey(PrivateKey privateKey) { - this.privateKey = privateKey; - return this; - } - - /** - * The list of trusted certificates used to authenticate devices using X.509 DTLS authentication. - *

- * If you need more complex/dynamic trust behavior, look at - * {@link LeshanBootstrapServerBuilder#setDtlsConfig(org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder)} - * and - * {@link Builder#setAdvancedCertificateVerifier(org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier)} - * instead. - * - * @param trustedCertificates certificates trusted by the bootstrap server. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setTrustedCertificates(T[] trustedCertificates) { - this.trustedCertificates = trustedCertificates; - return this; - } - - /** - * Set the {@link BootstrapConfigStore} containing bootstrap configuration to apply to each devices. - *

- * By default an {@link InMemoryBootstrapConfigStore} is used. - *

- * See {@link BootstrapConfig} to see what is could be done during a bootstrap session. - * - * @param configStore the bootstrap configuration store. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setConfigStore(BootstrapConfigStore configStore) { - this.configStore = configStore; - return this; - } - - /** - * Set the {@link BootstrapSecurityStore} which contains data needed to authenticate devices. - *

- * WARNING: without security store all devices will be accepted which is not really recommended in production - * environment. - *

- * There is not default implementation. - * - * @param securityStore the security store used to authenticate devices. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setSecurityStore(BootstrapSecurityStore securityStore) { - this.securityStore = securityStore; - return this; - } - - /** - * Advanced setter used to define {@link BootstrapSessionManager}. - *

- * See {@link BootstrapSessionManager} and {@link DefaultBootstrapSessionManager} for more details. - * - * @param sessionManager the manager responsible to handle bootstrap session. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setSessionManager(BootstrapSessionManager sessionManager) { - this.sessionManager = sessionManager; - return this; - } - - /** - * Advanced setter used to customize default bootstrap server behavior. - *

- * If default bootstrap server behavior is not flexible enough, you can create your own {@link BootstrapHandler} by - * inspiring yourself from {@link DefaultBootstrapHandler}. - * - * @param bootstrapHandlerFactory the factory used to create {@link BootstrapHandler}. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setBootstrapHandlerFactory(BootstrapHandlerFactory bootstrapHandlerFactory) { - this.bootstrapHandlerFactory = bootstrapHandlerFactory; - return this; - } - - /** - *

- * Set your {@link LwM2mBootstrapModelProvider} implementation. - *

- * By default the {@link StandardBootstrapModelProvider}. - */ - public LeshanBootstrapServerBuilder setObjectModelProvider(LwM2mBootstrapModelProvider objectModelProvider) { - this.modelProvider = objectModelProvider; - return this; - } - - /** - *

- * Set the {@link LwM2mEncoder} which will encode {@link LwM2mNode} with supported content format. - *

- * By default the {@link DefaultLwM2mEncoder} is used. It supports Text, Opaque, TLV and JSON format. - */ - public LeshanBootstrapServerBuilder setEncoder(LwM2mEncoder encoder) { - this.encoder = encoder; - return this; - } - - /** - *

- * Set the {@link LwM2mDecoder} which will decode data in supported content format to create {@link LwM2mNode}. - *

- * By default the {@link DefaultLwM2mDecoder} is used. It supports Text, Opaque, TLV and JSON format. - */ - public LeshanBootstrapServerBuilder setDecoder(LwM2mDecoder decoder) { - this.decoder = decoder; - return this; - } - - /** - * Set the CoAP/Californium {@link Configuration}. - *

- * This is strongly recommended to create the {@link Configuration} with {@link #createDefaultCoapConfiguration()} - * before to modify it. - * - * @param coapConfig the CoAP configuration. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setCoapConfig(Configuration coapConfig) { - this.coapConfig = coapConfig; - return this; - } - - /** - * Set the DTLS/Scandium {@link DtlsConnectorConfig}. - *

- * For advanced DTLS setting. - * - * @param dtlsConfig the DTLS configuration builder. - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setDtlsConfig(Builder dtlsConfig) { - this.dtlsConfigBuilder = dtlsConfig; - 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 Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder setEndpointFactory(EndpointFactory endpointFactory) { - this.endpointFactory = endpointFactory; - return this; - } - - /** - * Set the CoRE Link parser {@link LwM2mLinkParser} - *

- * By default the {@link DefaultLwM2mLinkParser} is used. - */ - public void setLinkParser(LwM2mLinkParser linkParser) { - this.linkParser = linkParser; - } - - /** - * Deactivate unsecured CoAP endpoint, meaning that coap:// communication will be impossible. - * - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder disableUnsecuredEndpoint() { - this.noUnsecuredEndpoint = true; - return this; - } - - /** - * Deactivate secured CoAP endpoint (DTLS), meaning that coaps:// communication will be impossible. - * - * @return the builder for fluent Bootstrap Server creation. - */ - public LeshanBootstrapServerBuilder disableSecuredEndpoint() { - this.noSecuredEndpoint = true; - return this; - } - - /** - * Enable EXPERIMENTAL OSCORE feature. - *

- * By default OSCORE is not enabled. - */ - public LeshanBootstrapServerBuilder setEnableOscore(boolean enableOscore) { - this.enableOscore = enableOscore; - return this; - } - - /** - * Set the Bootstrap authorizer {@link BootstrapAuthorizer} - *

- * By default the {@link DefaultBootstrapAuthorizer} is used. - */ - public LeshanBootstrapServerBuilder setAuthorizer(BootstrapAuthorizer authorizer) { - this.authorizer = authorizer; - return this; - } - - /** - * Create the default CoAP/Californium {@link Configuration} used by the builder. - *

- * It could be used as a base to create a custom CoAP configuration, then use it with - * {@link #setCoapConfig(Configuration)} - * - * @return the default CoAP config. - */ - 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(DtlsConfig.DTLS_ROLE, DtlsRole.SERVER_ONLY); - return networkConfig; - } - - /** - * Create the {@link LeshanBootstrapServer}. - *

- * Next step will be to start it : {@link LeshanBootstrapServer#start()}. - * - * @return the LWM2M Bootstrap server. - * @throws IllegalStateException if builder configuration is not consistent. - */ - public LeshanBootstrapServer build() { - if (localAddress == null) - localAddress = new InetSocketAddress(LwM2m.DEFAULT_COAP_PORT); - if (bootstrapHandlerFactory == null) - bootstrapHandlerFactory = new BootstrapHandlerFactory() { - @Override - public BootstrapHandler create(LwM2mBootstrapRequestSender sender, - BootstrapSessionManager sessionManager, BootstrapSessionListener listener) { - return new DefaultBootstrapHandler(sender, sessionManager, listener); - } - }; - if (configStore == null) { - configStore = new InMemoryBootstrapConfigStore(); - } else if (sessionManager != null) { - LOG.warn("configStore is set but you also provide a custom SessionManager so this store will not be used"); - } - if (modelProvider == null) { - modelProvider = new StandardBootstrapModelProvider(); - } else if (sessionManager != null) { - LOG.warn( - "modelProvider is set but you also provide a custom SessionManager so this provider will not be used"); - } - if (sessionManager == null) { - SecurityChecker securityChecker = new SecurityChecker(); - if (authorizer == null) - authorizer = new DefaultBootstrapAuthorizer(securityStore, securityChecker); - sessionManager = new DefaultBootstrapSessionManager(new BootstrapConfigStoreTaskProvider(configStore), - modelProvider, authorizer); - } - if (coapConfig == null) { - coapConfig = createDefaultCoapConfiguration(); - } - if (endpointFactory == null) { - endpointFactory = new DefaultEndpointFactory("LWM2M BS Server", false); - } - if (encoder == null) - encoder = new DefaultLwM2mEncoder(); - if (decoder == null) - decoder = new DefaultLwM2mDecoder(); - if (linkParser == null) - linkParser = new DefaultLwM2mLinkParser(); - - // handle dtlsConfig - DtlsConnectorConfig dtlsConfig = null; - if (!noSecuredEndpoint && shouldTryToCreateSecureEndpoint()) { - if (dtlsConfigBuilder == null) { - dtlsConfigBuilder = DtlsConnectorConfig.builder(coapConfig); - } - // Set default DTLS setting for Leshan unless user change it. - DtlsConnectorConfig incompleteConfig = dtlsConfigBuilder.getIncompleteConfig(); - - // Handle PSK Store - if (incompleteConfig.getAdvancedPskStore() != null) { - LOG.warn( - "PskStore should be automatically set by Leshan. Using a custom implementation is not advised."); - } else if (securityStore != null) { - List ciphers = incompleteConfig.getConfiguration().get(DtlsConfig.DTLS_CIPHER_SUITES); - if (ciphers == null // if null ciphers will be chosen automatically by Scandium - || CipherSuite.containsPskBasedCipherSuite(ciphers)) { - dtlsConfigBuilder.setAdvancedPskStore(new LwM2mBootstrapPskStore(securityStore)); - } - } - - // Handle secure address - if (incompleteConfig.getAddress() == null) { - if (localAddressSecure == null) { - localAddressSecure = new InetSocketAddress(LwM2m.DEFAULT_COAP_SECURE_PORT); - } - dtlsConfigBuilder.setAddress(localAddressSecure); - } else if (localAddressSecure != null && !localAddressSecure.equals(incompleteConfig.getAddress())) { - throw new IllegalStateException(String.format( - "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for secure address: %s != %s", - localAddressSecure, incompleteConfig.getAddress())); - } - - // check conflict in configuration - if (incompleteConfig.getCertificateIdentityProvider() != null) { - if (privateKey != null) { - throw new IllegalStateException(String.format( - "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for private key")); - } - if (publicKey != null) { - throw new IllegalStateException(String.format( - "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for public key")); - } - if (certificateChain != null) { - throw new IllegalStateException(String.format( - "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for certificate chain")); - } - } else if (privateKey != null) { - // if in raw key mode and not in X.509 set the raw keys - if (certificateChain == null && publicKey != null) { - dtlsConfigBuilder - .setCertificateIdentityProvider(new SingleCertificateProvider(privateKey, publicKey)); - } - // if in X.509 mode set the private key, certificate chain, public key is extracted from the certificate - if (certificateChain != null && certificateChain.length > 0) { - - dtlsConfigBuilder.setCertificateIdentityProvider(new SingleCertificateProvider(privateKey, - certificateChain, CertificateType.X_509, CertificateType.RAW_PUBLIC_KEY)); - } - } - - // handle trusted certificates or RPK - if (incompleteConfig.getAdvancedCertificateVerifier() != null) { - if (trustedCertificates != null) { - throw new IllegalStateException( - "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder: if a AdvancedCertificateVerifier is set, trustedCertificates must not be set."); - } - } else if (incompleteConfig.getCertificateIdentityProvider() != null) { - StaticNewAdvancedCertificateVerifier.Builder verifierBuilder = StaticNewAdvancedCertificateVerifier - .builder(); - // by default trust all RPK - verifierBuilder.setTrustAllRPKs(); - if (trustedCertificates != null) { - verifierBuilder.setTrustedCertificates(trustedCertificates); - } - dtlsConfigBuilder.setAdvancedCertificateVerifier(verifierBuilder.build()); - } - - // we try to build the dtlsConfig, if it fail we will just not create the secured endpoint - try { - dtlsConfig = dtlsConfigBuilder.build(); - } catch (IllegalStateException e) { - LOG.warn("Unable to create DTLS config and so secured endpoint.", e); - } - } - - // Handle OSCORE support. - OSCoreCtxDB oscoreCtxDB = null; - OscoreBootstrapListener sessionHolder = null; - BootstrapOscoreContextCleaner oscoreContextCleaner = null; - if (enableOscore) { - if (securityStore != null) { - sessionHolder = new OscoreBootstrapListener(); - oscoreCtxDB = new InMemoryOscoreContextDB(new LwM2mBootstrapOscoreStore(securityStore, sessionHolder)); - oscoreContextCleaner = new BootstrapOscoreContextCleaner(oscoreCtxDB); - LOG.warn("Experimental OSCORE feature is enabled."); - } - } - - CoapEndpoint unsecuredEndpoint = null; - if (!noUnsecuredEndpoint) { - unsecuredEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null, oscoreCtxDB); - } - - CoapEndpoint securedEndpoint = null; - if (!noSecuredEndpoint && dtlsConfig != null) { - securedEndpoint = endpointFactory.createSecuredEndpoint(dtlsConfig, coapConfig, null, null); - } - - if (securedEndpoint == null && unsecuredEndpoint == null) { - throw new IllegalStateException( - "All CoAP enpoints are deactivated, at least one endpoint should be activated"); - } - - // TODO OSCORE - // - LeshanBootstrapServer bootstrapServer = createBootstrapServer(unsecuredEndpoint, securedEndpoint, - sessionManager, bootstrapHandlerFactory, coapConfig, encoder, decoder, linkParser); - - if (sessionHolder != null) { - bootstrapServer.addListener(sessionHolder); - } - if (oscoreContextCleaner != null) { - bootstrapServer.addListener(oscoreContextCleaner); - } - return bootstrapServer; - // - // replacing ===> - // return createBootstrapServer(unsecuredEndpoint, securedEndpoint, - // sessionManager, bootstrapHandlerFactory, coapConfig, encoder, decoder, linkParser); - - } - - /** - * @return true if we should try to create a secure endpoint on {@link #build()} - */ - protected boolean shouldTryToCreateSecureEndpoint() { - return dtlsConfigBuilder != null || certificateChain != null || privateKey != null || publicKey != null - || securityStore != null || trustedCertificates != null; - } - - /** - * Create the LeshanBootstrapServer. - *

- * You can extend LeshanBootstrapServerBuilder and override this method to create a new builder which - * will be able to build an extended LeshanBootstrapServer. - * - * @param unsecuredEndpoint CoAP endpoint used for coap:// communication. - * @param securedEndpoint CoAP endpoint used for coaps:// communication. - * @param bsSessionManager the manager responsible to handle bootstrap session. - * @param bsHandlerFactory the factory used to create {@link BootstrapHandler}. - * @param coapConfig the CoAP configuration. - * @param decoder decoder used to decode response payload. - * @param encoder encode used to encode request payload. - * @param linkParser a parser {@link LwM2mLinkParser} used to parse a CoRE Link. - * @return the LWM2M Bootstrap server. - */ - protected LeshanBootstrapServer createBootstrapServer(CoapEndpoint unsecuredEndpoint, CoapEndpoint securedEndpoint, - BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, - Configuration coapConfig, LwM2mEncoder encoder, LwM2mDecoder decoder, LwM2mLinkParser linkParser) { - return new LeshanBootstrapServer(unsecuredEndpoint, securedEndpoint, bsSessionManager, bsHandlerFactory, - coapConfig, encoder, decoder, linkParser); - } -} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/BootstrapServerCoapMessageTranslator.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/BootstrapServerCoapMessageTranslator.java new file mode 100644 index 0000000000..dc99a176d9 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/BootstrapServerCoapMessageTranslator.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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.server.californium.bootstrap.endpoint; + +import java.util.Arrays; +import java.util.List; + +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.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; +import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.server.bootstrap.BootstrapSession; +import org.eclipse.leshan.server.bootstrap.endpoint.BootstrapServerEndpointToolbox; +import org.eclipse.leshan.server.bootstrap.request.BootstrapUplinkRequestReceiver; +import org.eclipse.leshan.server.californium.bootstrap.BootstrapResource; +import org.eclipse.leshan.server.californium.bootstrap.request.CoapRequestBuilder; +import org.eclipse.leshan.server.californium.bootstrap.request.LwM2mResponseBuilder; + +public class BootstrapServerCoapMessageTranslator { + + public Request createCoapRequest(BootstrapSession destination, + BootstrapDownlinkRequest lwm2mRequest, BootstrapServerEndpointToolbox toolbox, + IdentityHandler identityHandler) { + CoapRequestBuilder builder = new CoapRequestBuilder(destination.getIdentity(), destination.getModel(), + toolbox.getEncoder(), identityHandler); + lwm2mRequest.accept(builder); + return builder.getRequest(); + } + + public T createLwM2mResponse(BootstrapSession destination, + BootstrapDownlinkRequest lwm2mRequest, Response coapResponse, BootstrapServerEndpointToolbox toolbox) { + LwM2mResponseBuilder builder = new LwM2mResponseBuilder(coapResponse, destination.getEndpoint(), + destination.getModel(), toolbox.getDecoder(), toolbox.getLinkParser()); + lwm2mRequest.accept(builder); + return builder.getResponse(); + } + + public List createResources(BootstrapUplinkRequestReceiver receiver, + BootstrapServerEndpointToolbox toolbox, IdentityHandlerProvider identityHandlerProvider) { + return Arrays.asList((Resource) new BootstrapResource(receiver, identityHandlerProvider)); + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/BootstrapServerProtocolProvider.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/BootstrapServerProtocolProvider.java new file mode 100644 index 0000000000..2fb2e70e7c --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/BootstrapServerProtocolProvider.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * 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.server.californium.bootstrap.endpoint; + +import java.net.URI; +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 BootstrapServerProtocolProvider { + + Protocol getProtocol(); + + List getModuleDefinitionsProviders(); + + void applyDefaultValue(Configuration configuration); + + CaliforniumBootstrapServerEndpointFactory createDefaultEndpointFactory(URI uri); + + URI getDefaultUri(Configuration configuration); +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpoint.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpoint.java new file mode 100644 index 0000000000..6aedd42668 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpoint.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * 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.server.californium.bootstrap.endpoint; + +import java.net.URI; +import java.util.SortedMap; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.californium.core.coap.MessageObserver; +import org.eclipse.californium.core.coap.MessageObserverAdapter; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.network.CoapEndpoint; +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.EndpointUriUtil; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; +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.eclipse.leshan.server.bootstrap.BootstrapSession; +import org.eclipse.leshan.server.bootstrap.endpoint.BootstrapServerEndpointToolbox; +import org.eclipse.leshan.server.bootstrap.endpoint.LwM2mBootstrapServerEndpoint; + +public class CaliforniumBootstrapServerEndpoint implements LwM2mBootstrapServerEndpoint { + + private final Protocol protocol; + private final ScheduledExecutorService executor; + private final CoapEndpoint endpoint; + private final BootstrapServerEndpointToolbox toolbox; + private final BootstrapServerCoapMessageTranslator translator; + private final IdentityHandler identityHandler; + private final ExceptionTranslator exceptionTranslator; + + // A map which contains all ongoing CoAP requests + // This is used to be able to cancel request + private final ConcurrentNavigableMap ongoingRequests = new ConcurrentSkipListMap<>(); + + public CaliforniumBootstrapServerEndpoint(Protocol protocol, CoapEndpoint endpoint, + BootstrapServerCoapMessageTranslator translator, BootstrapServerEndpointToolbox toolbox, + IdentityHandler identityHandler, ExceptionTranslator exceptionTranslator, + ScheduledExecutorService executor) { + this.protocol = protocol; + this.translator = translator; + this.toolbox = toolbox; + this.endpoint = endpoint; + this.identityHandler = identityHandler; + this.exceptionTranslator = exceptionTranslator; + this.executor = executor; + } + + @Override + public Protocol getProtocol() { + return protocol; + } + + @Override + public URI getURI() { + return EndpointUriUtil.createUri(protocol.getUriScheme(), endpoint.getAddress()); + } + + public CoapEndpoint getCoapEndpoint() { + return endpoint; + } + + @Override + public T send(BootstrapSession destination, BootstrapDownlinkRequest lwm2mRequest, + long timeoutInMs) throws InterruptedException { + // Create the CoAP request from LwM2m request + final Request coapRequest = translator.createCoapRequest(destination, lwm2mRequest, toolbox, identityHandler); + + // Send CoAP request synchronously + SyncRequestObserver syncMessageObserver = new SyncRequestObserver(coapRequest, timeoutInMs, + exceptionTranslator) { + @Override + public T buildResponse(Response coapResponse) { + // Build LwM2m response + return translator.createLwM2mResponse(destination, lwm2mRequest, coapResponse, toolbox); + } + }; + coapRequest.addMessageObserver(syncMessageObserver); + + // Store pending request to be able to cancel it later + addOngoingRequest(destination.getId(), coapRequest); + + // Send CoAP request asynchronously + endpoint.sendRequest(coapRequest); + + // Wait for response, then return it + return syncMessageObserver.waitForResponse(); + } + + @Override + public void send(BootstrapSession destination, BootstrapDownlinkRequest 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(destination, lwm2mRequest, toolbox, identityHandler); + + // Add CoAP request callback + MessageObserver obs = new AsyncRequestObserver(coapRequest, responseCallback, errorCallback, timeoutInMs, + executor, exceptionTranslator) { + @Override + public T buildResponse(Response coapResponse) { + // Build LwM2m response + return translator.createLwM2mResponse(destination, lwm2mRequest, coapResponse, toolbox); + } + }; + coapRequest.addMessageObserver(obs); + + // Store pending request to be able to cancel it later + addOngoingRequest(destination.getId(), coapRequest); + + // Send CoAP request asynchronously + endpoint.sendRequest(coapRequest); + } + + /** + * Cancel all ongoing requests for the given sessionID. + * + * @param sessionID the Id associated to the ongoing requests you want to cancel. + * + * @see "All others send methods." + */ + @Override + public void cancelRequests(String sessionID) { + Validate.notNull(sessionID); + SortedMap requests = ongoingRequests.subMap(getFloorKey(sessionID), getCeilingKey(sessionID)); + for (Request coapRequest : requests.values()) { + coapRequest.cancel(); + } + requests.clear(); + } + + private static String getFloorKey(String sessionID) { + // The key format is sessionid#long, So we need a key which is always before this pattern (in natural order). + return sessionID + '#'; + } + + private static String getCeilingKey(String sessionID) { + // The key format is sessionid#long, So we need a key which is always after this pattern (in natural order). + return sessionID + "#A"; + } + + private static String getKey(String sessionID, long requestId) { + return sessionID + '#' + requestId; + } + + private void addOngoingRequest(String sessionID, Request coapRequest) { + if (sessionID != null) { + CleanerMessageObserver observer = new CleanerMessageObserver(sessionID, coapRequest); + coapRequest.addMessageObserver(observer); + ongoingRequests.put(observer.getRequestKey(), coapRequest); + } + } + + private void removeOngoingRequest(String key, Request coapRequest) { + Validate.notNull(key); + ongoingRequests.remove(key, coapRequest); + } + + private final AtomicLong idGenerator = new AtomicLong(0l); + + private class CleanerMessageObserver extends MessageObserverAdapter { + + private final String requestKey; + private final Request coapRequest; + + public CleanerMessageObserver(String sessionID, Request coapRequest) { + super(); + requestKey = getKey(sessionID, idGenerator.incrementAndGet()); + this.coapRequest = coapRequest; + } + + public String getRequestKey() { + return requestKey; + } + + @Override + public void onRetransmission() { + } + + @Override + public void onResponse(Response response) { + removeOngoingRequest(requestKey, coapRequest); + } + + @Override + public void onAcknowledgement() { + } + + @Override + protected void failed() { + removeOngoingRequest(requestKey, coapRequest); + } + + @Override + public void onCancel() { + removeOngoingRequest(requestKey, coapRequest); + } + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpointFactory.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpointFactory.java new file mode 100644 index 0000000000..a33d376117 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpointFactory.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * 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.server.californium.bootstrap.endpoint; + +import java.net.URI; + +import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.leshan.core.californium.ExceptionTranslator; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.security.ServerSecurityInfo; + +public interface CaliforniumBootstrapServerEndpointFactory { + + Protocol getProtocol(); + + URI getUri(); + + CoapEndpoint createCoapEndpoint(Configuration defaultCaliforniumConfiguration, + ServerSecurityInfo serverSecurityInfo, LeshanBootstrapServer server); + + IdentityHandler createIdentityHandler(); + + ExceptionTranslator createExceptionTranslator(); +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpointsProvider.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpointsProvider.java new file mode 100644 index 0000000000..0029b93df7 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/CaliforniumBootstrapServerEndpointsProvider.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * 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.server.californium.bootstrap.endpoint; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +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.CoapEndpoint; +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.core.californium.ExceptionTranslator; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; +import org.eclipse.leshan.core.endpoint.EndpointUriUtil; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.util.NamedThreadFactory; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.bootstrap.endpoint.BootstrapServerEndpointToolbox; +import org.eclipse.leshan.server.bootstrap.endpoint.LwM2mBootstrapServerEndpoint; +import org.eclipse.leshan.server.bootstrap.endpoint.LwM2mBootstrapServerEndpointsProvider; +import org.eclipse.leshan.server.bootstrap.request.BootstrapUplinkRequestReceiver; +import org.eclipse.leshan.server.californium.RootResource; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coap.CoapBootstrapServerProtocolProvider; +import org.eclipse.leshan.server.security.ServerSecurityInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CaliforniumBootstrapServerEndpointsProvider implements LwM2mBootstrapServerEndpointsProvider { + + // TODO TL : provide a COAP/Californium API ? like previous LeshanServer.coapAPI() + + private final Logger LOG = LoggerFactory.getLogger(CaliforniumBootstrapServerEndpointsProvider.class); + + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, + new NamedThreadFactory("Leshan Async Request timeout")); + + private final Configuration serverConfig; + private final List endpointsFactory; + private final BootstrapServerCoapMessageTranslator messagetranslator = new BootstrapServerCoapMessageTranslator(); + private final List endpoints; + private CoapServer coapServer; + + public CaliforniumBootstrapServerEndpointsProvider() { + this(new Builder().generateDefaultValue()); + } + + protected CaliforniumBootstrapServerEndpointsProvider(Builder builder) { + this.serverConfig = builder.serverConfiguration; + this.endpointsFactory = builder.endpointsFactory; + this.endpoints = new ArrayList(); + } + + @Override + public List getEndpoints() { + return Collections.unmodifiableList(endpoints); + } + + @Override + public LwM2mBootstrapServerEndpoint getEndpoint(URI uri) { + for (CaliforniumBootstrapServerEndpoint endpoint : endpoints) { + if (endpoint.getURI().equals(uri)) + return endpoint; + } + return null; + } + + @Override + public void createEndpoints(BootstrapUplinkRequestReceiver requestReceiver, BootstrapServerEndpointToolbox toolbox, + ServerSecurityInfo serverSecurityInfo, LeshanBootstrapServer server) { + // create server; + coapServer = new CoapServer(serverConfig) { + @Override + protected Resource createRoot() { + return new RootResource(); + } + }; + + // create identity handler provider + IdentityHandlerProvider identityHandlerProvider = new IdentityHandlerProvider(); + + // create endpoints + for (CaliforniumBootstrapServerEndpointFactory endpointFactory : endpointsFactory) { + // create Californium endpoint + CoapEndpoint coapEndpoint = endpointFactory.createCoapEndpoint(serverConfig, serverSecurityInfo, server); + + if (coapEndpoint != null) { + + // create identity handler and add it to provider + final IdentityHandler identityHandler = endpointFactory.createIdentityHandler(); + identityHandlerProvider.addIdentityHandler(coapEndpoint, identityHandler); + + // create exception translator; + ExceptionTranslator exceptionTranslator = endpointFactory.createExceptionTranslator(); + + // create LWM2M endpoint + CaliforniumBootstrapServerEndpoint lwm2mEndpoint = new CaliforniumBootstrapServerEndpoint( + endpointFactory.getProtocol(), coapEndpoint, messagetranslator, toolbox, identityHandler, + exceptionTranslator, executor); + endpoints.add(lwm2mEndpoint); + + // add Californium endpoint to coap server + coapServer.addEndpoint(coapEndpoint); + + } + } + + // create resources + List resources = messagetranslator.createResources(requestReceiver, toolbox, identityHandlerProvider); + coapServer.add(resources.toArray(new Resource[resources.size()])); + } + + @Override + public void start() { + coapServer.start(); + + } + + @Override + public void stop() { + coapServer.stop(); + + } + + @Override + public void destroy() { + 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 serverConfiguration; + private final List endpointsFactory; + + public Builder(BootstrapServerProtocolProvider... protocolProviders) { + // TODO TL : handle duplicate ? + this.protocolProviders = new ArrayList(); + if (protocolProviders.length == 0) { + this.protocolProviders.add(new CoapBootstrapServerProtocolProvider()); + } 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 (BootstrapServerProtocolProvider protocolProvider : protocolProviders) { + moduleProviders.addAll(protocolProvider.getModuleDefinitionsProviders()); + } + + // create Californium Configuration + Configuration configuration = new Configuration( + moduleProviders.toArray(new ModuleDefinitionsProvider[moduleProviders.size()])); + + // apply default value + for (BootstrapServerProtocolProvider protocolProvider : protocolProviders) { + protocolProvider.applyDefaultValue(configuration); + } + + return configuration; + } + + /** + * @param serverConfiguration the @{link Configuration} used by the {@link CoapServer}. + */ + public Builder setConfiguration(Configuration serverConfiguration) { + this.serverConfiguration = serverConfiguration; + return this; + } + + public Builder addEndpoint(String uri) { + return addEndpoint(EndpointUriUtil.createUri(uri)); + } + + public Builder addEndpoint(URI uri) { + for (BootstrapServerProtocolProvider protocolProvider : protocolProviders) { + // TODO TL : validate URI + if (protocolProvider.getProtocol().getUriScheme().equals(uri.getScheme())) { + // TODO TL: handle duplicate addr + endpointsFactory.add(protocolProvider.createDefaultEndpointFactory(uri)); + } + } + // TODO TL: handle missing provider for given protocol + return this; + } + + public Builder addEndpoint(InetSocketAddress addr, Protocol protocol) { + return addEndpoint(EndpointUriUtil.createUri(protocol.getUriScheme(), addr)); + } + + public Builder addEndpoint(CaliforniumBootstrapServerEndpointFactory endpointFactory) { + // TODO TL: handle duplicate addr + endpointsFactory.add(endpointFactory); + return this; + } + + protected Builder generateDefaultValue() { + if (serverConfiguration == null) { + serverConfiguration = createDefaultConfiguration(); + } + + if (endpointsFactory.isEmpty()) { + for (BootstrapServerProtocolProvider protocolProvider : protocolProviders) { + // TODO TL : handle duplicates + endpointsFactory.add(protocolProvider + .createDefaultEndpointFactory(protocolProvider.getDefaultUri(serverConfiguration))); + } + } + return this; + } + + public CaliforniumBootstrapServerEndpointsProvider build() { + generateDefaultValue(); + return new CaliforniumBootstrapServerEndpointsProvider(this); + } + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapBootstrapServerEndpointFactory.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapBootstrapServerEndpointFactory.java new file mode 100644 index 0000000000..85c56b1c7b --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapBootstrapServerEndpointFactory.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * 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.server.californium.bootstrap.endpoint.coap; + +import java.net.InetSocketAddress; +import java.net.URI; + +import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.core.network.CoapEndpoint.Builder; +import org.eclipse.californium.elements.Connector; +import org.eclipse.californium.elements.UDPConnector; +import org.eclipse.californium.elements.config.Configuration; +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; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointFactory; +import org.eclipse.leshan.server.security.ServerSecurityInfo; + +public class CoapBootstrapServerEndpointFactory implements CaliforniumBootstrapServerEndpointFactory { + + protected final String loggingTagPrefix; + protected URI endpointUri = null; + + public CoapBootstrapServerEndpointFactory(URI uri) { + this(uri, "Bootstrap Server"); + } + + public CoapBootstrapServerEndpointFactory(URI uri, String loggingTagPrefix) { + this.endpointUri = uri; + this.loggingTagPrefix = loggingTagPrefix; + } + + @Override + public Protocol getProtocol() { + return Protocol.COAP; + } + + @Override + public URI getUri() { + return endpointUri; + } + + protected String getLoggingTag() { + if (loggingTagPrefix != null) { + return String.format("[%s-%s]", loggingTagPrefix, getUri().toString()); + } else { + return String.format("[%s-%s]", getUri().toString()); + } + } + + @Override + public CoapEndpoint createCoapEndpoint(Configuration defaultConfiguration, ServerSecurityInfo serverSecurityInfo, + LeshanBootstrapServer server) { + return createEndpointBuilder(EndpointUriUtil.getSocketAddr(endpointUri), defaultConfiguration, server).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, Configuration coapConfig, + LeshanBootstrapServer server) { + CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); + builder.setConnector(createConnector(address, coapConfig)); + builder.setConfiguration(coapConfig); + builder.setLoggingTag(getLoggingTag()); + 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 ExceptionTranslator createExceptionTranslator() { + return new DefaultExceptionTranslator(); + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapBootstrapServerProtocolProvider.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapBootstrapServerProtocolProvider.java new file mode 100644 index 0000000000..4629e54924 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapBootstrapServerProtocolProvider.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * 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.server.californium.bootstrap.endpoint.coap; + +import java.net.InetSocketAddress; +import java.net.URI; +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.core.endpoint.EndpointUriUtil; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.BootstrapServerProtocolProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointFactory; + +public class CoapBootstrapServerProtocolProvider implements BootstrapServerProtocolProvider { + + @Override + public Protocol getProtocol() { + return Protocol.COAP; + } + + @Override + public void applyDefaultValue(Configuration configuration) { + configuration.set(CoapConfig.MID_TRACKER, TrackerMode.NULL); + } + + @Override + public List getModuleDefinitionsProviders() { + return Arrays.asList(SystemConfig.DEFINITIONS, CoapConfig.DEFINITIONS, UdpConfig.DEFINITIONS); + } + + @Override + public CaliforniumBootstrapServerEndpointFactory createDefaultEndpointFactory(URI uri) { + return new CoapBootstrapServerEndpointFactory(uri); + } + + @Override + public URI getDefaultUri(Configuration configuration) { + return EndpointUriUtil.createUri(getProtocol().getUriScheme(), + new InetSocketAddress(configuration.get(CoapConfig.COAP_PORT))); + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapOscoreBootstrapServerEndpointFactory.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapOscoreBootstrapServerEndpointFactory.java new file mode 100644 index 0000000000..5655992ed6 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coap/CoapOscoreBootstrapServerEndpointFactory.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * 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.server.californium.bootstrap.endpoint.coap; + +import java.net.InetSocketAddress; +import java.net.URI; +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.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.OSCoreCtxDB; +import org.eclipse.californium.oscore.OSCoreEndpointContextInfo; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.californium.oscore.cf.InMemoryOscoreContextDB; +import org.eclipse.leshan.core.oscore.OscoreIdentity; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.californium.bootstrap.BootstrapOscoreContextCleaner; +import org.eclipse.leshan.server.californium.bootstrap.LwM2mBootstrapOscoreStore; +import org.eclipse.leshan.server.californium.bootstrap.OscoreBootstrapListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CoapOscoreBootstrapServerEndpointFactory extends CoapBootstrapServerEndpointFactory { + + private static final Logger LOG = LoggerFactory.getLogger(CoapOscoreBootstrapServerEndpointFactory.class); + + public CoapOscoreBootstrapServerEndpointFactory(URI uri) { + super(uri); + } + + /** + * 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, Configuration coapConfig, + LeshanBootstrapServer server) { + CoapEndpoint.Builder builder = super.createEndpointBuilder(address, coapConfig, server); + + // handle oscore + if (server.getSecurityStore() != null) { + // Handle OSCORE support. + OSCoreCtxDB oscoreCtxDB = null; + OscoreBootstrapListener sessionHolder = null; + BootstrapOscoreContextCleaner oscoreContextCleaner = null; + if (server.getSecurityStore() != null) { + sessionHolder = new OscoreBootstrapListener(); + server.addListener(sessionHolder); + + oscoreCtxDB = new InMemoryOscoreContextDB( + new LwM2mBootstrapOscoreStore(server.getSecurityStore(), sessionHolder)); + oscoreContextCleaner = new BootstrapOscoreContextCleaner(oscoreCtxDB); + server.addListener(oscoreContextCleaner); + + 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()); + } + }; + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coaps/CoapsBootstrapServerEndpointFactory.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coaps/CoapsBootstrapServerEndpointFactory.java new file mode 100644 index 0000000000..f7dc4f3071 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coaps/CoapsBootstrapServerEndpointFactory.java @@ -0,0 +1,308 @@ +/******************************************************************************* + * 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.server.californium.bootstrap.endpoint.coaps; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.security.Principal; +import java.security.PublicKey; +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.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.scandium.DTLSConnector; +import org.eclipse.californium.scandium.config.DtlsConfig; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.californium.scandium.dtls.CertificateType; +import org.eclipse.californium.scandium.dtls.DtlsHandshakeTimeoutException; +import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; +import org.eclipse.californium.scandium.dtls.x509.SingleCertificateProvider; +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; +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.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.californium.bootstrap.LwM2mBootstrapPskStore; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointFactory; +import org.eclipse.leshan.server.security.ServerSecurityInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CoapsBootstrapServerEndpointFactory implements CaliforniumBootstrapServerEndpointFactory { + + private static final Logger LOG = LoggerFactory.getLogger(CoapsBootstrapServerEndpointFactory.class); + + protected final String loggingTagPrefix; + protected URI endpointUri = null; + + public CoapsBootstrapServerEndpointFactory(URI uri) { + this(uri, "Bootstrap Server"); + } + + public CoapsBootstrapServerEndpointFactory(URI uri, String loggingTagPrefix) { + this.endpointUri = uri; + this.loggingTagPrefix = loggingTagPrefix; + } + + @Override + public Protocol getProtocol() { + return Protocol.COAPS; + } + + @Override + public URI getUri() { + return endpointUri; + } + + protected String getLoggingTag() { + if (loggingTagPrefix != null) { + return String.format("[%s-%s]", loggingTagPrefix, getUri().toString()); + } else { + return String.format("[%s-%s]", getUri().toString()); + } + } + + @Override + public CoapEndpoint createCoapEndpoint(Configuration defaultConfiguration, ServerSecurityInfo serverSecurityInfo, + LeshanBootstrapServer server) { + // we do no create coaps endpoint if server does have security store + if (server.getSecurityStore() == null) { + return null; + } + + // create DTLS connector Config + DtlsConnectorConfig.Builder dtlsConfigBuilder = createDtlsConnectorConfigBuilder(defaultConfiguration); + setUpDtlsConfig(dtlsConfigBuilder, EndpointUriUtil.getSocketAddr(endpointUri), serverSecurityInfo, server); + DtlsConnectorConfig dtlsConfig; + try { + dtlsConfig = dtlsConfigBuilder.build(); + } catch (IllegalStateException e) { + LOG.warn("Unable to create DTLS config for endpont {}.", endpointUri.toString(), 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 void setUpDtlsConfig(DtlsConnectorConfig.Builder dtlsConfigBuilder, InetSocketAddress address, + ServerSecurityInfo serverSecurityInfo, LeshanBootstrapServer server) { + + // Set default DTLS setting for Leshan unless user change it. + DtlsConnectorConfig incompleteConfig = dtlsConfigBuilder.getIncompleteConfig(); + + // Handle PSK Store + if (incompleteConfig.getAdvancedPskStore() != null) { + LOG.warn("PskStore should be automatically set by Leshan. Using a custom implementation is not advised."); + } else if (server.getSecurityStore() != null) { + List ciphers = incompleteConfig.getConfiguration().get(DtlsConfig.DTLS_CIPHER_SUITES); + if (ciphers == null // if null ciphers will be chosen automatically by Scandium + || CipherSuite.containsPskBasedCipherSuite(ciphers)) { + dtlsConfigBuilder.setAdvancedPskStore(new LwM2mBootstrapPskStore(server.getSecurityStore())); + } + } + + // Handle secure address + if (incompleteConfig.getAddress() == null) { + dtlsConfigBuilder.setAddress(address); + } else if (address != null && !address.equals(incompleteConfig.getAddress())) { + throw new IllegalStateException(String.format( + "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for secure address: %s != %s", + address, incompleteConfig.getAddress())); + } + + // check conflict in configuration + if (incompleteConfig.getCertificateIdentityProvider() != null) { + if (serverSecurityInfo.getPrivateKey() != null) { + throw new IllegalStateException(String.format( + "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for private key")); + } + if (serverSecurityInfo.getPublicKey() != null) { + throw new IllegalStateException(String.format( + "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for public key")); + } + if (serverSecurityInfo.getCertificateChain() != null) { + throw new IllegalStateException(String.format( + "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for certificate chain")); + } + } else if (serverSecurityInfo.getPrivateKey() != null) { + // if in raw key mode and not in X.509 set the raw keys + if (serverSecurityInfo.getCertificateChain() == null && serverSecurityInfo.getPublicKey() != null) { + dtlsConfigBuilder.setCertificateIdentityProvider(new SingleCertificateProvider( + serverSecurityInfo.getPrivateKey(), serverSecurityInfo.getPublicKey())); + } + // if in X.509 mode set the private key, certificate chain, public key is extracted from the certificate + if (serverSecurityInfo.getCertificateChain() != null + && serverSecurityInfo.getCertificateChain().length > 0) { + + dtlsConfigBuilder.setCertificateIdentityProvider(new SingleCertificateProvider( + serverSecurityInfo.getPrivateKey(), serverSecurityInfo.getCertificateChain(), + CertificateType.X_509, CertificateType.RAW_PUBLIC_KEY)); + } + } + + // handle trusted certificates or RPK + if (incompleteConfig.getAdvancedCertificateVerifier() != null) { + if (serverSecurityInfo.getTrustedCertificates() != null) { + throw new IllegalStateException( + "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder: if a AdvancedCertificateVerifier is set, trustedCertificates must not be set."); + } + } else if (incompleteConfig.getCertificateIdentityProvider() != null) { + StaticNewAdvancedCertificateVerifier.Builder verifierBuilder = StaticNewAdvancedCertificateVerifier + .builder(); + // by default trust all RPK + verifierBuilder.setTrustAllRPKs(); + if (serverSecurityInfo.getTrustedCertificates() != null) { + verifierBuilder.setTrustedCertificates(serverSecurityInfo.getTrustedCertificates()); + } + dtlsConfigBuilder.setAdvancedCertificateVerifier(verifierBuilder.build()); + } + } + + /** + * 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(createConnector(dtlsConfig)); + builder.setConfiguration(coapConfig); + builder.setLoggingTag(getLoggingTag()); + builder.setEndpointContextMatcher(createEndpointContextMatcher()); + 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 createEndpointContextMatcher() { + return new Lwm2mEndpointContextMatcher(); + } + + @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); + } + }; + } + + /** + * 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 createConnector(DtlsConnectorConfig dtlsConfig) { + return new DTLSConnector(dtlsConfig); + } + + @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-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coaps/CoapsBootstrapServerProtocolProvider.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coaps/CoapsBootstrapServerProtocolProvider.java new file mode 100644 index 0000000000..9f4d7b8b0a --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/endpoint/coaps/CoapsBootstrapServerProtocolProvider.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.server.californium.bootstrap.endpoint.coaps; + +import java.net.InetSocketAddress; +import java.net.URI; +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.californium.scandium.config.DtlsConfig.DtlsRole; +import org.eclipse.leshan.core.endpoint.EndpointUriUtil; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.BootstrapServerProtocolProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointFactory; + +public class CoapsBootstrapServerProtocolProvider implements BootstrapServerProtocolProvider { + + @Override + public Protocol getProtocol() { + return Protocol.COAPS; + } + + @Override + public void applyDefaultValue(Configuration configuration) { + configuration.set(CoapConfig.MID_TRACKER, TrackerMode.NULL); + configuration.set(DtlsConfig.DTLS_ROLE, DtlsRole.SERVER_ONLY); + } + + @Override + public List getModuleDefinitionsProviders() { + return Arrays.asList(SystemConfig.DEFINITIONS, CoapConfig.DEFINITIONS, UdpConfig.DEFINITIONS, + DtlsConfig.DEFINITIONS); + } + + @Override + public CaliforniumBootstrapServerEndpointFactory createDefaultEndpointFactory(URI uri) { + return new CoapsBootstrapServerEndpointFactory(uri); + } + + @Override + public URI getDefaultUri(Configuration configuration) { + return EndpointUriUtil.createUri(getProtocol().getUriScheme(), + new InetSocketAddress(configuration.get(CoapConfig.COAP_SECURE_PORT))); + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/request/CoapRequestBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/request/CoapRequestBuilder.java new file mode 100644 index 0000000000..603daa8def --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/request/CoapRequestBuilder.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * 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: + * Sierra Wireless - initial API and implementation + * Achim Kraus (Bosch Software Innovations GmbH) - use Identity as destination + * and transform them to + * EndpointContext for requests + * Michał Wadowski (Orange) - Add Observe-Composite feature. + * Michał Wadowski (Orange) - Add Cancel Composite-Observation feature. + *******************************************************************************/ +package org.eclipse.leshan.server.californium.bootstrap.request; + +import org.eclipse.californium.core.coap.MediaTypeRegistry; +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.identity.IdentityHandler; +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.node.LwM2mPath; +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.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.ContentFormat; +import org.eclipse.leshan.core.request.CreateRequest; +import org.eclipse.leshan.core.request.DeleteRequest; +import org.eclipse.leshan.core.request.DiscoverRequest; +import org.eclipse.leshan.core.request.DownlinkRequest; +import org.eclipse.leshan.core.request.DownlinkRequestVisitor; +import org.eclipse.leshan.core.request.ExecuteRequest; +import org.eclipse.leshan.core.request.Identity; +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.WriteAttributesRequest; +import org.eclipse.leshan.core.request.WriteCompositeRequest; +import org.eclipse.leshan.core.request.WriteRequest; + +/** + * This class is able to create CoAP request from LWM2M {@link DownlinkRequest}. + *

+ * Call CoapRequestBuilder#visit(lwm2mRequest), then get the result using {@link #getRequest()} + */ +public class CoapRequestBuilder implements DownlinkRequestVisitor { + + private Request coapRequest; + + // client information + private final Identity destination; + private final LwM2mModel model; + private final LwM2mEncoder encoder; + private final IdentityHandler identityHandler; + + public CoapRequestBuilder(Identity destination, LwM2mModel model, LwM2mEncoder encoder, + IdentityHandler identityHandler) { + this.destination = destination; + this.model = model; + this.encoder = encoder; + this.identityHandler = identityHandler; + } + + @Override + public void visit(ReadRequest request) { + } + + @Override + public void visit(DiscoverRequest request) { + } + + @Override + public void visit(WriteRequest request) { + } + + @Override + public void visit(WriteAttributesRequest request) { + } + + @Override + public void visit(ExecuteRequest request) { + } + + @Override + public void visit(CreateRequest request) { + } + + @Override + public void visit(DeleteRequest request) { + } + + @Override + public void visit(ObserveRequest request) { + } + + @Override + public void visit(CancelObservationRequest request) { + } + + @Override + public void visit(ReadCompositeRequest request) { + } + + @Override + public void visit(ObserveCompositeRequest request) { + } + + @Override + public void visit(CancelCompositeObservationRequest request) { + } + + @Override + public void visit(WriteCompositeRequest request) { + } + + @Override + public void visit(BootstrapWriteRequest request) { + coapRequest = Request.newPut(); + coapRequest.setConfirmable(true); + ContentFormat format = request.getContentFormat(); + coapRequest.getOptions().setContentFormat(format.getCode()); + coapRequest.setPayload(encoder.encode(request.getNode(), format, request.getPath(), model)); + setURI(coapRequest, request.getPath()); + setSecurityContext(coapRequest); + } + + @Override + public void visit(BootstrapReadRequest request) { + coapRequest = Request.newGet(); + if (request.getContentFormat() != null) + coapRequest.getOptions().setAccept(request.getContentFormat().getCode()); + setURI(coapRequest, request.getPath()); + setSecurityContext(coapRequest); + } + + @Override + public void visit(BootstrapDiscoverRequest request) { + coapRequest = Request.newGet(); + setURI(coapRequest, request.getPath()); + setSecurityContext(coapRequest); + coapRequest.getOptions().setAccept(MediaTypeRegistry.APPLICATION_LINK_FORMAT); + } + + @Override + public void visit(BootstrapDeleteRequest request) { + coapRequest = Request.newDelete(); + coapRequest.setConfirmable(true); + setSecurityContext(coapRequest); + setURI(coapRequest, request.getPath()); + } + + @Override + public void visit(BootstrapFinishRequest request) { + coapRequest = Request.newPost(); + coapRequest.setConfirmable(true); + setSecurityContext(coapRequest); + coapRequest.getOptions().addUriPath("bs"); + } + + protected void setURI(Request coapRequest, LwM2mPath path) { + // objectId + if (path.getObjectId() != null) { + coapRequest.getOptions().addUriPath(Integer.toString(path.getObjectId())); + } + + // objectInstanceId + if (path.getObjectInstanceId() == null) { + if (path.getResourceId() != null) { + coapRequest.getOptions().addUriPath("0"); // default instanceId + } + } else { + coapRequest.getOptions().addUriPath(Integer.toString(path.getObjectInstanceId())); + } + + // resourceId + if (path.getResourceId() != null) { + coapRequest.getOptions().addUriPath(Integer.toString(path.getResourceId())); + } + + // resourceInstanceId + if (path.getResourceInstanceId() != null) { + coapRequest.getOptions().addUriPath(Integer.toString(path.getResourceInstanceId())); + } + } + + protected void setSecurityContext(Request coapRequest) { + if (identityHandler != null) { + EndpointContext context = identityHandler.createEndpointContext(destination, false); + coapRequest.setDestinationContext(context); + } + if (destination.isOSCORE()) { + coapRequest.getOptions().setOscore(Bytes.EMPTY); + } + } + + public Request getRequest() { + return coapRequest; + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/request/LwM2mResponseBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/request/LwM2mResponseBuilder.java new file mode 100644 index 0000000000..bf40faee56 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/request/LwM2mResponseBuilder.java @@ -0,0 +1,276 @@ +/******************************************************************************* + * 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: + * Sierra Wireless - initial API and implementation + * Michał Wadowski (Orange) - Add Observe-Composite feature. + * Michał Wadowski (Orange) - Add Cancel Composite-Observation feature. + * Michał Wadowski (Orange) - Improved compliance with rfc6690. + * Rikard Höglund (RISE SICS) - Additions to support OSCORE + *******************************************************************************/ +package org.eclipse.leshan.server.californium.bootstrap.request; + +import static org.eclipse.leshan.core.californium.ResponseCodeUtil.toLwM2mResponseCode; + +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.link.LinkParseException; +import org.eclipse.leshan.core.link.lwm2m.LwM2mLink; +import org.eclipse.leshan.core.link.lwm2m.LwM2mLinkParser; +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.CodecException; +import org.eclipse.leshan.core.node.codec.LwM2mDecoder; +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.ContentFormat; +import org.eclipse.leshan.core.request.CreateRequest; +import org.eclipse.leshan.core.request.DeleteRequest; +import org.eclipse.leshan.core.request.DiscoverRequest; +import org.eclipse.leshan.core.request.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.WriteAttributesRequest; +import org.eclipse.leshan.core.request.WriteCompositeRequest; +import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.request.exception.InvalidResponseException; +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.LwM2mResponse; +import org.eclipse.leshan.core.util.Hex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is able to create a {@link LwM2mResponse} from a CoAP {@link Response}. + *

+ * Call LwM2mResponseBuilder#visit(coapResponse), then get the result using {@link #getResponse()} + * + * @param the type of the response to build. + */ +public class LwM2mResponseBuilder implements DownlinkRequestVisitor { + + private static final Logger LOG = LoggerFactory.getLogger(LwM2mResponseBuilder.class); + + private LwM2mResponse lwM2mresponse; + private final Response coapResponse; + private final String clientEndpoint; + private final LwM2mModel model; + private final LwM2mDecoder decoder; + private final LwM2mLinkParser linkParser; + + public LwM2mResponseBuilder(Response coapResponse, String clientEndpoint, LwM2mModel model, LwM2mDecoder decoder, + LwM2mLinkParser linkParser) { + this.coapResponse = coapResponse; + this.clientEndpoint = clientEndpoint; + this.model = model; + this.decoder = decoder; + this.linkParser = linkParser; + } + + @Override + public void visit(ReadRequest request) { + } + + @Override + public void visit(DiscoverRequest request) { + } + + @Override + public void visit(WriteRequest request) { + } + + @Override + public void visit(WriteAttributesRequest request) { + } + + @Override + public void visit(ExecuteRequest request) { + } + + @Override + public void visit(CreateRequest request) { + } + + @Override + public void visit(DeleteRequest request) { + } + + @Override + public void visit(ObserveRequest request) { + } + + @Override + public void visit(CancelObservationRequest request) { + } + + @Override + public void visit(ReadCompositeRequest request) { + } + + @Override + public void visit(ObserveCompositeRequest request) { + } + + @Override + public void visit(CancelCompositeObservationRequest request) { + } + + @Override + public void visit(WriteCompositeRequest request) { + } + + @Override + public void visit(BootstrapDiscoverRequest request) { + if (coapResponse.isError()) { + // handle error response: + lwM2mresponse = new BootstrapDiscoverResponse(toLwM2mResponseCode(coapResponse.getCode()), null, + coapResponse.getPayloadString(), coapResponse); + } else if (isResponseCodeContent()) { + // handle success response: + LwM2mLink[] links; + if (MediaTypeRegistry.APPLICATION_LINK_FORMAT != coapResponse.getOptions().getContentFormat()) { + throw new InvalidResponseException("Client [%s] returned unexpected content format [%s] for [%s]", + clientEndpoint, coapResponse.getOptions().getContentFormat(), request); + } else { + try { + links = linkParser.parseLwM2mLinkFromCoreLinkFormat(coapResponse.getPayload(), null); + } catch (LinkParseException e) { + throw new InvalidResponseException(e, + "Unable to decode response payload of request [%s] from client [%s]", request, + clientEndpoint); + } + } + lwM2mresponse = new BootstrapDiscoverResponse(ResponseCode.CONTENT, links, null, coapResponse); + } else { + // handle unexpected response: + handleUnexpectedResponseCode(clientEndpoint, request, coapResponse); + } + } + + @Override + public void visit(BootstrapWriteRequest request) { + if (coapResponse.isError()) { + // handle error response: + lwM2mresponse = new BootstrapWriteResponse(toLwM2mResponseCode(coapResponse.getCode()), + coapResponse.getPayloadString(), coapResponse); + } else if (isResponseCodeChanged()) { + // handle success response: + lwM2mresponse = new BootstrapWriteResponse(ResponseCode.CHANGED, null, coapResponse); + } else { + // handle unexpected response: + handleUnexpectedResponseCode(clientEndpoint, request, coapResponse); + } + } + + @Override + public void visit(BootstrapReadRequest request) { + if (coapResponse.isError()) { + // handle error response: + lwM2mresponse = new BootstrapReadResponse(toLwM2mResponseCode(coapResponse.getCode()), null, + coapResponse.getPayloadString(), coapResponse); + } else if (isResponseCodeContent()) { + // handle success response: + LwM2mNode content = decodeCoapResponse(request.getPath(), coapResponse, request, clientEndpoint); + lwM2mresponse = new BootstrapReadResponse(ResponseCode.CONTENT, content, null, coapResponse); + } else { + // handle unexpected response: + handleUnexpectedResponseCode(clientEndpoint, request, coapResponse); + } + } + + @Override + public void visit(BootstrapDeleteRequest request) { + if (coapResponse.isError()) { + // handle error response: + lwM2mresponse = new BootstrapDeleteResponse(toLwM2mResponseCode(coapResponse.getCode()), + coapResponse.getPayloadString(), coapResponse); + } else if (coapResponse.getCode() == org.eclipse.californium.core.coap.CoAP.ResponseCode.DELETED) { + // handle success response: + lwM2mresponse = new BootstrapDeleteResponse(ResponseCode.DELETED, null, coapResponse); + } else { + // handle unexpected response: + handleUnexpectedResponseCode(clientEndpoint, request, coapResponse); + } + } + + @Override + public void visit(BootstrapFinishRequest request) { + if (coapResponse.isError()) { + // handle error response: + lwM2mresponse = new BootstrapFinishResponse(toLwM2mResponseCode(coapResponse.getCode()), + coapResponse.getPayloadString(), coapResponse); + } else if (isResponseCodeChanged()) { + // handle success response: + lwM2mresponse = new BootstrapFinishResponse(ResponseCode.CHANGED, null, coapResponse); + } else { + // handle unexpected response: + handleUnexpectedResponseCode(clientEndpoint, request, coapResponse); + } + } + + private boolean isResponseCodeContent() { + return coapResponse.getCode() == org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; + } + + private boolean isResponseCodeChanged() { + return coapResponse.getCode() == org.eclipse.californium.core.coap.CoAP.ResponseCode.CHANGED; + } + + private LwM2mNode decodeCoapResponse(LwM2mPath path, Response coapResponse, LwM2mRequest request, + String endpoint) { + + // Get content format + ContentFormat contentFormat = null; + if (coapResponse.getOptions().hasContentFormat()) { + contentFormat = ContentFormat.fromCode(coapResponse.getOptions().getContentFormat()); + } + + // Decode payload + try { + return decoder.decode(coapResponse.getPayload(), contentFormat, path, model); + } catch (CodecException e) { + if (LOG.isDebugEnabled()) { + byte[] payload = coapResponse.getPayload() == null ? new byte[0] : coapResponse.getPayload(); + LOG.debug( + String.format("Unable to decode response payload of request [%s] from client [%s] [payload:%s]", + request, endpoint, Hex.encodeHexString(payload))); + } + throw new InvalidResponseException(e, "Unable to decode response payload of request [%s] from client [%s]", + request, endpoint); + } + } + + @SuppressWarnings("unchecked") + public T getResponse() { + return (T) lwM2mresponse; + } + + private void handleUnexpectedResponseCode(String clientEndpoint, LwM2mRequest request, Response coapResponse) { + throw new InvalidResponseException("Client [%s] returned unexpected response code [%s] for [%s]", + clientEndpoint, coapResponse.getCode(), request); + } +} diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilderTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilderTest.java index fe6220a5a6..2ac8eb64bc 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilderTest.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilderTest.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.eclipse.leshan.server.californium.bootstrap; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -37,12 +38,19 @@ import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.scandium.config.DtlsConfig; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; +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.Hex; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.BootstrapSession; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServerBuilder; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointsProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointsProvider.Builder; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coap.CoapBootstrapServerProtocolProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coaps.CoapsBootstrapServerProtocolProvider; import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.SecurityInfo; import org.junit.Before; @@ -97,39 +105,31 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe } @Test - public void create_server_minimal_parameters() { + public void create_server_with_default_californiumEndpointsProvider() { + builder.setEndpointsProvider(new CaliforniumBootstrapServerEndpointsProvider()); server = builder.build(); - assertNull(server.getSecuredAddress()); - assertNotNull(server.getUnsecuredAddress()); + assertEquals(1, server.getEndpoints().size()); + assertEquals(Protocol.COAP, server.getEndpoints().get(0).getProtocol()); } @Test - public void create_server_with_securityStore() { - builder.setSecurityStore(new BootstrapSecurityStore() { - @Override - public SecurityInfo getByIdentity(String pskIdentity) { - return null; - } - - @Override - public Iterator getAllByEndpoint(String endpoint) { - return null; - } - - @Override - public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { - return null; - } - }); + public void create_server_without_securityStore() { + Builder endpointsBuilder = new CaliforniumBootstrapServerEndpointsProvider.Builder( + new CoapBootstrapServerProtocolProvider(), new CoapsBootstrapServerProtocolProvider()); + builder.setEndpointsProvider(endpointsBuilder.build()); server = builder.build(); - assertNotNull(server.getSecuredAddress()); - assertNotNull(server.getUnsecuredAddress()); + assertEquals(1, server.getEndpoints().size()); + assertEquals(Protocol.COAP, server.getEndpoints().get(0).getProtocol()); + assertNull(server.getSecurityStore()); } @Test - public void create_server_with_securityStore_and_disable_secured_endpoint() { + public void create_server_with_securityStore() { + Builder endpointsBuilder = new CaliforniumBootstrapServerEndpointsProvider.Builder( + new CoapBootstrapServerProtocolProvider(), new CoapsBootstrapServerProtocolProvider()); + builder.setEndpointsProvider(endpointsBuilder.build()); builder.setSecurityStore(new BootstrapSecurityStore() { @Override public SecurityInfo getByIdentity(String pskIdentity) { @@ -146,15 +146,19 @@ public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { return null; } }); - builder.disableSecuredEndpoint(); server = builder.build(); - assertNull(server.getSecuredAddress()); - assertNotNull(server.getUnsecuredAddress()); + assertEquals(2, server.getEndpoints().size()); + assertEquals(Protocol.COAP, server.getEndpoints().get(0).getProtocol()); + assertEquals(Protocol.COAPS, server.getEndpoints().get(1).getProtocol()); + assertNotNull(server.getSecurityStore()); } @Test - public void create_server_with_securityStore_and_disable_unsecured_endpoint() { + public void create_server_with_coaps_only() { + Builder endpointsBuilder = new CaliforniumBootstrapServerEndpointsProvider.Builder( + new CoapsBootstrapServerProtocolProvider()); + builder.setEndpointsProvider(endpointsBuilder.build()); builder.setSecurityStore(new BootstrapSecurityStore() { @Override public SecurityInfo getByIdentity(String pskIdentity) { @@ -171,18 +175,22 @@ public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { return null; } }); - builder.disableUnsecuredEndpoint(); server = builder.build(); - assertNotNull(server.getSecuredAddress()); - assertNull(server.getUnsecuredAddress()); + assertEquals(1, server.getEndpoints().size()); + assertEquals(Protocol.COAPS, server.getEndpoints().get(0).getProtocol()); + assertNotNull(server.getSecurityStore()); } @Test public void create_server_without_psk_cipher() { - Configuration coapConfiguration = LeshanBootstrapServerBuilder.createDefaultCoapConfiguration(); + Builder endpointsBuilder = new CaliforniumBootstrapServerEndpointsProvider.Builder( + new CoapsBootstrapServerProtocolProvider()); + Configuration coapConfiguration = endpointsBuilder.createDefaultConfiguration(); coapConfiguration.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8); - builder.setCoapConfig(coapConfiguration); + endpointsBuilder.setConfiguration(coapConfiguration); + builder.setEndpointsProvider(endpointsBuilder.build()); + builder.setPrivateKey(privateKey); builder.setPublicKey(publicKey); builder.setSecurityStore(new BootstrapSecurityStore() { @@ -203,6 +211,8 @@ public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { }); server = builder.build(); - assertNotNull(server.getSecuredAddress()); + + assertEquals(1, server.getEndpoints().size()); + assertEquals(Protocol.COAPS, server.getEndpoints().get(0).getProtocol()); } } diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java index b1ac757be9..b0cbdb151a 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java @@ -18,7 +18,9 @@ import static org.junit.Assert.assertEquals; import java.net.InetSocketAddress; +import java.net.URI; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.response.BootstrapResponse; @@ -31,7 +33,10 @@ import org.eclipse.leshan.server.bootstrap.BootstrapSessionListener; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapHandler; -import org.eclipse.leshan.server.bootstrap.LwM2mBootstrapRequestSender; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServerBuilder; +import org.eclipse.leshan.server.bootstrap.request.BootstrapDownlinkRequestSender; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointsProvider; import org.junit.Test; public class LeshanBootstrapServerTest { @@ -39,13 +44,12 @@ public class LeshanBootstrapServerTest { private BootstrapHandler bsHandler; private LeshanBootstrapServer createBootstrapServer() { - LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder() - .setLocalAddress(new InetSocketAddress(0)); + LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); builder.setBootstrapHandlerFactory(new BootstrapHandlerFactory() { @Override - public BootstrapHandler create(LwM2mBootstrapRequestSender sender, BootstrapSessionManager sessionManager, - BootstrapSessionListener listener) { + public BootstrapHandler create(BootstrapDownlinkRequestSender sender, + BootstrapSessionManager sessionManager, BootstrapSessionListener listener) { bsHandler = new DefaultBootstrapHandler(sender, sessionManager, listener); return bsHandler; } @@ -59,6 +63,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe return config; } }); + builder.setEndpointsProvider(new CaliforniumBootstrapServerEndpointsProvider()); return builder.build(); } @@ -83,7 +88,7 @@ public void testStartDestroy() throws InterruptedException { server.start(); Thread.sleep(100); // HACK force creation thread creation. - forceThreadsCreation(); + forceThreadsCreation(server.getEndpoint(Protocol.COAP).getURI()); Thread.sleep(100); server.destroy(); @@ -101,7 +106,7 @@ public void testStartStopDestroy() throws InterruptedException { server.start(); Thread.sleep(100); // HACK force creation thread creation. - forceThreadsCreation(); + forceThreadsCreation(server.getEndpoint(Protocol.COAP).getURI()); Thread.sleep(100); server.stop(); Thread.sleep(100); @@ -112,9 +117,9 @@ public void testStartStopDestroy() throws InterruptedException { assertEquals("All news created threads must be destroyed", numberOfThreadbefore, Thread.activeCount()); } - private void forceThreadsCreation() { + private void forceThreadsCreation(URI endpointURI) { SendableResponse bootstrap = bsHandler - .bootstrap(Identity.unsecure(new InetSocketAddress(5683)), new BootstrapRequest("test")); + .bootstrap(Identity.unsecure(new InetSocketAddress(5683)), new BootstrapRequest("test"), endpointURI); bootstrap.sent(); } } diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java index 79b0de28c1..f467ccffd1 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java @@ -15,6 +15,8 @@ *******************************************************************************/ package org.eclipse.leshan.server.bootstrap; +import java.net.URI; + import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.response.BootstrapResponse; @@ -28,5 +30,5 @@ */ public interface BootstrapHandler { - SendableResponse bootstrap(Identity sender, BootstrapRequest request); + SendableResponse bootstrap(Identity sender, BootstrapRequest request, URI serverEndpointUri); } diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java index 75464f58b5..ae1a714731 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java @@ -15,6 +15,8 @@ *******************************************************************************/ package org.eclipse.leshan.server.bootstrap; +import org.eclipse.leshan.server.bootstrap.request.BootstrapDownlinkRequestSender; + /** * Creates {@link BootstrapHandler}. * @@ -30,6 +32,6 @@ public interface BootstrapHandlerFactory { * @param listener a listener of bootstrap session life-cycle. * @return the new {@link BootstrapHandler}. */ - BootstrapHandler create(LwM2mBootstrapRequestSender sender, BootstrapSessionManager sessionManager, + BootstrapHandler create(BootstrapDownlinkRequestSender sender, BootstrapSessionManager sessionManager, BootstrapSessionListener listener); } diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSession.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSession.java index 72c9d8dc0a..315707502f 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSession.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSession.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.eclipse.leshan.server.bootstrap; +import java.net.URI; import java.util.Map; import org.eclipse.leshan.core.model.LwM2mModel; @@ -27,7 +28,7 @@ * Represent a single Bootstrapping session. * * Should be created by {@link BootstrapSessionManager} implementations in - * {@link BootstrapSessionManager#begin(BootstrapRequest, Identity)}. + * {@link BootstrapSessionManager#begin(BootstrapRequest, Identity, URI)}. */ public interface BootstrapSession { @@ -51,6 +52,8 @@ public interface BootstrapSession { */ Identity getIdentity(); + URI getEndpointUsed(); + /** * @return true if the LwM2M client is authorized to start a bootstrap session. */ diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java index 72c368dfd3..2e2bdf4b76 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java @@ -15,6 +15,8 @@ *******************************************************************************/ package org.eclipse.leshan.server.bootstrap; +import java.net.URI; + import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.Identity; @@ -100,11 +102,11 @@ public String toString() { * * @return a BootstrapSession, possibly authorized. */ - public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity); + public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity, URI endpointUsed); /** - * Generally called after {@link #begin(BootstrapRequest, Identity)} to know if there is something to do on this - * device. + * Generally called after {@link #begin(BootstrapRequest, Identity, URI)} to know if there is something to do on + * this device. * * @param bsSession the bootstrap session concerned. * @return true if there is a bootstrap requests to send for this client. diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java index 6c150a1413..f7e1c14724 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java @@ -22,6 +22,7 @@ import static org.eclipse.leshan.server.bootstrap.BootstrapFailureCause.REQUEST_FAILED; import static org.eclipse.leshan.server.bootstrap.BootstrapFailureCause.UNAUTHORIZED; +import java.net.URI; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; @@ -35,6 +36,7 @@ import org.eclipse.leshan.core.response.SendableResponse; import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager.BootstrapPolicy; +import org.eclipse.leshan.server.bootstrap.request.BootstrapDownlinkRequestSender; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,19 +58,19 @@ public class DefaultBootstrapHandler implements BootstrapHandler { // send a Confirmable message to the time when an acknowledgement is no longer expected. public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000l; // 2min in ms - protected final LwM2mBootstrapRequestSender sender; + protected final BootstrapDownlinkRequestSender sender; protected final long requestTimeout; protected final ConcurrentHashMap onGoingSession = new ConcurrentHashMap<>(); protected final BootstrapSessionManager sessionManager; protected final BootstrapSessionListener listener; - public DefaultBootstrapHandler(LwM2mBootstrapRequestSender sender, BootstrapSessionManager sessionManager, + public DefaultBootstrapHandler(BootstrapDownlinkRequestSender sender, BootstrapSessionManager sessionManager, BootstrapSessionListener listener) { this(sender, sessionManager, listener, DEFAULT_TIMEOUT); } - public DefaultBootstrapHandler(LwM2mBootstrapRequestSender sender, BootstrapSessionManager sessionManager, + public DefaultBootstrapHandler(BootstrapDownlinkRequestSender sender, BootstrapSessionManager sessionManager, BootstrapSessionListener listener, long requestTimeout) { Validate.notNull(sender); Validate.notNull(sessionManager); @@ -80,12 +82,12 @@ public DefaultBootstrapHandler(LwM2mBootstrapRequestSender sender, BootstrapSess } @Override - public SendableResponse bootstrap(Identity sender, BootstrapRequest request) { + public SendableResponse bootstrap(Identity sender, BootstrapRequest request, URI endpointUsed) { String endpoint = request.getEndpointName(); // Start session, checking the BS credentials final BootstrapSession session; - session = sessionManager.begin(request, sender); + session = sessionManager.begin(request, sender, endpointUsed); listener.sessionInitiated(request, sender); if (!session.isAuthorized()) { @@ -215,7 +217,7 @@ protected void send(BootstrapSession session, Bootstra protected abstract class SafeResponseCallback implements ResponseCallback { - private BootstrapSession session; + private final BootstrapSession session; public SafeResponseCallback(BootstrapSession session) { this.session = session; @@ -236,7 +238,7 @@ public void onResponse(T response) { protected abstract class SafeErrorCallback implements ErrorCallback { - private BootstrapSession session; + private final BootstrapSession session; public SafeErrorCallback(BootstrapSession session) { this.session = session; diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java index 97ca7f65ce..083f256b95 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.eclipse.leshan.server.bootstrap; +import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -41,6 +42,7 @@ public class DefaultBootstrapSession implements BootstrapSession { private final ContentFormat contentFormat; private final Map applicationData; private final long creationTime; + private final URI endpointUsed; private final BootstrapRequest request; private volatile LwM2mModel model; @@ -60,8 +62,8 @@ public class DefaultBootstrapSession implements BootstrapSession { * @param applicationData Data that could be attached to a session. */ public DefaultBootstrapSession(BootstrapRequest request, Identity identity, boolean authorized, - Map applicationData) { - this(request, identity, authorized, null, applicationData); + Map applicationData, URI endpointUsed) { + this(request, identity, authorized, null, applicationData, endpointUsed); } /** @@ -74,8 +76,8 @@ public DefaultBootstrapSession(BootstrapRequest request, Identity identity, bool * @param applicationData Data that could be attached to a session. */ public DefaultBootstrapSession(BootstrapRequest request, Identity identity, boolean authorized, - ContentFormat contentFormat, Map applicationData) { - this(request, identity, authorized, contentFormat, applicationData, System.currentTimeMillis()); + ContentFormat contentFormat, Map applicationData, URI endpointUsed) { + this(request, identity, authorized, contentFormat, applicationData, System.currentTimeMillis(), endpointUsed); } /** @@ -89,12 +91,13 @@ public DefaultBootstrapSession(BootstrapRequest request, Identity identity, bool * @param creationTime The creation time of this session in ms. */ public DefaultBootstrapSession(BootstrapRequest request, Identity identity, boolean authorized, - ContentFormat contentFormat, Map applicationData, long creationTime) { + ContentFormat contentFormat, Map applicationData, long creationTime, URI endpointUsed) { Validate.notNull(request); this.id = RandomStringUtils.random(10, true, true); this.request = request; this.endpoint = request.getEndpointName(); this.identity = identity; + this.endpointUsed = endpointUsed; this.authorized = authorized; if (contentFormat == null) { if (request.getPreferredContentFormat() != null) { @@ -128,6 +131,11 @@ public Identity getIdentity() { return identity; } + @Override + public URI getEndpointUsed() { + return endpointUsed; + } + @Override public boolean isAuthorized() { return authorized; diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java index 8e0a715741..c3b4c10b76 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.eclipse.leshan.server.bootstrap; +import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -72,10 +73,10 @@ public DefaultBootstrapSessionManager(BootstrapTaskProvider tasksProvider, } @Override - public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { + public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity, URI endpointUsed) { Authorization authorization = authorizer.isAuthorized(request, clientIdentity); DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, - authorization.isApproved(), authorization.getApplicationData()); + authorization.isApproved(), authorization.getApplicationData(), endpointUsed); LOG.trace("Bootstrap session started : {}", session); return session; diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LeshanBootstrapServer.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LeshanBootstrapServer.java new file mode 100644 index 0000000000..aa1f283e54 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LeshanBootstrapServer.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * 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: + * Sierra Wireless - initial API and implementation + * Michał Wadowski (Orange) - Improved compliance with rfc6690 + *******************************************************************************/ +package org.eclipse.leshan.server.bootstrap; + +import java.util.List; + +import org.eclipse.leshan.core.Destroyable; +import org.eclipse.leshan.core.Startable; +import org.eclipse.leshan.core.Stoppable; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.link.lwm2m.LwM2mLinkParser; +import org.eclipse.leshan.core.node.codec.LwM2mDecoder; +import org.eclipse.leshan.core.node.codec.LwM2mEncoder; +import org.eclipse.leshan.core.util.Validate; +import org.eclipse.leshan.server.bootstrap.endpoint.BootstrapServerEndpointToolbox; +import org.eclipse.leshan.server.bootstrap.endpoint.LwM2mBootstrapServerEndpoint; +import org.eclipse.leshan.server.bootstrap.endpoint.LwM2mBootstrapServerEndpointsProvider; +import org.eclipse.leshan.server.bootstrap.request.BootstrapDownlinkRequestSender; +import org.eclipse.leshan.server.bootstrap.request.DefaultBootstrapDownlinkRequestSender; +import org.eclipse.leshan.server.bootstrap.request.DefaultBootstrapUplinkRequestReceiver; +import org.eclipse.leshan.server.security.BootstrapSecurityStore; +import org.eclipse.leshan.server.security.ServerSecurityInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Lightweight M2M server, serving bootstrap information on /bs. + */ +public class LeshanBootstrapServer { + + private final static Logger LOG = LoggerFactory.getLogger(LeshanBootstrapServer.class); + + private final BootstrapSessionDispatcher dispatcher = new BootstrapSessionDispatcher(); + + private final BootstrapDownlinkRequestSender requestSender; + private final LwM2mBootstrapServerEndpointsProvider endpointsProvider; + private final BootstrapSecurityStore securityStore; + + /** + * /** Initialize a server which will bind to the specified address and port. + *

+ * {@link LeshanBootstrapServerBuilder} is the priviledged way to create a {@link LeshanBootstrapServer}. + * + * @param bsSessionManager manages life cycle of a bootstrap process + * @param bsHandlerFactory responsible to create the {@link BootstrapHandler} + * @param encoder encode used to encode request payload. + * @param decoder decoder used to decode response payload. + * @param linkParser a parser {@link LwM2mLinkParser} used to parse a CoRE Link. + */ + public LeshanBootstrapServer(LwM2mBootstrapServerEndpointsProvider endpointsProvider, + BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, LwM2mEncoder encoder, + LwM2mDecoder decoder, LwM2mLinkParser linkParser, BootstrapSecurityStore securityStore, + ServerSecurityInfo serverSecurityInfo) { + + Validate.notNull(endpointsProvider, "endpoints provider must not be null"); + Validate.notNull(bsSessionManager, "session manager must not be null"); + Validate.notNull(bsHandlerFactory, "BootstrapHandler factory must not be null"); + this.endpointsProvider = endpointsProvider; + this.securityStore = securityStore; + + // create request sender + requestSender = createRequestSender(endpointsProvider); + + // create endpoints + BootstrapServerEndpointToolbox toolbox = new BootstrapServerEndpointToolbox(decoder, encoder, linkParser); + DefaultBootstrapUplinkRequestReceiver requestReceiver = new DefaultBootstrapUplinkRequestReceiver( + bsHandlerFactory.create(requestSender, bsSessionManager, dispatcher)); + endpointsProvider.createEndpoints(requestReceiver, toolbox, serverSecurityInfo, this); + } + + protected BootstrapDownlinkRequestSender createRequestSender( + LwM2mBootstrapServerEndpointsProvider endpointsProvider) { + return new DefaultBootstrapDownlinkRequestSender(endpointsProvider); + } + + /** + * Starts the server and binds it to the specified port. + */ + public void start() { + if (requestSender instanceof Startable) { + ((Startable) requestSender).start(); + } + + endpointsProvider.start(); + + if (LOG.isInfoEnabled()) { + LOG.info("Bootstrap server started."); + for (LwM2mBootstrapServerEndpoint endpoint : endpointsProvider.getEndpoints()) { + LOG.info("{} endpoint available at {}.", endpoint.getProtocol().getName(), endpoint.getURI()); + } + } + } + + /** + * Stops the server and unbinds it from assigned ports (can be restarted). + */ + public void stop() { + endpointsProvider.stop(); + + if (requestSender instanceof Stoppable) { + ((Stoppable) requestSender).stop(); + } + LOG.info("Bootstrap server stopped."); + } + + /** + * Destroys the server, unbinds from all ports and frees all system resources. + *

+ * Server can not be restarted anymore. + */ + public void destroy() { + endpointsProvider.destroy(); + + if (requestSender instanceof Destroyable) { + ((Destroyable) requestSender).destroy(); + } else if (requestSender instanceof Stoppable) { + ((Stoppable) requestSender).stop(); + } + LOG.info("Bootstrap server destroyed."); + } + + public void addListener(BootstrapSessionListener listener) { + dispatcher.addListener(listener); + } + + public void removeListener(BootstrapSessionListener listener) { + dispatcher.removeListener(listener); + } + + public List getEndpoints() { + return endpointsProvider.getEndpoints(); + } + + public LwM2mBootstrapServerEndpoint getEndpoint(Protocol protocol) { + for (LwM2mBootstrapServerEndpoint endpoint : endpointsProvider.getEndpoints()) { + if (endpoint.getProtocol().equals(protocol)) { + return endpoint; + } + } + return null; + } + + public BootstrapSecurityStore getSecurityStore() { + return securityStore; + } +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LeshanBootstrapServerBuilder.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LeshanBootstrapServerBuilder.java new file mode 100644 index 0000000000..2dc2cb3f0d --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LeshanBootstrapServerBuilder.java @@ -0,0 +1,314 @@ +/******************************************************************************* + * 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 + * Achim Kraus (Bosch Software Innovations GmbH) - use CoapEndpointBuilder + * Michał Wadowski (Orange) - Improved compliance with rfc6690 + *******************************************************************************/ +package org.eclipse.leshan.server.bootstrap; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; +import org.eclipse.leshan.core.link.lwm2m.LwM2mLinkParser; +import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; +import org.eclipse.leshan.core.node.codec.LwM2mDecoder; +import org.eclipse.leshan.core.node.codec.LwM2mEncoder; +import org.eclipse.leshan.server.bootstrap.endpoint.LwM2mBootstrapServerEndpointsProvider; +import org.eclipse.leshan.server.bootstrap.request.BootstrapDownlinkRequestSender; +import org.eclipse.leshan.server.model.LwM2mBootstrapModelProvider; +import org.eclipse.leshan.server.model.StandardBootstrapModelProvider; +import org.eclipse.leshan.server.security.BootstrapAuthorizer; +import org.eclipse.leshan.server.security.BootstrapSecurityStore; +import org.eclipse.leshan.server.security.SecurityChecker; +import org.eclipse.leshan.server.security.ServerSecurityInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class helping you to build and configure a Californium based Leshan Bootstrap Lightweight M2M server. + *

+ * Usage: create it, call the different setters for changing the configuration and then call the {@link #build()} method + * for creating the {@link LeshanBootstrapServer} ready to operate. + */ +public class LeshanBootstrapServerBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(LeshanBootstrapServerBuilder.class); + + private BootstrapConfigStore configStore; + private BootstrapSecurityStore securityStore; + private BootstrapSessionManager sessionManager; + private BootstrapHandlerFactory bootstrapHandlerFactory; + private BootstrapAuthorizer authorizer; + + private LwM2mBootstrapModelProvider modelProvider; + + private LwM2mEncoder encoder; + private LwM2mDecoder decoder; + + private PublicKey publicKey; + private PrivateKey privateKey; + private X509Certificate[] certificateChain; + private Certificate[] trustedCertificates; + + private LwM2mLinkParser linkParser; + + private LwM2mBootstrapServerEndpointsProvider endpointsProvider; + + /** + * Set the {@link PublicKey} of the server which will be used for Raw Public Key DTLS authentication. + *

+ * This should be used for RPK support only. + *

+ * Setting publicKey and privateKey will enable RawPublicKey DTLS authentication, see also + * {@link LeshanBootstrapServerBuilder#setPrivateKey(PrivateKey)}. + * + * @param publicKey the Raw Public Key of the bootstrap server. + * @return the builder for fluent Bootstrap Server creation. + */ + public LeshanBootstrapServerBuilder setPublicKey(PublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + /** + * Set the CertificateChain of the server which will be used for X.509 DTLS authentication. + *

+ * Setting publicKey and privateKey will enable RPK and X.509 DTLS authentication, see + * also {@link LeshanBootstrapServerBuilder#setPrivateKey(PrivateKey)}. + *

+ * For RPK the public key will be extracted from the first X.509 certificate of the certificate chain. If you only + * need RPK support, use {@link LeshanBootstrapServerBuilder#setPublicKey(PublicKey)} instead. + * + * @param certificateChain the certificate chain of the bootstrap server. + * @return the builder for fluent Bootstrap Server creation. + */ + public LeshanBootstrapServerBuilder setCertificateChain(T[] certificateChain) { + this.certificateChain = certificateChain; + return this; + } + + /** + * Set the {@link PrivateKey} of the server which will be used for RawPublicKey(RPK) and/or X.509 DTLS + * authentication. + * + * @param privateKey the Private Key of the bootstrap server. + * @return the builder for fluent Bootstrap Server creation. + */ + public LeshanBootstrapServerBuilder setPrivateKey(PrivateKey privateKey) { + this.privateKey = privateKey; + return this; + } + + /** + * The list of trusted certificates used to authenticate devices using X.509 DTLS authentication. + * + * @param trustedCertificates certificates trusted by the bootstrap server. + * @return the builder for fluent Bootstrap Server creation. + */ + public LeshanBootstrapServerBuilder setTrustedCertificates(T[] trustedCertificates) { + this.trustedCertificates = trustedCertificates; + return this; + } + + /** + * Set the {@link BootstrapConfigStore} containing bootstrap configuration to apply to each devices. + *

+ * By default an {@link InMemoryBootstrapConfigStore} is used. + *

+ * See {@link BootstrapConfig} to see what is could be done during a bootstrap session. + * + * @param configStore the bootstrap configuration store. + * @return the builder for fluent Bootstrap Server creation. + * + */ + public LeshanBootstrapServerBuilder setConfigStore(BootstrapConfigStore configStore) { + this.configStore = configStore; + return this; + } + + /** + * Set the {@link BootstrapSecurityStore} which contains data needed to authenticate devices. + *

+ * WARNING: without security store all devices will be accepted which is not really recommended in production + * environment. + *

+ * There is not default implementation. + * + * @param securityStore the security store used to authenticate devices. + * @return the builder for fluent Bootstrap Server creation. + */ + public LeshanBootstrapServerBuilder setSecurityStore(BootstrapSecurityStore securityStore) { + this.securityStore = securityStore; + return this; + } + + /** + * Advanced setter used to define {@link BootstrapSessionManager}. + *

+ * See {@link BootstrapSessionManager} and {@link DefaultBootstrapSessionManager} for more details. + * + * @param sessionManager the manager responsible to handle bootstrap session. + * @return the builder for fluent Bootstrap Server creation. + */ + public LeshanBootstrapServerBuilder setSessionManager(BootstrapSessionManager sessionManager) { + this.sessionManager = sessionManager; + return this; + } + + /** + * Advanced setter used to customize default bootstrap server behavior. + *

+ * If default bootstrap server behavior is not flexible enough, you can create your own {@link BootstrapHandler} by + * inspiring yourself from {@link DefaultBootstrapHandler}. + * + * @param bootstrapHandlerFactory the factory used to create {@link BootstrapHandler}. + * @return the builder for fluent Bootstrap Server creation. + */ + public LeshanBootstrapServerBuilder setBootstrapHandlerFactory(BootstrapHandlerFactory bootstrapHandlerFactory) { + this.bootstrapHandlerFactory = bootstrapHandlerFactory; + return this; + } + + /** + *

+ * Set your {@link LwM2mBootstrapModelProvider} implementation. + *

+ * By default the {@link StandardBootstrapModelProvider}. + * + */ + public LeshanBootstrapServerBuilder setObjectModelProvider(LwM2mBootstrapModelProvider objectModelProvider) { + this.modelProvider = objectModelProvider; + return this; + } + + /** + *

+ * Set the {@link LwM2mEncoder} which will encode {@link LwM2mNode} with supported content format. + *

+ * By default the {@link DefaultLwM2mEncoder} is used. It supports Text, Opaque, TLV and JSON format. + */ + public LeshanBootstrapServerBuilder setEncoder(LwM2mEncoder encoder) { + this.encoder = encoder; + return this; + } + + /** + *

+ * Set the {@link LwM2mDecoder} which will decode data in supported content format to create {@link LwM2mNode}. + *

+ * By default the {@link DefaultLwM2mDecoder} is used. It supports Text, Opaque, TLV and JSON format. + */ + public LeshanBootstrapServerBuilder setDecoder(LwM2mDecoder decoder) { + this.decoder = decoder; + return this; + } + + /** + * Set the CoRE Link parser {@link LwM2mLinkParser} + *

+ * By default the {@link DefaultLwM2mLinkParser} is used. + */ + public LeshanBootstrapServerBuilder setLinkParser(LwM2mLinkParser linkParser) { + this.linkParser = linkParser; + return this; + } + + /** + * Set the Bootstrap authorizer {@link BootstrapAuthorizer} + *

+ * By default the {@link DefaultBootstrapAuthorizer} is used. + */ + public LeshanBootstrapServerBuilder setAuthorizer(BootstrapAuthorizer authorizer) { + this.authorizer = authorizer; + return this; + } + + public LeshanBootstrapServerBuilder setEndpointsProvider(LwM2mBootstrapServerEndpointsProvider endpointsProvider) { + this.endpointsProvider = endpointsProvider; + return this; + } + + /** + * Create the {@link LeshanBootstrapServer}. + *

+ * Next step will be to start it : {@link LeshanBootstrapServer#start()}. + * + * @return the LWM2M Bootstrap server. + * @throws IllegalStateException if builder configuration is not consistent. + */ + public LeshanBootstrapServer build() { + if (bootstrapHandlerFactory == null) + bootstrapHandlerFactory = new BootstrapHandlerFactory() { + @Override + public BootstrapHandler create(BootstrapDownlinkRequestSender sender, + BootstrapSessionManager sessionManager, BootstrapSessionListener listener) { + return new DefaultBootstrapHandler(sender, sessionManager, listener); + } + }; + if (configStore == null) { + configStore = new InMemoryBootstrapConfigStore(); + } else if (sessionManager != null) { + LOG.warn("configStore is set but you also provide a custom SessionManager so this store will not be used"); + } + if (modelProvider == null) { + modelProvider = new StandardBootstrapModelProvider(); + } else if (sessionManager != null) { + LOG.warn( + "modelProvider is set but you also provide a custom SessionManager so this provider will not be used"); + } + if (sessionManager == null) { + SecurityChecker securityChecker = new SecurityChecker(); + if (authorizer == null) + authorizer = new DefaultBootstrapAuthorizer(securityStore, securityChecker); + sessionManager = new DefaultBootstrapSessionManager(new BootstrapConfigStoreTaskProvider(configStore), + modelProvider, authorizer); + } + if (encoder == null) + encoder = new DefaultLwM2mEncoder(); + if (decoder == null) + decoder = new DefaultLwM2mDecoder(); + if (linkParser == null) + linkParser = new DefaultLwM2mLinkParser(); + + return createBootstrapServer(endpointsProvider, sessionManager, bootstrapHandlerFactory, encoder, decoder, + linkParser, securityStore, + new ServerSecurityInfo(privateKey, publicKey, certificateChain, trustedCertificates)); + } + + /** + * Create the LeshanBootstrapServer. + *

+ * You can extend LeshanBootstrapServerBuilder and override this method to create a new builder which + * will be able to build an extended LeshanBootstrapServer. + * + * @param bsSessionManager the manager responsible to handle bootstrap session. + * @param bsHandlerFactory the factory used to create {@link BootstrapHandler}. + * @param decoder decoder used to decode response payload. + * @param encoder encode used to encode request payload. + * @param linkParser a parser {@link LwM2mLinkParser} used to parse a CoRE Link. + * @return the LWM2M Bootstrap server. + */ + protected LeshanBootstrapServer createBootstrapServer(LwM2mBootstrapServerEndpointsProvider endpointsProvider, + BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, LwM2mEncoder encoder, + LwM2mDecoder decoder, LwM2mLinkParser linkParser, BootstrapSecurityStore securityStore, + ServerSecurityInfo serverSecurityInfo) { + return new LeshanBootstrapServer(endpointsProvider, bsSessionManager, bsHandlerFactory, encoder, decoder, + linkParser, securityStore, serverSecurityInfo); + } +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/BootstrapServerEndpointToolbox.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/BootstrapServerEndpointToolbox.java new file mode 100644 index 0000000000..6e79cd0e3f --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/BootstrapServerEndpointToolbox.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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.server.bootstrap.endpoint; + +import org.eclipse.leshan.core.link.lwm2m.LwM2mLinkParser; +import org.eclipse.leshan.core.node.codec.LwM2mDecoder; +import org.eclipse.leshan.core.node.codec.LwM2mEncoder; + +public class BootstrapServerEndpointToolbox { + + private final LwM2mDecoder decoder; + private final LwM2mEncoder encoder; + private final LwM2mLinkParser linkParser; + + public BootstrapServerEndpointToolbox(LwM2mDecoder decoder, LwM2mEncoder encoder, LwM2mLinkParser linkParser) { + this.decoder = decoder; + this.encoder = encoder; + this.linkParser = linkParser; + } + + public LwM2mDecoder getDecoder() { + return decoder; + } + + public LwM2mEncoder getEncoder() { + return encoder; + } + + public LwM2mLinkParser getLinkParser() { + return linkParser; + } +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/LwM2mBootstrapServerEndpoint.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/LwM2mBootstrapServerEndpoint.java new file mode 100644 index 0000000000..d27034d503 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/LwM2mBootstrapServerEndpoint.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * 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.server.bootstrap.endpoint; + +import java.net.URI; + +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; +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.server.bootstrap.BootstrapSession; + +public interface LwM2mBootstrapServerEndpoint { + + Protocol getProtocol(); + + URI getURI(); + + T send(BootstrapSession destination, BootstrapDownlinkRequest request, + long timeoutInMs) throws InterruptedException; + + void send(BootstrapSession destination, BootstrapDownlinkRequest request, + ResponseCallback responseCallback, ErrorCallback errorCallback, long timeoutInMs); + + void cancelRequests(String sessionID); +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/LwM2mBootstrapServerEndpointsProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/LwM2mBootstrapServerEndpointsProvider.java new file mode 100644 index 0000000000..8e07e8c034 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/endpoint/LwM2mBootstrapServerEndpointsProvider.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * 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.server.bootstrap.endpoint; + +import java.net.URI; +import java.util.List; + +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.bootstrap.request.BootstrapUplinkRequestReceiver; +import org.eclipse.leshan.server.security.ServerSecurityInfo; + +public interface LwM2mBootstrapServerEndpointsProvider { + + List getEndpoints(); + + LwM2mBootstrapServerEndpoint getEndpoint(URI uri); + + void createEndpoints(BootstrapUplinkRequestReceiver requestReceiver, BootstrapServerEndpointToolbox toolbox, + ServerSecurityInfo serverSecurityInfo, LeshanBootstrapServer server); + + void start(); + + void stop(); + + void destroy(); + +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/ClientProfile.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/ClientProfile.java new file mode 100644 index 0000000000..b50229faac --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/ClientProfile.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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.server.bootstrap.profile; + +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.server.registration.Registration; + +public class ClientProfile { + + private final Registration registration; + private final LwM2mModel model; + + public ClientProfile(Registration registration, LwM2mModel model) { + this.registration = registration; + this.model = model; + } + + public Identity getIdentity() { + return registration.getIdentity(); + } + + public String getRegistrationId() { + return registration.getId(); + } + + public LwM2mModel getModel() { + return model; + } + + public String getEndpoint() { + return registration.getEndpoint(); + } + + public String getRootPath() { + return registration.getRootPath(); + } + + public boolean canInitiateConnection() { + return registration.canInitiateConnection(); + } + + public Registration getRegistration() { + return registration; + } +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/ClientProfileProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/ClientProfileProvider.java new file mode 100644 index 0000000000..7583656503 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/ClientProfileProvider.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.server.bootstrap.profile; + +import org.eclipse.leshan.core.request.Identity; + +public interface ClientProfileProvider { + + ClientProfile getProfile(Identity identity); +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/DefaultClientProfileProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/DefaultClientProfileProvider.java new file mode 100644 index 0000000000..60a7dd70f4 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/profile/DefaultClientProfileProvider.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * 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.server.bootstrap.profile; + +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.server.model.LwM2mModelProvider; +import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationStore; + +public class DefaultClientProfileProvider implements ClientProfileProvider { + + private final RegistrationStore registrationStore; + private final LwM2mModelProvider modelProvider; + + public DefaultClientProfileProvider(RegistrationStore registrationStore, LwM2mModelProvider modelProvider) { + this.registrationStore = registrationStore; + this.modelProvider = modelProvider; + } + + @Override + public ClientProfile getProfile(Identity identity) { + Registration registration = registrationStore.getRegistrationByIdentity(identity); + LwM2mModel model = modelProvider.getObjectModel(registration); + return new ClientProfile(registration, model); + } + +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/BootstrapDownlinkRequestSender.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/BootstrapDownlinkRequestSender.java new file mode 100644 index 0000000000..43632333bf --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/BootstrapDownlinkRequestSender.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * 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: + * Sierra Wireless - initial API and implementation + * Bosch Software Innovations GmbH - extension of ticket based asynchronous call. + *******************************************************************************/ +package org.eclipse.leshan.server.bootstrap.request; + +import org.eclipse.leshan.core.node.codec.CodecException; +import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; +import org.eclipse.leshan.core.request.DownlinkRequest; +import org.eclipse.leshan.core.request.exception.ClientSleepingException; +import org.eclipse.leshan.core.request.exception.InvalidResponseException; +import org.eclipse.leshan.core.request.exception.RequestCanceledException; +import org.eclipse.leshan.core.request.exception.RequestRejectedException; +import org.eclipse.leshan.core.request.exception.SendFailedException; +import org.eclipse.leshan.core.request.exception.TimeoutException; +import org.eclipse.leshan.core.request.exception.UnconnectedPeerException; +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.server.bootstrap.BootstrapSession; + +/** + * A {@link BootstrapDownlinkRequestSender} is responsible to send LWM2M {@link BootstrapDownlinkRequest} for a given + * {@link BootstrapSession}. + */ +public interface BootstrapDownlinkRequestSender { + + /** + * Send a Lightweight M2M request synchronously. Will block until a response is received from the remote server. + *

+ * The synchronous way could block a thread during a long time so it is more recommended to use the asynchronous + * way. + * + * @param destination The {@link BootstrapSession} associate to the device we want to sent the request. + * @param request The request to send to the client. + * @param timeoutInMs The global timeout to wait in milliseconds (see + * https://github.com/eclipse/leshan/wiki/Request-Timeout) + * @param The expected type of the response received. + * @return the LWM2M response. The response can be null if the timeout expires (see + * https://github.com/eclipse/leshan/wiki/Request-Timeout). + * + * @throws CodecException if request payload can not be encoded. + * @throws InterruptedException if the thread was interrupted. + * @throws RequestRejectedException if the request is rejected by foreign peer. + * @throws RequestCanceledException if the request is cancelled. + * @throws SendFailedException if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer. + * @throws InvalidResponseException if the response received is malformed. + * @throws UnconnectedPeerException if client is not connected (no dtls connection available). + * @throws ClientSleepingException if client is currently sleeping. + */ + T send(BootstrapSession destination, BootstrapDownlinkRequest request, + long timeoutInMs) throws InterruptedException; + + /** + * Send a Lightweight M2M {@link DownlinkRequest} asynchronously to a LWM2M client. + * + * {@link ResponseCallback} and {@link ErrorCallback} are exclusively called. + * + * @param destination The {@link BootstrapSession} associate to the device we want to sent the request. + * @param request The request to send to the client. + * @param timeoutInMs The global timeout to wait in milliseconds (see + * https://github.com/eclipse/leshan/wiki/Request-Timeout) + * @param responseCallback a callback called when a response is received (successful or error response). This + * callback MUST NOT be null. + * @param The expected type of the response received. + * @param errorCallback a callback called when an error or exception occurred when response is received. It can be : + *

    + *
  • {@link RequestRejectedException} if the request is rejected by foreign peer.
  • + *
  • {@link RequestCanceledException} if the request is cancelled.
  • + *
  • {@link SendFailedException} if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.
  • + *
  • {@link InvalidResponseException} if the response received is malformed.
  • + *
  • {@link UnconnectedPeerException} if client is not connected (no dtls connection available).
  • + *
  • {@link ClientSleepingException} if client is currently sleeping.
  • + *
  • {@link TimeoutException} if the timeout expires (see + * https://github.com/eclipse/leshan/wiki/Request-Timeout).
  • + *
  • or any other RuntimeException for unexpected issue. + *
+ * This callback MUST NOT be null. + * @throws CodecException if request payload can not be encoded. + */ + void send(BootstrapSession destination, BootstrapDownlinkRequest request, + long timeoutInMs, ResponseCallback responseCallback, ErrorCallback errorCallback); + + /** + * cancel all ongoing messages for a LWM2M client identified by the registration identifier. In case a client + * de-registers, the consumer can use this method to cancel all ongoing messages for the given client. + * + * @param session client registration meta data of a LWM2M client. + */ + void cancelOngoingRequests(BootstrapSession session); +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/BootstrapUplinkRequestReceiver.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/BootstrapUplinkRequestReceiver.java new file mode 100644 index 0000000000..458653004c --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/BootstrapUplinkRequestReceiver.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * 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.server.bootstrap.request; + +import java.net.URI; + +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.request.UplinkRequest; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.SendableResponse; + +public interface BootstrapUplinkRequestReceiver { + + SendableResponse requestReceived(Identity senderIdentity, UplinkRequest request, + URI serverEndpointUri); + + void onError(Identity senderIdentity, Exception exception, + Class> requestType, URI serverEndpointUri); +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/DefaultBootstrapDownlinkRequestSender.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/DefaultBootstrapDownlinkRequestSender.java new file mode 100644 index 0000000000..d18cde5c7d --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/DefaultBootstrapDownlinkRequestSender.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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: + * Sierra Wireless - initial API and implementation + * Achim Kraus (Bosch Software Innovations GmbH) - use Identity as destination + * Michał Wadowski (Orange) - Improved compliance with rfc6690 + *******************************************************************************/ +package org.eclipse.leshan.server.bootstrap.request; + +import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; +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.server.bootstrap.BootstrapSession; +import org.eclipse.leshan.server.bootstrap.endpoint.LwM2mBootstrapServerEndpoint; +import org.eclipse.leshan.server.bootstrap.endpoint.LwM2mBootstrapServerEndpointsProvider; + +/** + * The default implementation of {@link BootstrapDownlinkRequestSender}. + */ +public class DefaultBootstrapDownlinkRequestSender implements BootstrapDownlinkRequestSender { + + private final LwM2mBootstrapServerEndpointsProvider endpointsProvider; + + /** + * @param endpointsProvider which provides available {@link LwM2mBootstrapServerEndpoint} + */ + public DefaultBootstrapDownlinkRequestSender(LwM2mBootstrapServerEndpointsProvider endpointsProvider) { + this.endpointsProvider = endpointsProvider; + } + + @Override + public T send(BootstrapSession destination, BootstrapDownlinkRequest request, + long timeoutInMs) throws InterruptedException { + + // find endpoint to use + LwM2mBootstrapServerEndpoint endpoint = endpointsProvider.getEndpoint(destination.getEndpointUsed()); + + // Send requests synchronously + T response = endpoint.send(destination, request, timeoutInMs); + return response; + } + + @Override + public void send(final BootstrapSession destination, BootstrapDownlinkRequest request, + long timeoutInMs, final ResponseCallback responseCallback, ErrorCallback errorCallback) { + + // find endpoint to use + LwM2mBootstrapServerEndpoint endpoint = endpointsProvider.getEndpoint(destination.getEndpointUsed()); + + // Send requests asynchronously + endpoint.send(destination, request, new ResponseCallback() { + @Override + public void onResponse(T response) { + responseCallback.onResponse(response); + } + }, errorCallback, timeoutInMs); + } + + @Override + public void cancelOngoingRequests(BootstrapSession session) { + for (LwM2mBootstrapServerEndpoint endpoint : endpointsProvider.getEndpoints()) { + endpoint.cancelRequests(session.getId()); + } + } +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/DefaultBootstrapUplinkRequestReceiver.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/DefaultBootstrapUplinkRequestReceiver.java new file mode 100644 index 0000000000..b944c4e7f1 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/request/DefaultBootstrapUplinkRequestReceiver.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * 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.server.bootstrap.request; + +import java.net.URI; + +import org.eclipse.leshan.core.request.BootstrapRequest; +import org.eclipse.leshan.core.request.DeregisterRequest; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.request.RegisterRequest; +import org.eclipse.leshan.core.request.SendRequest; +import org.eclipse.leshan.core.request.UpdateRequest; +import org.eclipse.leshan.core.request.UplinkRequest; +import org.eclipse.leshan.core.request.UplinkRequestVisitor; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.SendableResponse; +import org.eclipse.leshan.server.bootstrap.BootstrapHandler; + +public class DefaultBootstrapUplinkRequestReceiver implements BootstrapUplinkRequestReceiver { + + private final BootstrapHandler bootstapHandler; + + public DefaultBootstrapUplinkRequestReceiver(BootstrapHandler bootstapHandler) { + this.bootstapHandler = bootstapHandler; + } + + @Override + public void onError(Identity senderIdentity, Exception exception, + Class> requestType, URI serverEndpointUri) { + } + + @Override + public SendableResponse requestReceived(Identity senderIdentity, + UplinkRequest request, URI serverEndpointUri) { + + RequestHandler requestHandler = new RequestHandler(senderIdentity, serverEndpointUri); + request.accept(requestHandler); + return requestHandler.getResponse(); + } + + public class RequestHandler implements UplinkRequestVisitor { + + private final Identity senderIdentity; + private final URI serverEndpointUri; + private SendableResponse response; + + public RequestHandler(Identity senderIdentity, URI serverEndpointUri) { + this.senderIdentity = senderIdentity; + this.serverEndpointUri = serverEndpointUri; + } + + @Override + public void visit(RegisterRequest request) { + } + + @Override + public void visit(UpdateRequest request) { + + } + + @Override + public void visit(DeregisterRequest request) { + } + + @Override + public void visit(BootstrapRequest request) { + response = bootstapHandler.bootstrap(senderIdentity, request, serverEndpointUri); + } + + @Override + public void visit(SendRequest request) { + } + + @SuppressWarnings("unchecked") + public SendableResponse getResponse() { + return (SendableResponse) response; + } + } + +} diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java index 87f5bec4a3..8bf97eb504 100644 --- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java +++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java @@ -21,17 +21,19 @@ import static org.junit.Assert.assertTrue; import java.net.InetSocketAddress; +import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.endpoint.EndpointUriUtil; import org.eclipse.leshan.core.request.BootstrapDeleteRequest; +import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; import org.eclipse.leshan.core.request.BootstrapFinishRequest; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.BootstrapWriteRequest; -import org.eclipse.leshan.core.request.DownlinkRequest; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.request.exception.RequestCanceledException; import org.eclipse.leshan.core.response.BootstrapDeleteResponse; @@ -42,11 +44,17 @@ import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.core.response.ResponseCallback; import org.eclipse.leshan.core.response.SendableResponse; -import org.eclipse.leshan.server.bootstrap.BootstrapHandlerTest.MockRequestSender.Mode; +import org.eclipse.leshan.server.bootstrap.request.BootstrapDownlinkRequestSender; import org.junit.Test; public class BootstrapHandlerTest { + public enum Mode { + ALWAYS_SUCCESS, ALWAYS_FAILURE, NO_RESPONSE + }; + + private final URI endpointUsed = EndpointUriUtil.createUri("coap://localhost:5683"); + @Test public void error_if_not_authorized() { // prepare bootstrapHandler with a session manager which does not authorized any session @@ -56,9 +64,8 @@ public void error_if_not_authorized() { bsSessionManager, new BootstrapSessionDispatcher()); // Try to bootstrap - BootstrapResponse response = bsHandler - .bootstrap(Identity.psk(new InetSocketAddress(4242), "pskdentity"), new BootstrapRequest("endpoint")) - .getResponse(); + BootstrapResponse response = bsHandler.bootstrap(Identity.psk(new InetSocketAddress(4242), "pskdentity"), + new BootstrapRequest("endpoint"), endpointUsed).getResponse(); // Ensure bootstrap session is refused assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); @@ -69,7 +76,7 @@ public void bootstrap_success() throws InvalidConfigurationException { // prepare a bootstrap handler with a session manager which authorize all session // and a sender which "obtains" always successful response. // and a config store with and an empty config for the expected endpoint - LwM2mBootstrapRequestSender requestSender = new MockRequestSender(Mode.ALWAYS_SUCCESS); + BootstrapDownlinkRequestSender requestSender = new MockRequestSender(Mode.ALWAYS_SUCCESS); EditableBootstrapConfigStore bsStore = new InMemoryBootstrapConfigStore(); bsStore.add("endpoint", new BootstrapConfig()); MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true, bsStore); @@ -78,8 +85,9 @@ public void bootstrap_success() throws InvalidConfigurationException { new BootstrapSessionDispatcher()); // Try to bootstrap - SendableResponse sendableResponse = bsHandler - .bootstrap(Identity.psk(new InetSocketAddress(4242), "pskdentity"), new BootstrapRequest("endpoint")); + SendableResponse sendableResponse = bsHandler.bootstrap( + Identity.psk(new InetSocketAddress(4242), "pskdentity"), new BootstrapRequest("endpoint"), + endpointUsed); sendableResponse.sent(); // Ensure bootstrap finished @@ -92,7 +100,7 @@ public void bootstrap_failed_because_of_sent_failure() throws InvalidConfigurati // prepare a bootstrap handler with a session manager which authorize all session // and a sender which always failed to send request. // and a config store with and an empty config for the expected endpoint - LwM2mBootstrapRequestSender requestSender = new MockRequestSender(Mode.ALWAYS_FAILURE); + BootstrapDownlinkRequestSender requestSender = new MockRequestSender(Mode.ALWAYS_FAILURE); EditableBootstrapConfigStore bsStore = new InMemoryBootstrapConfigStore(); bsStore.add("endpoint", new BootstrapConfig()); MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true, bsStore); @@ -100,8 +108,9 @@ public void bootstrap_failed_because_of_sent_failure() throws InvalidConfigurati new BootstrapSessionDispatcher()); // Try to bootstrap - SendableResponse sendableResponse = bsHandler - .bootstrap(Identity.psk(new InetSocketAddress(4242), "pskdentity"), new BootstrapRequest("endpoint")); + SendableResponse sendableResponse = bsHandler.bootstrap( + Identity.psk(new InetSocketAddress(4242), "pskdentity"), new BootstrapRequest("endpoint"), + endpointUsed); sendableResponse.sent(); // Ensure bootstrap failed @@ -124,8 +133,9 @@ public void two_bootstrap_at_the_same_time_not_allowed() new BootstrapSessionDispatcher(), DefaultBootstrapHandler.DEFAULT_TIMEOUT); // First bootstrap : which will not end (because of sender) - SendableResponse first_response = bsHandler - .bootstrap(Identity.psk(new InetSocketAddress(4242), "pskdentity"), new BootstrapRequest("endpoint")); + SendableResponse first_response = bsHandler.bootstrap( + Identity.psk(new InetSocketAddress(4242), "pskdentity"), new BootstrapRequest("endpoint"), + endpointUsed); first_response.sent(); // Ensure bootstrap is accepted and not finished BootstrapSession firstSession = bsSessionManager.lastSession; @@ -137,8 +147,9 @@ public void two_bootstrap_at_the_same_time_not_allowed() // Second bootstrap : for the same endpoint it must be accepted and previous one should be cancelled bsSessionManager.reset(); requestSender.setMode(Mode.ALWAYS_SUCCESS); - SendableResponse second_response = bsHandler - .bootstrap(Identity.psk(new InetSocketAddress(4243), "pskdentity"), new BootstrapRequest("endpoint")); + SendableResponse second_response = bsHandler.bootstrap( + Identity.psk(new InetSocketAddress(4243), "pskdentity"), new BootstrapRequest("endpoint"), + endpointUsed); second_response.sent(); // ensure last session is accepted assertTrue(second_response.getResponse().isSuccess()); @@ -149,11 +160,7 @@ public void two_bootstrap_at_the_same_time_not_allowed() assertTrue(bsSessionManager.failedWasCalled(firstSession, BootstrapFailureCause.CANCELLED)); } - static class MockRequestSender implements LwM2mBootstrapRequestSender { - - public enum Mode { - ALWAYS_SUCCESS, ALWAYS_FAILURE, NO_RESPONSE - }; + public static class MockRequestSender implements BootstrapDownlinkRequestSender { private Mode mode; private ErrorCallback errorCallback; @@ -163,8 +170,8 @@ public MockRequestSender(Mode mode) { } @Override - public T send(BootstrapSession session, DownlinkRequest request, long timeout) - throws InterruptedException { + public T send(BootstrapSession session, BootstrapDownlinkRequest request, + long timeout) throws InterruptedException { // Not Implemented return null; } @@ -175,8 +182,8 @@ public void setMode(Mode mode) { @SuppressWarnings("unchecked") @Override - public void send(BootstrapSession session, DownlinkRequest request, long timeout, - ResponseCallback responseCallback, ErrorCallback errorCallback) { + public void send(BootstrapSession session, BootstrapDownlinkRequest request, + long timeout, ResponseCallback responseCallback, ErrorCallback errorCallback) { // no response, no callback call if (mode == Mode.NO_RESPONSE) { this.errorCallback = errorCallback; @@ -219,11 +226,11 @@ public void cancelOngoingRequests(BootstrapSession destination) { private static class MockBootstrapSessionManager extends DefaultBootstrapSessionManager { - private boolean authorized; + private final boolean authorized; private BootstrapSession lastSession; private BootstrapFailureCause lastFailureCause; - private List endedSession = new ArrayList(); - private Map failureCauses = new HashMap<>(); + private final List endedSession = new ArrayList(); + private final Map failureCauses = new HashMap<>(); public MockBootstrapSessionManager(boolean authorized, BootstrapConfigStore store) { super(null, store); @@ -231,8 +238,8 @@ public MockBootstrapSessionManager(boolean authorized, BootstrapConfigStore stor } @Override - public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { - lastSession = new DefaultBootstrapSession(request, clientIdentity, authorized, null); + public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity, URI endpointUsed) { + lastSession = new DefaultBootstrapSession(request, clientIdentity, authorized, null, endpointUsed); return lastSession; } diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/IdentitySerDes.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/IdentitySerDes.java index fe5600a8af..2f3cfc8e4e 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/IdentitySerDes.java +++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/IdentitySerDes.java @@ -23,8 +23,6 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; -import javax.xml.ws.EndpointContext; - import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.util.Hex; @@ -32,9 +30,6 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; -/** - * Functions for serializing and deserializing a Californium {@link EndpointContext} in JSON. - */ public class IdentitySerDes { private static final String KEY_ADDRESS = "address";