From 74f1357017abba84893311675a3b81100b9b2cd7 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Fri, 23 Sep 2022 18:43:47 +0200 Subject: [PATCH] Add Transport Layer abstraction at Server side. --- .../californium/LwM2mClientCoapResource.java | 2 +- .../californium/AsyncRequestObserver.java | 10 +- .../californium/CoapAsyncRequestObserver.java | 21 +- .../californium/CoapSyncRequestObserver.java | 28 +- .../DefaultExceptionTranslator.java | 34 + .../core/californium/EndpointContextUtil.java | 2 + .../core/californium/ExceptionTranslator.java | 16 +- .../core/californium/LwM2mCoapResource.java | 32 +- .../Lwm2mEndpointContextMatcher.java | 2 + .../leshan/core/californium/ObserveUtil.java | 53 +- .../core/californium/SyncRequestObserver.java | 6 +- .../TemporaryExceptionTranslator.java | 37 + .../identity/DefaultCoapIdentityHandler.java | 43 ++ .../californium/identity/IdentityHandler.java | 27 + .../identity/IdentityHandlerProvider.java | 33 + .../leshan/core/endpoint/EndpointUriUtil.java | 51 ++ .../leshan/core/endpoint/Protocol.java | 70 ++ .../observation/CompositeObservation.java | 10 +- .../leshan/core/observation/Observation.java | 28 +- .../observation/ObservationIdentifier.java | 75 ++ .../core/observation/SingleObservation.java | 9 +- .../CancelCompositeObservationRequest.java | 4 +- .../request/CancelObservationRequest.java | 4 +- .../integration/tests/BootstrapTest.java | 4 +- .../leshan/integration/tests/DeleteTest.java | 16 +- .../integration/tests/RegistrationTest.java | 8 +- .../integration/tests/SecurityTest.java | 18 +- .../tests/lockstep/LockStepLwM2mClient.java | 8 +- .../tests/lockstep/LockStepTest.java | 31 +- .../tests/observe/ObserveTest.java | 4 +- .../tests/observe/ObserveTimeStampTest.java | 11 +- .../tests/observe/TestObserveUtil.java | 6 +- .../tests/send/LockStepSendTest.java | 17 +- .../redis/RedisRegistrationStoreTest.java | 51 +- .../util/BootstrapIntegrationTestHelper.java | 24 +- .../tests/util/IntegrationTestHelper.java | 49 +- .../util/QueueModeIntegrationTestHelper.java | 8 +- .../util/RedisIntegrationTestHelper.java | 24 +- .../util/SecureIntegrationTestHelper.java | 60 +- .../californium/LeshanServerBuilder.java | 651 ------------------ .../bootstrap/BootstrapResource.java | 2 +- .../endpoint/CaliforniumServerEndpoint.java | 276 ++++++++ .../CaliforniumServerEndpointFactory.java | 41 ++ .../CaliforniumServerEndpointsProvider.java | 303 ++++++++ .../endpoint/ServerCoapMessageTranslator.java | 143 ++++ .../endpoint/ServerProtocolProvider.java | 36 + .../coap/CoapOscoreServerEndpointFactory.java | 112 +++ .../coap/CoapServerEndpointFactory.java | 124 ++++ .../coap/CoapServerProtocolProvider.java | 61 ++ .../coaps/CoapsServerEndpointFactory.java | 346 ++++++++++ .../coaps/CoapsServerProtocolProvider.java | 69 ++ .../observation}/EndpointContextSerDes.java | 2 +- .../observation/LwM2mObservationStore.java | 136 ++++ .../observation/ObservationSerDes.java | 102 +++ .../observation/ObservationServiceImpl.java | 400 ----------- .../registration/RegisterResource.java | 32 +- .../CaliforniumLwM2mRequestSender.java | 253 ------- .../CaliforniumQueueModeRequestSender.java | 139 ---- .../request/CoapRequestBuilder.java | 22 +- .../californium/request/RequestSender.java | 12 +- .../server/californium/send/SendResource.java | 53 +- .../californium/CaliforniumTestSupport.java | 53 -- .../californium/LeshanServerBuilderTest.java | 61 +- .../server/californium/LeshanServerTest.java | 22 +- .../LeshanBootstrapServerBuilderTest.java | 3 +- .../LwM2mObservationStoreTest.java} | 108 ++- .../observation/ObservationServiceTest.java | 258 +++---- .../observation/ObserveUtilTest.java | 5 +- .../request/CoapRequestBuilderTest.java | 30 +- leshan-server-core/pom.xml | 5 - .../eclipse/leshan/server}/LeshanServer.java | 443 +++--------- .../leshan/server/LeshanServerBuilder.java | 324 +++++++++ .../server/endpoint/LwM2mServerEndpoint.java | 45 ++ .../LwM2mServerEndpointsProvider.java | 41 ++ .../endpoint/ServerEndpointToolbox.java | 53 ++ .../LwM2mNotificationReceiver.java | 75 ++ .../observation/ObservationServiceImpl.java | 257 +++++++ .../leshan/server/profile/ClientProfile.java | 59 ++ .../server/profile/ClientProfileProvider.java | 23 + .../profile/DefaultClientProfileProvider.java | 41 ++ .../queue/QueueModeLwM2mRequestSender.java | 10 +- .../InMemoryRegistrationStore.java | 233 ++----- .../server/registration/Registration.java | 16 + .../registration/RegistrationHandler.java | 8 +- .../registration/RegistrationStore.java | 8 +- .../registration/RegistrationUpdate.java | 4 +- .../request/DefaultDownlinkRequestSender.java | 151 ++++ .../request/DefaultUplinkRequestReceiver.java | 106 +++ ...Sender.java => DownlinkRequestSender.java} | 5 +- .../server/request/UplinkRequestReceiver.java | 33 + .../server/security/ServerSecurityInfo.java | 53 ++ .../InMemoryRegistrationStoreTest.java | 102 +++ .../registration/RegistrationHandlerTest.java | 19 +- .../leshan/server/demo/LeshanServerDemo.java | 176 +++-- .../server/demo/servlet/ClientServlet.java | 2 +- .../server/demo/servlet/EventServlet.java | 16 +- .../server/demo/servlet/ServerServlet.java | 23 +- leshan-server-redis/pom.xml | 2 +- .../server/redis/RedisRegistrationStore.java | 241 ++----- .../redis/serialization/IdentitySerDes.java | 3 +- .../serialization/ObservationSerDes.java | 165 +++-- .../serialization/RegistrationSerDes.java | 14 +- .../EndpointContextSerDesTest.java | 53 -- .../serialization/RegistrationSerDesTest.java | 11 +- 104 files changed, 4678 insertions(+), 2933 deletions(-) create mode 100644 leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/DefaultExceptionTranslator.java rename leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/CaliforniumRegistrationStore.java => leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ExceptionTranslator.java (62%) create mode 100644 leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/TemporaryExceptionTranslator.java create mode 100644 leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/DefaultCoapIdentityHandler.java create mode 100644 leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandler.java create mode 100644 leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandlerProvider.java create mode 100644 leshan-core/src/main/java/org/eclipse/leshan/core/endpoint/EndpointUriUtil.java create mode 100644 leshan-core/src/main/java/org/eclipse/leshan/core/endpoint/Protocol.java create mode 100644 leshan-core/src/main/java/org/eclipse/leshan/core/observation/ObservationIdentifier.java delete mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpoint.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpointFactory.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpointsProvider.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/ServerCoapMessageTranslator.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/ServerProtocolProvider.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coap/CoapOscoreServerEndpointFactory.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coap/CoapServerEndpointFactory.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coap/CoapServerProtocolProvider.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coaps/CoapsServerEndpointFactory.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coaps/CoapsServerProtocolProvider.java rename {leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization => leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation}/EndpointContextSerDes.java (99%) create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/LwM2mObservationStore.java create mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/ObservationSerDes.java delete mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/ObservationServiceImpl.java delete mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumLwM2mRequestSender.java delete mode 100644 leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumQueueModeRequestSender.java delete mode 100644 leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/CaliforniumTestSupport.java rename leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/{registration/InMemoryRegistrationStoreTest.java => observation/LwM2mObservationStoreTest.java} (69%) rename {leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium => leshan-server-core/src/main/java/org/eclipse/leshan/server}/LeshanServer.java (55%) create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/LeshanServerBuilder.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/LwM2mServerEndpoint.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/LwM2mServerEndpointsProvider.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/ServerEndpointToolbox.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/LwM2mNotificationReceiver.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/profile/ClientProfile.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/profile/ClientProfileProvider.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/profile/DefaultClientProfileProvider.java rename {leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium => leshan-server-core/src/main/java/org/eclipse/leshan/server}/registration/InMemoryRegistrationStore.java (64%) create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DefaultDownlinkRequestSender.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DefaultUplinkRequestReceiver.java rename leshan-server-core/src/main/java/org/eclipse/leshan/server/request/{LwM2mRequestSender.java => DownlinkRequestSender.java} (97%) create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/request/UplinkRequestReceiver.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/security/ServerSecurityInfo.java create mode 100644 leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/InMemoryRegistrationStoreTest.java rename {leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium => leshan-server-core/src/test/java/org/eclipse/leshan/server}/registration/RegistrationHandlerTest.java (91%) delete mode 100644 leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/EndpointContextSerDesTest.java diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java index 261bfd7714..45c94ef57c 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java @@ -34,7 +34,7 @@ public class LwM2mClientCoapResource extends LwM2mCoapResource { public LwM2mClientCoapResource(String name, RegistrationEngine registrationEngine, CaliforniumEndpointsManager endpointsManager) { - super(name); + super(name, null); this.registrationEngine = registrationEngine; this.endpointsManager = endpointsManager; } diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/AsyncRequestObserver.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/AsyncRequestObserver.java index 0bbbd3cb58..0ec9b853ac 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/AsyncRequestObserver.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/AsyncRequestObserver.java @@ -33,6 +33,11 @@ */ public abstract class AsyncRequestObserver extends CoapAsyncRequestObserver { + public AsyncRequestObserver(Request coapRequest, final ResponseCallback responseCallback, + final ErrorCallback errorCallback, long timeoutInMs, ScheduledExecutorService executor) { + this(coapRequest, responseCallback, errorCallback, timeoutInMs, executor, new TemporaryExceptionTranslator()); + } + /** * A Californium message observer for a CoAP request helping to get results asynchronously dedicated for LWM2M * requests. @@ -49,8 +54,9 @@ public abstract class AsyncRequestObserver extends Coap * @param executor used to scheduled timeout tasks. */ public AsyncRequestObserver(Request coapRequest, final ResponseCallback responseCallback, - final ErrorCallback errorCallback, long timeoutInMs, ScheduledExecutorService executor) { - super(coapRequest, null, errorCallback, timeoutInMs, executor); + final ErrorCallback errorCallback, long timeoutInMs, ScheduledExecutorService executor, + ExceptionTranslator exceptionTranslator) { + super(coapRequest, null, errorCallback, timeoutInMs, executor, exceptionTranslator); this.responseCallback = new CoapResponseCallback() { @Override diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapAsyncRequestObserver.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapAsyncRequestObserver.java index b84060d796..69ba9aa365 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapAsyncRequestObserver.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapAsyncRequestObserver.java @@ -25,14 +25,10 @@ import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; -import org.eclipse.californium.elements.exception.EndpointUnconnectedException; -import org.eclipse.californium.scandium.dtls.DtlsHandshakeTimeoutException; 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.TimeoutException.Type; -import org.eclipse.leshan.core.request.exception.UnconnectedPeerException; import org.eclipse.leshan.core.response.ErrorCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +52,7 @@ public class CoapAsyncRequestObserver extends AbstractRequestObserver { private ScheduledFuture cleaningTask; private boolean cancelled = false; private final ScheduledExecutorService executor; + private final ExceptionTranslator exceptionTranslator; // The Californium API does not ensure that message callback are exclusive // meaning that you can get a onReponse call and a onCancel one. @@ -81,12 +78,14 @@ public class CoapAsyncRequestObserver extends AbstractRequestObserver { * @param executor used to scheduled timeout tasks. */ public CoapAsyncRequestObserver(Request coapRequest, CoapResponseCallback responseCallback, - ErrorCallback errorCallback, long timeoutInMs, ScheduledExecutorService executor) { + ErrorCallback errorCallback, long timeoutInMs, ScheduledExecutorService executor, + ExceptionTranslator exceptionTranslator) { super(coapRequest); this.responseCallback = responseCallback; this.errorCallback = errorCallback; this.timeoutInMs = timeoutInMs; this.executor = executor; + this.exceptionTranslator = exceptionTranslator; } @Override @@ -155,17 +154,7 @@ public void onReject() { public void onSendError(Throwable error) { if (eventRaised.compareAndSet(false, true)) { cancelCleaningTask(); - if (error instanceof DtlsHandshakeTimeoutException) { - errorCallback.onError(new TimeoutException(Type.DTLS_HANDSHAKE_TIMEOUT, error, - "Request %s timeout : dtls handshake timeout", coapRequest.getURI())); - } else if (error instanceof EndpointUnconnectedException) { - errorCallback.onError(new UnconnectedPeerException(error, - "Unable to send request %s : peer is not connected (no DTLS connection)", - coapRequest.getURI())); - } else { - errorCallback - .onError(new SendFailedException(error, "Unable to send request %s", coapRequest.getURI())); - } + errorCallback.onError(exceptionTranslator.translate(coapRequest, error)); } else { LOG.debug("onSendError callback ignored because an event was already raised for this request {}", coapRequest); diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapSyncRequestObserver.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapSyncRequestObserver.java index a3634c34e2..669da1a2c5 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapSyncRequestObserver.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapSyncRequestObserver.java @@ -24,12 +24,10 @@ import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; -import org.eclipse.californium.elements.exception.EndpointUnconnectedException; -import org.eclipse.californium.scandium.dtls.DtlsHandshakeTimeoutException; 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.UnconnectedPeerException; +import org.eclipse.leshan.core.request.exception.TimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,20 +44,22 @@ public class CoapSyncRequestObserver extends AbstractRequestObserver { private static final Logger LOG = LoggerFactory.getLogger(CoapSyncRequestObserver.class); - private CountDownLatch latch = new CountDownLatch(1); - private AtomicReference ref = new AtomicReference<>(null); - private AtomicBoolean coapTimeout = new AtomicBoolean(false); - private AtomicReference exception = new AtomicReference<>(); - private long timeout; + private final CountDownLatch latch = new CountDownLatch(1); + private final AtomicReference ref = new AtomicReference<>(null); + private final AtomicBoolean coapTimeout = new AtomicBoolean(false); + private final AtomicReference exception = new AtomicReference<>(); + private final long timeout; + private final ExceptionTranslator exceptionTranslator; /** * @param coapRequest The CoAP request to observe. * @param timeoutInMs A response timeout(in millisecond) which is raised if neither a response or error happens (see * https://github.com/eclipse/leshan/wiki/Request-Timeout). */ - public CoapSyncRequestObserver(Request coapRequest, long timeoutInMs) { + public CoapSyncRequestObserver(Request coapRequest, long timeoutInMs, ExceptionTranslator exceptionTranslator) { super(coapRequest); this.timeout = timeoutInMs; + this.exceptionTranslator = exceptionTranslator; } @Override @@ -97,13 +97,13 @@ public void onReject() { @Override public void onSendError(Throwable error) { - if (error instanceof DtlsHandshakeTimeoutException) { + Exception e = exceptionTranslator.translate(coapRequest, error); + if (e instanceof TimeoutException) { coapTimeout.set(true); - } else if (error instanceof EndpointUnconnectedException) { - exception.set(new UnconnectedPeerException(error, - "Unable to send request %s : peer is not connected (no DTLS connection)", coapRequest.getURI())); + } else if (e instanceof RuntimeException) { + exception.set((RuntimeException) e); } else { - exception.set(new SendFailedException(error, "Request %s cannot be sent", coapRequest, error.getMessage())); + exception.set(new SendFailedException(e, "Request %s cannot be sent", coapRequest, e.getMessage())); } latch.countDown(); } diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/DefaultExceptionTranslator.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/DefaultExceptionTranslator.java new file mode 100644 index 0000000000..a805f8f8ea --- /dev/null +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/DefaultExceptionTranslator.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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.core.californium; + +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.elements.exception.EndpointUnconnectedException; +import org.eclipse.leshan.core.request.exception.SendFailedException; +import org.eclipse.leshan.core.request.exception.UnconnectedPeerException; + +public class DefaultExceptionTranslator implements ExceptionTranslator { + + @Override + public Exception translate(Request coapRequest, Throwable error) { + if (error instanceof EndpointUnconnectedException) { + return new UnconnectedPeerException(error, "Unable to send request %s : peer is not connected", + coapRequest.getURI()); + } else { + return new SendFailedException(error, "Unable to send request %s", coapRequest.getURI()); + } + } +} diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointContextUtil.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointContextUtil.java index 6fe85c098c..599f97a8c9 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointContextUtil.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointContextUtil.java @@ -39,6 +39,8 @@ import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.util.Hex; +//TODO TL : to be delete when no more class use it (at the end of the refactoring) + /** * Utility class used to handle Californium {@link EndpointContext} in Leshan. *

diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/CaliforniumRegistrationStore.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ExceptionTranslator.java similarity index 62% rename from leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/CaliforniumRegistrationStore.java rename to leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ExceptionTranslator.java index 1a9b41159d..fe416f7d35 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/CaliforniumRegistrationStore.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ExceptionTranslator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016 Sierra Wireless and others. + * 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 @@ -13,14 +13,14 @@ * Contributors: * Sierra Wireless - initial API and implementation *******************************************************************************/ -package org.eclipse.leshan.server.californium.registration; +package org.eclipse.leshan.core.californium; -import org.eclipse.californium.core.observe.ObservationStore; -import org.eclipse.leshan.server.registration.RegistrationStore; +import org.eclipse.californium.core.coap.Request; -/** - * A registration store which is able to store Californium observation. - */ -public interface CaliforniumRegistrationStore extends RegistrationStore, ObservationStore { +public interface ExceptionTranslator { + /** + * Translate exception from underlying transport layer to LwM2m Exception. + */ + Exception translate(Request coapRequest, Throwable error); } diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/LwM2mCoapResource.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/LwM2mCoapResource.java index 685eb5e941..0429929c43 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/LwM2mCoapResource.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/LwM2mCoapResource.java @@ -18,11 +18,14 @@ import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.core.coap.Message; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.Exchange; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.slf4j.Logger; @@ -37,12 +40,15 @@ public class LwM2mCoapResource extends CoapResource { private static final Logger LOG = LoggerFactory.getLogger(LwM2mCoapResource.class); + private final IdentityHandlerProvider identityHandlerProvider; + /** * @param name the resource name * @see CoapResource#CoapResource(String) */ - public LwM2mCoapResource(String name) { + public LwM2mCoapResource(String name, IdentityHandlerProvider identityHandlerProvider) { super(name); + this.identityHandlerProvider = identityHandlerProvider; } @Override @@ -54,7 +60,7 @@ public void handleRequest(Exchange exchange) { } catch (RuntimeException e) { Request request = exchange.getRequest(); LOG.error("Exception while handling request [{}] on the resource {} from {}", request, getURI(), - extractIdentitySafely(request.getSourceContext()), e); + extractIdentitySafely(exchange, request), e); exchange.sendResponse(new Response(ResponseCode.INTERNAL_SERVER_ERROR)); } } @@ -83,10 +89,10 @@ protected void handleInvalidRequest(Exchange exchange, String message, Throwable if (LOG.isDebugEnabled()) { if (error != null) { LOG.debug("Invalid request [{}] received on the resource {} from {}", request, getURI(), - extractIdentitySafely(request.getSourceContext()), error); + extractIdentitySafely(exchange, request), error); } else { LOG.debug("Invalid request [{}] received on the resource {} from {} : {}", request, getURI(), - extractIdentitySafely(request.getSourceContext()), message); + extractIdentitySafely(exchange, request), message); } } @@ -107,18 +113,30 @@ protected void handleInvalidRequest(Exchange exchange, String message, Throwable * @throws IllegalStateException if we are not able to extract {@link Identity}. */ protected Identity extractIdentity(EndpointContext context) { + // TODO TL : to delete once server / client / bootstrap server will use new design return EndpointContextUtil.extractIdentity(context); } + protected Identity getForeignPeerIdentity(Exchange exchange, Message receivedMessage) { + IdentityHandler identityHandler = identityHandlerProvider.getIdentityHandler(exchange.getEndpoint()); + if (identityHandler != null) { + return identityHandler.getIdentity(receivedMessage); + } + return null; + } + /** * Create Leshan {@link Identity} from Californium {@link EndpointContext}. * - * @param context The Californium {@link EndpointContext} to convert. * @return The corresponding Leshan {@link Identity} or null if we didn't succeed to extract Identity. */ - protected Identity extractIdentitySafely(EndpointContext context) { + protected Identity extractIdentitySafely(Exchange exchange, Message receivedMessage) { try { - return extractIdentity(context); + if (identityHandlerProvider == null) { + return extractIdentity(receivedMessage.getSourceContext()); + } else { + return getForeignPeerIdentity(exchange, receivedMessage); + } } catch (RuntimeException e) { LOG.error("Unable to extract identity", e); return null; diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/Lwm2mEndpointContextMatcher.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/Lwm2mEndpointContextMatcher.java index 365bae041e..962138dcef 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/Lwm2mEndpointContextMatcher.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/Lwm2mEndpointContextMatcher.java @@ -22,6 +22,8 @@ import org.eclipse.californium.elements.PrincipalEndpointContextMatcher; import org.eclipse.leshan.core.request.Identity; +// TODO TL: to be move in californium.identity package + /** * LWM2M principal based endpoint context matcher. * diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ObserveUtil.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ObserveUtil.java index 8710452884..5a589eadb8 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ObserveUtil.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ObserveUtil.java @@ -26,8 +26,10 @@ import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.observe.Observation; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.observation.CompositeObservation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ObserveCompositeRequest; @@ -42,37 +44,63 @@ public class ObserveUtil { public static final String CTX_ENDPOINT = "leshan-endpoint"; public static final String CTX_REGID = "leshan-regId"; public static final String CTX_LWM2M_PATH = "leshan-path"; + public static final String CTX_CF_OBERSATION = "leshan-cf-obs"; + + public static org.eclipse.leshan.core.observation.Observation createLwM2mObservation(Observation observation, + String serializedObservation) { + if (observation == null) + return null; + + if (observation.getRequest().getCode() == CoAP.Code.GET) { + return ObserveUtil.createLwM2mObservation(observation.getRequest(), serializedObservation); + } else if (observation.getRequest().getCode() == CoAP.Code.FETCH) { + return ObserveUtil.createLwM2mCompositeObservation(observation.getRequest(), serializedObservation); + } else { + throw new IllegalStateException("Observation request can be GET or FETCH only"); + } + } + + public static SingleObservation createLwM2mObservation(Request request) { + return createLwM2mObservation(request, null); + } /** * Create a LWM2M observation from a CoAP request. */ - public static SingleObservation createLwM2mObservation(Request request) { - ObserveCommon observeCommon = new ObserveCommon(request); + public static SingleObservation createLwM2mObservation(Request request, String serializedObservation) { + ObserveCommon observeCommon = new ObserveCommon(request, serializedObservation); if (observeCommon.lwm2mPaths.size() != 1) { throw new IllegalStateException( "1 path is expected in observe request context but was " + observeCommon.lwm2mPaths); } - return new SingleObservation(request.getToken().getBytes(), observeCommon.regId, - observeCommon.lwm2mPaths.get(0), observeCommon.responseContentFormat, observeCommon.context); + return new SingleObservation(new ObservationIdentifier(request.getToken().getBytes()), observeCommon.regId, + observeCommon.lwm2mPaths.get(0), observeCommon.responseContentFormat, observeCommon.context, + observeCommon.protocolData); } public static CompositeObservation createLwM2mCompositeObservation(Request request) { - ObserveCommon observeCommon = new ObserveCommon(request); + return createLwM2mCompositeObservation(request, null); + } + + public static CompositeObservation createLwM2mCompositeObservation(Request request, String serializedObservation) { + ObserveCommon observeCommon = new ObserveCommon(request, serializedObservation); - return new CompositeObservation(request.getToken().getBytes(), observeCommon.regId, observeCommon.lwm2mPaths, - observeCommon.requestContentFormat, observeCommon.responseContentFormat, observeCommon.context); + return new CompositeObservation(new ObservationIdentifier(request.getToken().getBytes()), observeCommon.regId, + observeCommon.lwm2mPaths, observeCommon.requestContentFormat, observeCommon.responseContentFormat, + observeCommon.context, observeCommon.protocolData); } private static class ObserveCommon { String regId; Map context; + Map protocolData; List lwm2mPaths; ContentFormat requestContentFormat; ContentFormat responseContentFormat; - public ObserveCommon(Request request) { + public ObserveCommon(Request request, String serializedObservation) { if (request.getUserContext() == null) { throw new IllegalStateException("missing request context"); } @@ -105,6 +133,11 @@ public ObserveCommon(Request request) { if (request.getOptions().hasAccept()) { responseContentFormat = ContentFormat.fromCode(request.getOptions().getAccept()); } + + if (serializedObservation != null) { + protocolData = new HashMap<>(); + protocolData.put(CTX_CF_OBERSATION, serializedObservation); + } } } @@ -204,6 +237,10 @@ public static String extractEndpoint(org.eclipse.californium.core.observe.Observ return observation.getRequest().getUserContext().get(CTX_ENDPOINT); } + public static String extractSerializedObservation(org.eclipse.leshan.core.observation.Observation observation) { + return observation.getProtocolData().get(CTX_CF_OBERSATION); + } + /** * Validate the Californium observation. It is valid if it contains all necessary context for Leshan. */ diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/SyncRequestObserver.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/SyncRequestObserver.java index 5a40efbe6d..ee572efaff 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/SyncRequestObserver.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/SyncRequestObserver.java @@ -34,7 +34,11 @@ public abstract class SyncRequestObserver extends CoapSyncRequestObserver { public SyncRequestObserver(Request coapRequest, long timeout) { - super(coapRequest, timeout); + this(coapRequest, timeout, new TemporaryExceptionTranslator()); + } + + public SyncRequestObserver(Request coapRequest, long timeout, ExceptionTranslator exceptionTranslator) { + super(coapRequest, timeout, exceptionTranslator); } /** diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/TemporaryExceptionTranslator.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/TemporaryExceptionTranslator.java new file mode 100644 index 0000000000..787099afed --- /dev/null +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/TemporaryExceptionTranslator.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.core.californium; + +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.scandium.dtls.DtlsHandshakeTimeoutException; +import org.eclipse.leshan.core.request.exception.TimeoutException; +import org.eclipse.leshan.core.request.exception.TimeoutException.Type; + +// TODO TL : this is just a class for backward compatibility waiting TL refactoring was done. +// It should be deleted at the end. +public class TemporaryExceptionTranslator extends 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-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/DefaultCoapIdentityHandler.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/DefaultCoapIdentityHandler.java new file mode 100644 index 0000000000..9b6f3de717 --- /dev/null +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/DefaultCoapIdentityHandler.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.core.californium.identity; + +import java.net.InetSocketAddress; +import java.security.Principal; + +import org.eclipse.californium.core.coap.Message; +import org.eclipse.californium.elements.AddressEndpointContext; +import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.leshan.core.request.Identity; + +public class DefaultCoapIdentityHandler implements IdentityHandler { + + @Override + public Identity getIdentity(Message receivedMessage) { + EndpointContext context = receivedMessage.getSourceContext(); + InetSocketAddress peerAddress = context.getPeerAddress(); + Principal senderIdentity = context.getPeerIdentity(); + if (senderIdentity == null) { + return Identity.unsecure(peerAddress); + } + return null; + } + + @Override + public EndpointContext createEndpointContext(Identity identity, boolean allowConnectionInitiation) { + return new AddressEndpointContext(identity.getPeerAddress()); + } +} diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandler.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandler.java new file mode 100644 index 0000000000..6a3794a7be --- /dev/null +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandler.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * 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.core.californium.identity; + +import org.eclipse.californium.core.coap.Message; +import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.leshan.core.request.Identity; + +public interface IdentityHandler { + + Identity getIdentity(Message receivedMessage); + + EndpointContext createEndpointContext(Identity identity, boolean allowConnectionInitiation); +} diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandlerProvider.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandlerProvider.java new file mode 100644 index 0000000000..354259d119 --- /dev/null +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/identity/IdentityHandlerProvider.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.core.californium.identity; + +import java.util.HashMap; + +import org.eclipse.californium.core.network.Endpoint; + +public class IdentityHandlerProvider { + + private final HashMap identityHandlers = new HashMap<>(); + + public void addIdentityHandler(Endpoint endpoint, IdentityHandler identityHandler) { + identityHandlers.put(endpoint, identityHandler); + } + + public IdentityHandler getIdentityHandler(Endpoint endpoint) { + return identityHandlers.get(endpoint); + } +} diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/endpoint/EndpointUriUtil.java b/leshan-core/src/main/java/org/eclipse/leshan/core/endpoint/EndpointUriUtil.java new file mode 100644 index 0000000000..88c2d5aeab --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/endpoint/EndpointUriUtil.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.core.endpoint; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; + +public class EndpointUriUtil { + + public static URI createUri(String scheme, String host, int port) { + try { + return new URI(scheme, null, host, port, null, null, null); + } catch (URISyntaxException e) { + throw new IllegalStateException(e); + } + } + + public static URI createUri(String scheme, InetSocketAddress addr) { + try { + return new URI(scheme, null, addr.getHostString(), addr.getPort(), null, null, null); + } catch (URISyntaxException e) { + throw new IllegalStateException(e); + } + } + + public static URI createUri(String uri) { + try { + return new URI(uri); + } catch (URISyntaxException e) { + throw new IllegalStateException(e); + } + } + + public static InetSocketAddress getSocketAddr(URI uri) { + return new InetSocketAddress(uri.getHost(), uri.getPort()); + } +} diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/endpoint/Protocol.java b/leshan-core/src/main/java/org/eclipse/leshan/core/endpoint/Protocol.java new file mode 100644 index 0000000000..5b269358b5 --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/endpoint/Protocol.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * 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.core.endpoint; + +public class Protocol { + + public static final Protocol COAP = new Protocol("COAP", "coap"); + public static final Protocol COAPS = new Protocol("COAPS", "coaps"); + public static final Protocol COAP_TCP = new Protocol("COAP_TCP", "coap+tcp"); + public static final Protocol COAPS_TCP = new Protocol("COAPS_TCP", "coaps+tcp"); + + private final String name; + private final String uriScheme; + + public Protocol(String name, String uriScheme) { + this.name = name; + this.uriScheme = uriScheme; + } + + public String getName() { + return name; + } + + public String getUriScheme() { + return uriScheme; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Protocol other = (Protocol) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/observation/CompositeObservation.java b/leshan-core/src/main/java/org/eclipse/leshan/core/observation/CompositeObservation.java index d99ddf2b24..c37f23f0c5 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/observation/CompositeObservation.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/observation/CompositeObservation.java @@ -22,7 +22,6 @@ import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.request.ContentFormat; -import org.eclipse.leshan.core.util.Hex; /** * An composite-observation of a resource provided by a LWM2M Client. @@ -43,9 +42,10 @@ public class CompositeObservation extends Observation { * @param responseContentFormat The {@link ContentFormat} requested to encode the {@link LwM2mNode} of the response. * @param context additional information relative to this observation. */ - public CompositeObservation(byte[] id, String registrationId, List paths, - ContentFormat requestContentFormat, ContentFormat responseContentFormat, Map context) { - super(id, registrationId, context); + public CompositeObservation(ObservationIdentifier id, String registrationId, List paths, + ContentFormat requestContentFormat, ContentFormat responseContentFormat, Map context, + Map protocolData) { + super(id, registrationId, context, protocolData); this.requestContentFormat = requestContentFormat; this.responseContentFormat = responseContentFormat; this.paths = paths; @@ -78,7 +78,7 @@ public List getPaths() { public String toString() { return String.format( "CompositeObservation [paths=%s, id=%s, requestContentFormat=%s, responseContentFormat=%s, registrationId=%s, context=%s]", - paths, Hex.encodeHexString(id), requestContentFormat, responseContentFormat, registrationId, context); + paths, id, requestContentFormat, responseContentFormat, registrationId, context); } @Override diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/observation/Observation.java b/leshan-core/src/main/java/org/eclipse/leshan/core/observation/Observation.java index 708d476b65..b5bfefffc9 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/observation/Observation.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/observation/Observation.java @@ -17,7 +17,6 @@ *******************************************************************************/ package org.eclipse.leshan.core.observation; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -27,9 +26,10 @@ */ public abstract class Observation { - protected final byte[] id; + protected final ObservationIdentifier id; protected final String registrationId; protected final Map context; + protected final Map protocolData; /** * An abstract constructor for {@link Observation}. @@ -38,20 +38,25 @@ public abstract class Observation { * @param registrationId client's unique registration identifier. * @param context additional information relative to this observation. */ - public Observation(byte[] id, String registrationId, Map context) { + public Observation(ObservationIdentifier id, String registrationId, Map context, + Map protocolData) { this.id = id; this.registrationId = registrationId; if (context != null) this.context = Collections.unmodifiableMap(new HashMap<>(context)); else this.context = Collections.emptyMap(); + if (protocolData != null) + this.protocolData = Collections.unmodifiableMap(new HashMap<>(protocolData)); + else + this.protocolData = Collections.emptyMap(); } /** * Get the id of this observation. * */ - public byte[] getId() { + public ObservationIdentifier getId() { return id; } @@ -71,12 +76,19 @@ public Map getContext() { return context; } + /** + * @return internal data specific to LwM2mEndpointsProvider + */ + public Map getProtocolData() { + return protocolData; + } + @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((context == null) ? 0 : context.hashCode()); - result = prime * result + Arrays.hashCode(id); + result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((registrationId == null) ? 0 : registrationId.hashCode()); return result; } @@ -95,7 +107,10 @@ public boolean equals(Object obj) { return false; } else if (!context.equals(other.context)) return false; - if (!Arrays.equals(id, other.id)) + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) return false; if (registrationId == null) { if (other.registrationId != null) @@ -104,5 +119,4 @@ public boolean equals(Object obj) { return false; return true; } - } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/observation/ObservationIdentifier.java b/leshan-core/src/main/java/org/eclipse/leshan/core/observation/ObservationIdentifier.java new file mode 100644 index 0000000000..47c57ad5d1 --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/observation/ObservationIdentifier.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * 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.core.observation; + +import java.util.Arrays; + +import org.eclipse.leshan.core.util.Hex; + +/** + * An Observation Identifier. + */ +public class ObservationIdentifier { + + private final byte[] bytes; + + public ObservationIdentifier(byte[] bytes) { + this.bytes = Arrays.copyOf(bytes, bytes.length); + } + + public final byte[] getBytes() { + return Arrays.copyOf(this.bytes, length()); + } + + public String getAsHexString() { + return Hex.encodeHexString(bytes); + } + + public boolean isEmpty() { + return bytes.length == 0; + } + + public int length() { + return bytes.length; + } + + @Override + public String toString() { + return String.format("Ox%s", getAsHexString()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(bytes); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ObservationIdentifier other = (ObservationIdentifier) obj; + if (!Arrays.equals(bytes, other.bytes)) + return false; + return true; + } +} diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/observation/SingleObservation.java b/leshan-core/src/main/java/org/eclipse/leshan/core/observation/SingleObservation.java index a86c3a6886..49bf10f549 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/observation/SingleObservation.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/observation/SingleObservation.java @@ -21,7 +21,6 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.request.ContentFormat; -import org.eclipse.leshan.core.util.Hex; /** * An observation of a resource provided by a LWM2M Client. @@ -41,9 +40,9 @@ public class SingleObservation extends Observation { * @param contentFormat contentFormat used to read the resource (could be null). * @param context additional information relative to this observation. */ - public SingleObservation(byte[] id, String registrationId, LwM2mPath path, ContentFormat contentFormat, - Map context) { - super(id, registrationId, context); + public SingleObservation(ObservationIdentifier id, String registrationId, LwM2mPath path, + ContentFormat contentFormat, Map context, Map protocolData) { + super(id, registrationId, context, protocolData); this.path = path; this.contentFormat = contentFormat; } @@ -69,7 +68,7 @@ public ContentFormat getContentFormat() { @Override public String toString() { return String.format("SingleObservation [path=%s, id=%s, contentFormat=%s, registrationId=%s, context=%s]", - path, Hex.encodeHexString(id), contentFormat, registrationId, context); + path, id, contentFormat, registrationId, context); } @Override diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/CancelCompositeObservationRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/CancelCompositeObservationRequest.java index 414e138062..b0630cdc22 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/CancelCompositeObservationRequest.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/CancelCompositeObservationRequest.java @@ -21,7 +21,6 @@ import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.response.CancelCompositeObservationResponse; -import org.eclipse.leshan.core.util.Hex; /** * A Lightweight M2M request for actively cancel an composite-observation. @@ -54,8 +53,7 @@ public Observation getObservation() { @Override public final String toString() { - return String.format("CancelCompositeObservation [paths=%s token=%s]", getPaths(), - Hex.encodeHexString(observation.getId())); + return String.format("CancelCompositeObservation [paths=%s token=%s]", getPaths(), observation.getId()); } @Override diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/CancelObservationRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/CancelObservationRequest.java index c3fada5710..0ffccb81a1 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/CancelObservationRequest.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/CancelObservationRequest.java @@ -19,7 +19,6 @@ import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.eclipse.leshan.core.response.CancelObservationResponse; -import org.eclipse.leshan.core.util.Hex; /** * A Lightweight M2M request for actively cancel an observation. @@ -57,8 +56,7 @@ public void accept(DownlinkRequestVisitor visitor) { @Override public final String toString() { - return String.format("CancelObservation [path=%s token=%s]", getPath(), - Hex.encodeHexString(observation.getId())); + return String.format("CancelObservation [path=%s token=%s]", getPath(), observation.getId()); } @Override 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 b447ef9de3..d0a5349837 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 @@ -37,6 +37,7 @@ import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; @@ -293,7 +294,8 @@ public void bootstrapWithDiscoverOnRootThenRebootstrap() throws InvalidRequestEx ";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.server.getUnsecuredAddress().getHostString(), helper.server.getUnsecuredAddress().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/DeleteTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DeleteTest.java index 6e94b96c51..c0389665ef 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DeleteTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/DeleteTest.java @@ -27,13 +27,18 @@ import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.elements.AddressEndpointContext; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.californium.CoapSyncRequestObserver; +import org.eclipse.leshan.core.californium.DefaultExceptionTranslator; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.integration.tests.util.IntegrationTestHelper; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpoint; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -83,7 +88,16 @@ public void cannot_delete_resource() throws InterruptedException { // try to delete this resource using coap API as lwm2m API does not allow it. Request delete = Request.newDelete(); delete.getOptions().addUriPath("2").addUriPath("0").addUriPath("0"); - Response response = helper.server.coap().send(helper.getCurrentRegistration(), delete); + + // TODO TL add Coap API again ? + delete.setDestinationContext(new AddressEndpointContext(helper.getCurrentRegistration().getSocketAddress())); + CoapSyncRequestObserver syncMessageObserver = new CoapSyncRequestObserver(delete, 2000, + new DefaultExceptionTranslator()); + delete.addMessageObserver(syncMessageObserver); + + CaliforniumServerEndpoint endpoint = (CaliforniumServerEndpoint) helper.server.getEndpoint(Protocol.COAP); + endpoint.getCoapEndpoint().sendRequest(delete); + Response response = syncMessageObserver.waitForCoapResponse(); // verify result assertEquals(org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST, response.getCode()); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java index 4ff089bb73..b8eca02066 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/RegistrationTest.java @@ -33,6 +33,7 @@ import java.io.IOException; import java.net.InetSocketAddress; +import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -49,6 +50,7 @@ import org.eclipse.californium.elements.config.SystemConfig; import org.eclipse.californium.elements.config.UdpConfig; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.link.LinkParseException; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.observation.Observation; @@ -252,7 +254,7 @@ public void register_observe_deregister_observe() throws NonUniqueSecurityInfoEx } catch (SendFailedException e) { return; } - fail("Observe request should be sent"); + fail("Observe request should NOT be sent"); } @Test @@ -287,7 +289,9 @@ public void register_with_invalid_request() throws InterruptedException, IOExcep // create a register request without the list of supported object Request coapRequest = new Request(Code.POST); - coapRequest.setDestinationContext(new AddressEndpointContext(helper.server.getUnsecuredAddress())); + URI destinationURI = helper.server.getEndpoint(Protocol.COAP).getURI(); + coapRequest + .setDestinationContext(new AddressEndpointContext(destinationURI.getHost(), destinationURI.getPort())); coapRequest.getOptions().setContentFormat(ContentFormat.LINK.getCode()); coapRequest.getOptions().addUriPath("rd"); coapRequest.getOptions().addUriQuery("ep=" + helper.currentEndpointIdentifier); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java index 491ee07269..a7eaf0646d 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java @@ -32,6 +32,7 @@ import static org.junit.Assert.fail; import java.io.IOException; +import java.net.URI; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.Certificate; @@ -55,6 +56,7 @@ import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.dtls.DTLSSession; import org.eclipse.leshan.core.CertificateUsage; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.exception.SendFailedException; import org.eclipse.leshan.core.request.exception.TimeoutException; @@ -261,8 +263,9 @@ public void dont_sent_request_if_identity_change() request.setMID(0); byte[] ping = new UdpDataSerializer().getByteArray(request); // sent it - connector.send( - RawData.outbound(ping, new AddressEndpointContext(helper.server.getSecuredAddress()), callback, false)); + URI destinationUri = helper.server.getEndpoint(Protocol.COAPS).getURI(); + connector.send(RawData.outbound(ping, + new AddressEndpointContext(destinationUri.getHost(), destinationUri.getPort()), callback, false)); // Wait until new handshake DTLS is done EndpointContext endpointContext = callback.getEndpointContext(1000); assertEquals(((PreSharedKeyIdentity) endpointContext.getPeerIdentity()).getIdentity(), "anotherPSK"); @@ -379,15 +382,15 @@ public void server_initiates_dtls_handshake() throws NonUniqueSecurityInfoExcept helper.assertClientRegisterered(); // Remove DTLS connection at server side. - ((DTLSConnector) helper.server.coap().getSecuredEndpoint().getConnector()).clearConnectionState(); + DTLSConnector dtlsServerConnector = helper.getServerDTLSConnector(); + dtlsServerConnector.clearConnectionState(); // try to send request ReadResponse readResponse = helper.server.send(registration, new ReadRequest(3), 1000); assertTrue(readResponse.isSuccess()); // ensure we have a new session for it - DTLSSession session = ((DTLSConnector) helper.server.coap().getSecuredEndpoint().getConnector()) - .getSessionByAddress(registration.getSocketAddress()); + DTLSSession session = dtlsServerConnector.getSessionByAddress(registration.getSocketAddress()); assertNotNull(session); } @@ -412,7 +415,7 @@ public void server_initiates_dtls_handshake_timeout() throws NonUniqueSecurityIn helper.assertClientRegisterered(); // Remove DTLS connection at server side. - ((DTLSConnector) helper.server.coap().getSecuredEndpoint().getConnector()).clearConnectionState(); + helper.getServerDTLSConnector().clearConnectionState(); // stop client helper.client.stop(false); @@ -453,7 +456,7 @@ public void server_does_not_initiate_dtls_handshake_with_queue_mode() helper.assertClientRegisterered(); // Remove DTLS connection at server side. - ((DTLSConnector) helper.server.coap().getSecuredEndpoint().getConnector()).clearConnectionState(); + helper.getServerDTLSConnector().clearConnectionState(); // try to send request try { @@ -1817,7 +1820,6 @@ public void registered_device_with_rpk_to_server_with_x509cert() boolean useServerCertifcatePublicKey = true; helper.createRPKClient(useServerCertifcatePublicKey); - helper.client.start(); helper.getSecurityStore() .add(SecurityInfo.newRawPublicKeyInfo(helper.getCurrentEndpoint(), helper.clientPublicKey)); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepLwM2mClient.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepLwM2mClient.java index 86327c33e5..594ff74726 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepLwM2mClient.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepLwM2mClient.java @@ -17,6 +17,7 @@ package org.eclipse.leshan.integration.tests.lockstep; import java.net.InetSocketAddress; +import java.net.URI; import java.util.List; import java.util.Random; @@ -31,6 +32,7 @@ import org.eclipse.californium.elements.config.SystemConfig; import org.eclipse.californium.elements.config.UdpConfig; import org.eclipse.leshan.client.californium.request.CoapRequestBuilder; +import org.eclipse.leshan.core.endpoint.EndpointUriUtil; import org.eclipse.leshan.core.link.DefaultLinkSerializer; import org.eclipse.leshan.core.link.LinkSerializer; import org.eclipse.leshan.core.model.LwM2mModel; @@ -46,11 +48,15 @@ public class LockStepLwM2mClient extends LockstepEndpoint { private static final Random r = new Random(); - private InetSocketAddress destination; + private final InetSocketAddress destination; private final LwM2mEncoder encoder; private final LwM2mModel model; private final LinkSerializer linkSerializer; + public LockStepLwM2mClient(final URI destination) { + this(EndpointUriUtil.getSocketAddr(destination)); + } + public LockStepLwM2mClient(final InetSocketAddress destination) { super(destination, new Configuration( new Configuration(CoapConfig.DEFINITIONS, UdpConfig.DEFINITIONS, SystemConfig.DEFINITIONS))); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepTest.java index 324bcfd820..6fd0867ad7 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/lockstep/LockStepTest.java @@ -31,6 +31,7 @@ import org.eclipse.californium.core.coap.Token; import org.eclipse.californium.core.config.CoapConfig; import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.RegisterRequest; @@ -39,7 +40,7 @@ import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.integration.tests.util.Callback; import org.eclipse.leshan.integration.tests.util.IntegrationTestHelper; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider.Builder; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -48,9 +49,11 @@ public class LockStepTest { public IntegrationTestHelper helper = new IntegrationTestHelper() { + @Override - protected LeshanServerBuilder createServerBuilder() { - Configuration coapConfig = LeshanServerBuilder.createDefaultCoapConfiguration(); + protected Builder createEndpointsProviderBuilder() { + Builder builder = super.createEndpointsProviderBuilder(); + Configuration coapConfig = builder.createDefaultConfiguration(); // configure retransmission, with this configuration a request without ACK should timeout in ~200*5ms coapConfig.set(CoapConfig.ACK_TIMEOUT, 200, TimeUnit.MILLISECONDS) // @@ -58,11 +61,9 @@ protected LeshanServerBuilder createServerBuilder() { .set(CoapConfig.ACK_TIMEOUT_SCALE, 1f) // .set(CoapConfig.MAX_RETRANSMIT, 4); - LeshanServerBuilder builder = super.createServerBuilder(); - builder.setCoapConfig(coapConfig); - + builder.setConfiguration(coapConfig); return builder; - }; + } }; @Before @@ -81,7 +82,7 @@ public void stop() { @Test public void register_with_uq_binding_in_lw_1_0() throws Exception { // Register client - LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getEndpoint(Protocol.COAP).getURI()); Token token = client.sendLwM2mRequest( new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.0", EnumSet.of(BindingMode.U, BindingMode.Q), null, null, linkParser.parseCoreLinkFormat(",,".getBytes()), null)); @@ -92,7 +93,7 @@ public void register_with_uq_binding_in_lw_1_0() throws Exception { @Test public void register_with_ut_binding_in_lw_1_1() throws Exception { // Register client - LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getEndpoint(Protocol.COAP).getURI()); Token token = client.sendLwM2mRequest( new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", EnumSet.of(BindingMode.U, BindingMode.T), null, null, linkParser.parseCoreLinkFormat(",,".getBytes()), null)); @@ -102,7 +103,7 @@ public void register_with_ut_binding_in_lw_1_1() throws Exception { @Test public void register_update_with_invalid_binding_for_lw_1_1() throws Exception { - LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getEndpoint(Protocol.COAP).getURI()); // register with valid binding for 1.1 RegisterRequest validRegisterRequest = new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", @@ -135,7 +136,7 @@ public void register_update_with_invalid_binding_for_lw_1_1() throws Exception { @Test public void register_update_with_invalid_binding_for_lw_1_0() throws Exception { - LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getEndpoint(Protocol.COAP).getURI()); // register with valid binding for 1.0 RegisterRequest validRegisterRequest = new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.0", @@ -169,7 +170,7 @@ public void register_update_with_invalid_binding_for_lw_1_0() throws Exception { @Test public void sync_send_without_acknowleged() throws Exception { // Register client - LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getEndpoint(Protocol.COAP).getURI()); Token token = client.sendLwM2mRequest( new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", EnumSet.of(BindingMode.U), null, null, linkParser.parseCoreLinkFormat(",,".getBytes()), null)); @@ -192,7 +193,7 @@ public ReadResponse call() throws Exception { @Test public void sync_send_with_acknowleged_request_without_response() throws Exception { // Register client - LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getEndpoint(Protocol.COAP).getURI()); Token token = client.sendLwM2mRequest( new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", EnumSet.of(BindingMode.U), null, null, linkParser.parseCoreLinkFormat(",,".getBytes()), null)); @@ -222,7 +223,7 @@ public ReadResponse call() throws Exception { @Test public void async_send_without_acknowleged() throws Exception { // register client - LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getEndpoint(Protocol.COAP).getURI()); Token token = client.sendLwM2mRequest( new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", EnumSet.of(BindingMode.U), null, null, linkParser.parseCoreLinkFormat(",,".getBytes()), null)); @@ -242,7 +243,7 @@ public void async_send_without_acknowleged() throws Exception { @Test public void async_send_with_acknowleged_request_without_response() throws Exception { // register client - LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getEndpoint(Protocol.COAP).getURI()); Token token = client.sendLwM2mRequest( new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", EnumSet.of(BindingMode.U), null, null, linkParser.parseCoreLinkFormat(",,".getBytes()), null)); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/ObserveTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/ObserveTest.java index 8da1e1d467..38e2c85cdb 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/ObserveTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/ObserveTest.java @@ -31,6 +31,7 @@ import org.eclipse.californium.core.coap.Response; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; @@ -466,7 +467,8 @@ public void can_handle_error_on_notification() throws InterruptedException { Response firstCoapResponse = (Response) observeResponse.getCoapResponse(); // 666 is not a supported content format. TestObserveUtil.sendNotification(helper.getClientConnector(helper.getCurrentRegisteredServer()), - helper.server.getUnsecuredAddress(), payload, firstCoapResponse, ContentFormat.fromCode(666)); + helper.server.getEndpoint(Protocol.COAP).getURI(), payload, firstCoapResponse, + ContentFormat.fromCode(666)); // *** Hack End *** // // verify result diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/ObserveTimeStampTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/ObserveTimeStampTest.java index 1178515077..65a09d6d0e 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/ObserveTimeStampTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/ObserveTimeStampTest.java @@ -32,6 +32,7 @@ import org.eclipse.californium.core.coap.Response; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; @@ -66,8 +67,8 @@ public static Collection contentFormats() { protected IntegrationTestHelper helper = new IntegrationTestHelper(); - private ContentFormat contentFormat; - private LwM2mEncoder encoder = new DefaultLwM2mEncoder(); + private final ContentFormat contentFormat; + private final LwM2mEncoder encoder = new DefaultLwM2mEncoder(); public ObserveTimeStampTest(ContentFormat contentFormat) { this.contentFormat = contentFormat; @@ -123,7 +124,7 @@ public void can_observe_timestamped_resource() throws InterruptedException { new StaticModel(helper.createObjectModels())); Response firstCoapResponse = (Response) observeResponse.getCoapResponse(); TestObserveUtil.sendNotification(helper.getClientConnector(helper.getCurrentRegisteredServer()), - helper.server.getUnsecuredAddress(), payload, firstCoapResponse, contentFormat); + helper.server.getEndpoint(Protocol.COAP).getURI(), payload, firstCoapResponse, contentFormat); // *** Hack End *** // // verify result @@ -166,7 +167,7 @@ public void can_observe_timestamped_instance() throws InterruptedException { new StaticModel(helper.createObjectModels())); Response firstCoapResponse = (Response) observeResponse.getCoapResponse(); TestObserveUtil.sendNotification(helper.getClientConnector(helper.getCurrentRegisteredServer()), - helper.server.getUnsecuredAddress(), payload, firstCoapResponse, contentFormat); + helper.server.getEndpoint(Protocol.COAP).getURI(), payload, firstCoapResponse, contentFormat); // *** Hack End *** // // verify result @@ -210,7 +211,7 @@ public void can_observe_timestamped_object() throws InterruptedException { Response firstCoapResponse = (Response) observeResponse.getCoapResponse(); TestObserveUtil.sendNotification(helper.getClientConnector(helper.getCurrentRegisteredServer()), - helper.server.getUnsecuredAddress(), payload, firstCoapResponse, contentFormat); + helper.server.getEndpoint(Protocol.COAP).getURI(), payload, firstCoapResponse, contentFormat); // *** Hack End *** // // verify result diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/TestObserveUtil.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/TestObserveUtil.java index 35bf5037b4..abc3d17029 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/TestObserveUtil.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/observe/TestObserveUtil.java @@ -15,7 +15,7 @@ *******************************************************************************/ package org.eclipse.leshan.integration.tests.observe; -import java.net.InetSocketAddress; +import java.net.URI; import org.eclipse.californium.core.coap.CoAP.Type; import org.eclipse.californium.core.coap.OptionSet; @@ -28,7 +28,7 @@ import org.eclipse.leshan.core.request.ContentFormat; public class TestObserveUtil { - public static void sendNotification(Connector connector, InetSocketAddress destination, byte[] payload, + public static void sendNotification(Connector connector, URI destination, byte[] payload, Response firstCoapResponse, ContentFormat contentFormat) { // create observe response @@ -40,7 +40,7 @@ public static void sendNotification(Connector connector, InetSocketAddress desti OptionSet options = new OptionSet().setContentFormat(contentFormat.getCode()) .setObserve(firstCoapResponse.getOptions().getObserve() + 1); response.setOptions(options); - EndpointContext context = new AddressEndpointContext(destination); + EndpointContext context = new AddressEndpointContext(destination.getHost(), destination.getPort()); response.setDestinationContext(context); // serialize response diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/LockStepSendTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/LockStepSendTest.java index d1392547e3..9834f2c8da 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/LockStepSendTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/send/LockStepSendTest.java @@ -28,6 +28,7 @@ import org.eclipse.californium.core.coap.Token; import org.eclipse.californium.core.config.CoapConfig; import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mSingleResource; @@ -38,7 +39,7 @@ import org.eclipse.leshan.integration.tests.lockstep.LockStepLwM2mClient; import org.eclipse.leshan.integration.tests.util.IntegrationTestHelper; import org.eclipse.leshan.integration.tests.util.SynchronousSendListener; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider.Builder; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -46,9 +47,11 @@ public class LockStepSendTest { public IntegrationTestHelper helper = new IntegrationTestHelper() { + @Override - protected LeshanServerBuilder createServerBuilder() { - Configuration coapConfig = LeshanServerBuilder.createDefaultCoapConfiguration(); + protected Builder createEndpointsProviderBuilder() { + Builder builder = super.createEndpointsProviderBuilder(); + Configuration coapConfig = builder.createDefaultConfiguration(); // configure retransmission, with this configuration a request without ACK should timeout in ~200*5ms coapConfig.set(CoapConfig.ACK_TIMEOUT, 200, TimeUnit.MILLISECONDS) // @@ -56,11 +59,9 @@ protected LeshanServerBuilder createServerBuilder() { .set(CoapConfig.ACK_TIMEOUT_SCALE, 1f) // .set(CoapConfig.MAX_RETRANSMIT, 4); - LeshanServerBuilder builder = super.createServerBuilder(); - builder.setCoapConfig(coapConfig); - + builder.setConfiguration(coapConfig); return builder; - }; + } }; @Before @@ -79,7 +80,7 @@ public void stop() { @Test public void register_send_with_invalid_payload() throws Exception { // Register client - LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getUnsecuredAddress()); + LockStepLwM2mClient client = new LockStepLwM2mClient(helper.server.getEndpoint(Protocol.COAP).getURI()); Token token = client.sendLwM2mRequest( new RegisterRequest(helper.getCurrentEndpoint(), 60l, "1.1", EnumSet.of(BindingMode.U), null, null, linkParser.parseCoreLinkFormat(",,".getBytes()), null)); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/server/redis/RedisRegistrationStoreTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/server/redis/RedisRegistrationStoreTest.java index adead6f085..81ad9d7b0f 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/server/redis/RedisRegistrationStoreTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/server/redis/RedisRegistrationStoreTest.java @@ -31,22 +31,31 @@ import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Token; +import org.eclipse.californium.core.network.serialization.UdpDataParser; +import org.eclipse.californium.core.network.serialization.UdpDataSerializer; import org.eclipse.californium.elements.AddressEndpointContext; import org.eclipse.leshan.core.californium.ObserveUtil; import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.ContentFormat; 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.response.ObserveCompositeResponse; +import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.integration.tests.util.RedisIntegrationTestHelper; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; +import org.eclipse.leshan.server.californium.observation.LwM2mObservationStore; +import org.eclipse.leshan.server.californium.observation.ObservationSerDes; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.profile.ClientProfile; import org.eclipse.leshan.server.redis.RedisRegistrationStore; import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationStore; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -60,9 +69,11 @@ public class RedisRegistrationStoreTest { private final EnumSet binding = EnumSet.of(BindingMode.U, BindingMode.Q, BindingMode.S); private final Link[] objectLinks = new Link[] { new Link("/3") }; private final String registrationId = "4711"; - private final Token exampleToken = Token.EMPTY; + private final Token aToken = Token.EMPTY; + private final ObservationIdentifier anObservationId = new ObservationIdentifier(aToken.getBytes()); - CaliforniumRegistrationStore store; + RegistrationStore store; + LwM2mObservationStore observationStore; InetAddress address; Registration registration; @@ -73,6 +84,29 @@ public void setUp() throws UnknownHostException { helper = new RedisIntegrationTestHelper(); address = InetAddress.getLocalHost(); store = new RedisRegistrationStore(helper.createJedisPool()); + observationStore = new LwM2mObservationStore(store, new LwM2mNotificationReceiver() { + + @Override + public void onNotification(CompositeObservation observation, ClientProfile profile, + ObserveCompositeResponse response) { + } + + @Override + public void onNotification(SingleObservation observation, ClientProfile profile, ObserveResponse response) { + } + + @Override + public void onError(Observation observation, ClientProfile profile, Exception error) { + } + + @Override + public void newObservation(Observation observation, Registration registration) { + } + + @Override + public void cancelled(Observation observation) { + } + }, new ObservationSerDes(new UdpDataParser(), new UdpDataSerializer())); } @After @@ -92,10 +126,10 @@ public void get_observation_from_request() { examplePath); // when - store.put(exampleToken, observationToStore); + observationStore.put(aToken, observationToStore); // then - Observation leshanObservation = store.getObservation(registrationId, exampleToken.getBytes()); + Observation leshanObservation = store.getObservation(registrationId, anObservationId); assertNotNull(leshanObservation); assertTrue(leshanObservation instanceof SingleObservation); SingleObservation observation = (SingleObservation) leshanObservation; @@ -106,7 +140,6 @@ public void get_observation_from_request() { public void get_composite_observation_from_request() { // given List examplePaths = Arrays.asList(new LwM2mPath("/1/2/3"), new LwM2mPath("/4/5/6")); - Token exampleToken = Token.EMPTY; givenASimpleRegistration(lifetime); store.addRegistration(registration); @@ -115,10 +148,10 @@ public void get_composite_observation_from_request() { examplePaths); // when - store.put(exampleToken, observationToStore); + observationStore.put(aToken, observationToStore); // then - Observation leshanObservation = store.getObservation(registrationId, exampleToken.getBytes()); + Observation leshanObservation = store.getObservation(registrationId, anObservationId); assertNotNull(leshanObservation); assertTrue(leshanObservation instanceof CompositeObservation); CompositeObservation observation = (CompositeObservation) leshanObservation; @@ -153,7 +186,7 @@ private org.eclipse.californium.core.observe.Observation prepareCoapObservationO private org.eclipse.californium.core.observe.Observation prepareCoapObservation(Request coapRequest, Map userContext) { coapRequest.setUserContext(userContext); - coapRequest.setToken(exampleToken); + coapRequest.setToken(aToken); coapRequest.setObserve(); coapRequest.getOptions().setAccept(ContentFormat.DEFAULT.getCode()); coapRequest.setMID(1); 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 1478e58410..b1d65bb214 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 @@ -55,6 +55,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.Protocol; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; import org.eclipse.leshan.core.oscore.OscoreIdentity; @@ -100,7 +101,7 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper public final PrivateKey bootstrapServerPrivateKey; public volatile LwM2mResponse lastCustomResponse; - private SynchronousBootstrapListener bootstrapListener = new SynchronousBootstrapListener(); + private final SynchronousBootstrapListener bootstrapListener = new SynchronousBootstrapListener(); private class TestBootstrapSessionManager extends DefaultBootstrapSessionManager { @@ -490,8 +491,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for DM server ServerSecurity dmSecurity = new ServerSecurity(); - dmSecurity.uri = "coap://" + server.getUnsecuredAddress().getHostString() + ":" - + server.getUnsecuredAddress().getPort(); + dmSecurity.uri = server.getEndpoint(Protocol.COAP).getURI().toString(); dmSecurity.serverId = 2222; dmSecurity.securityMode = SecurityMode.NO_SEC; bsConfig.security.put(1, dmSecurity); @@ -546,8 +546,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for DM server ServerSecurity dmSecurity = new ServerSecurity(); - dmSecurity.uri = "coap://" + server.getUnsecuredAddress().getHostString() + ":" - + server.getUnsecuredAddress().getPort(); + dmSecurity.uri = server.getEndpoint(Protocol.COAP).getURI().toString(); dmSecurity.serverId = 2222; dmSecurity.securityMode = SecurityMode.NO_SEC; bsConfig.security.put(1, dmSecurity); @@ -596,8 +595,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for DM server ServerSecurity dmSecurity = new ServerSecurity(); - dmSecurity.uri = "coaps://" + server.getUnsecuredAddress().getHostString() + ":" - + server.getSecuredAddress().getPort(); + dmSecurity.uri = server.getEndpoint(Protocol.COAPS).getURI().toString(); dmSecurity.serverId = 2222; dmSecurity.securityMode = SecurityMode.PSK; dmSecurity.publicKeyOrId = GOOD_PSK_ID.getBytes(); @@ -632,8 +630,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for DM server ServerSecurity dmSecurity = new ServerSecurity(); - dmSecurity.uri = "coaps://" + server.getUnsecuredAddress().getHostString() + ":" - + server.getSecuredAddress().getPort(); + dmSecurity.uri = server.getEndpoint(Protocol.COAPS).getURI().toString(); dmSecurity.serverId = 2222; dmSecurity.securityMode = SecurityMode.RPK; dmSecurity.publicKeyOrId = clientPublicKey.getEncoded(); @@ -693,8 +690,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for DM server ServerSecurity dmSecurity = new ServerSecurity(); - dmSecurity.uri = "coap://" + server.getUnsecuredAddress().getHostString() + ":" - + server.getUnsecuredAddress().getPort(); + dmSecurity.uri = server.getEndpoint(Protocol.COAP).getURI().toString(); dmSecurity.serverId = 2222; dmSecurity.securityMode = SecurityMode.NO_SEC; dmSecurity.oscoreSecurityMode = 1; @@ -732,8 +728,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for DM server ServerSecurity dmSecurity = new ServerSecurity(); - dmSecurity.uri = "coap://" + server.getUnsecuredAddress().getHostString() + ":" - + server.getUnsecuredAddress().getPort(); + dmSecurity.uri = server.getEndpoint(Protocol.COAP).getURI().toString(); dmSecurity.serverId = 2222; dmSecurity.securityMode = SecurityMode.NO_SEC; dmSecurity.oscoreSecurityMode = 1; @@ -771,8 +766,7 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe // security for DM server ServerSecurity dmSecurity = new ServerSecurity(); - dmSecurity.uri = "coap://" + server.getUnsecuredAddress().getHostString() + ":" - + server.getUnsecuredAddress().getPort(); + dmSecurity.uri = server.getEndpoint(Protocol.COAP).getURI().toString(); dmSecurity.serverId = 2222; dmSecurity.securityMode = SecurityMode.NO_SEC; bsConfig.security.put(1, dmSecurity); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java index 0637415242..a04163fb58 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java @@ -36,6 +36,7 @@ import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.elements.Connector; +import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.leshan.client.californium.LeshanClient; import org.eclipse.leshan.client.californium.LeshanClientBuilder; import org.eclipse.leshan.client.object.Device; @@ -48,6 +49,8 @@ import org.eclipse.leshan.client.send.ManualDataSender; import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.LwM2mId; +import org.eclipse.leshan.core.endpoint.EndpointUriUtil; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.link.DefaultLinkSerializer; import org.eclipse.leshan.core.link.LinkParser; import org.eclipse.leshan.core.link.LinkSerializer; @@ -61,8 +64,14 @@ import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.util.TestLwM2mId; -import org.eclipse.leshan.server.californium.LeshanServer; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; +import org.eclipse.leshan.server.LeshanServer; +import org.eclipse.leshan.server.LeshanServerBuilder; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpoint; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider.Builder; +import org.eclipse.leshan.server.californium.endpoint.coap.CoapOscoreServerEndpointFactory; +import org.eclipse.leshan.server.californium.endpoint.coap.CoapServerProtocolProvider; +import org.eclipse.leshan.server.californium.endpoint.coaps.CoapsServerProtocolProvider; import org.eclipse.leshan.server.model.VersionedModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationServiceImpl; @@ -88,14 +97,16 @@ public class IntegrationTestHelper { public LeshanClient client; public AtomicReference currentEndpointIdentifier = new AtomicReference(); - private SynchronousClientObserver clientObserver = new SynchronousClientObserver(); - private SynchronousRegistrationListener registrationListener = new SynchronousRegistrationListener() { + private final SynchronousClientObserver clientObserver = new SynchronousClientObserver(); + private final SynchronousRegistrationListener registrationListener = new SynchronousRegistrationListener() { @Override public boolean accept(Registration registration) { return (registration != null && registration.getEndpoint().equals(currentEndpointIdentifier.get())); } }; + private boolean useOscore = false; + public List createObjectModels() { // load default object from the spec List objectModels = TestObjectLoader.loadDefaultObject(); @@ -146,9 +157,8 @@ protected ObjectsInitializer createObjectsInitializer() { public void createClient(Map additionalAttributes) { // Create objects Enabler ObjectsInitializer initializer = createObjectsInitializer(); - initializer.setInstancesForObject(LwM2mId.SECURITY, Security.noSec( - "coap://" + server.getUnsecuredAddress().getHostString() + ":" + server.getUnsecuredAddress().getPort(), - 12345)); + initializer.setInstancesForObject(LwM2mId.SECURITY, + Security.noSec(server.getEndpoint(Protocol.COAP).getURI().toString(), 12345)); initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); initializer.setInstancesForObject(LwM2mId.DEVICE, new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); @@ -173,18 +183,32 @@ public void createServer() { } public void createOscoreServer() { - server = createServerBuilder().setEnableOscore(true).build(); + // TODO support OSCORE + useOscore = true; + server = createServerBuilder().build(); // monitor client registration setupServerMonitoring(); } + protected Builder createEndpointsProviderBuilder() { + Builder endpointsBuilder = new CaliforniumServerEndpointsProvider.Builder(new CoapServerProtocolProvider(), + new CoapsServerProtocolProvider()); + if (useOscore) { + endpointsBuilder.addEndpoint(new CoapOscoreServerEndpointFactory( + EndpointUriUtil.createUri("coap", new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)))); + } else { + endpointsBuilder.addEndpoint(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), Protocol.COAP); + } + return endpointsBuilder; + } + protected LeshanServerBuilder createServerBuilder() { LeshanServerBuilder builder = new LeshanServerBuilder(); builder.setDecoder(new DefaultLwM2mDecoder(true)); builder.setEncoder(new DefaultLwM2mEncoder(true)); builder.setObjectModelProvider(new VersionedModelProvider(createObjectModels())); - builder.setLocalAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); - builder.setLocalSecureAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + + builder.setEndpointsProvider(createEndpointsProviderBuilder().build()); SecurityStore securityStore = createSecurityStore(); builder.setSecurityStore(securityStore); builder.setAuthorizer(new DefaultAuthorizer(securityStore) { @@ -348,4 +372,9 @@ public Connector getClientConnector(ServerIdentity server) { CoapEndpoint endpoint = (CoapEndpoint) client.coap().getServer().getEndpoint(client.getAddress(server)); return endpoint.getConnector(); } + + public DTLSConnector getServerDTLSConnector() { + CaliforniumServerEndpoint endpoint = (CaliforniumServerEndpoint) server.getEndpoint(Protocol.COAPS); + return (DTLSConnector) endpoint.getCoapEndpoint().getConnector(); + } } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/QueueModeIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/QueueModeIntegrationTestHelper.java index 0a98d33f00..b1cad9a4fe 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/QueueModeIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/QueueModeIntegrationTestHelper.java @@ -34,11 +34,12 @@ import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.core.LwM2mId; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.core.util.TestLwM2mId; import org.eclipse.leshan.integration.tests.PresenceCounter; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; +import org.eclipse.leshan.server.LeshanServerBuilder; import org.eclipse.leshan.server.queue.StaticClientAwakeTimeProvider; import org.eclipse.leshan.server.registration.Registration; @@ -68,9 +69,8 @@ public boolean accept(Registration registration) { public void createClient() { // Create objects Enabler ObjectsInitializer initializer = new TestObjectsInitializer(new StaticModel(createObjectModels())); - initializer.setInstancesForObject(LwM2mId.SECURITY, Security.noSec( - "coap://" + server.getUnsecuredAddress().getHostString() + ":" + server.getUnsecuredAddress().getPort(), - 12345)); + initializer.setInstancesForObject(LwM2mId.SECURITY, + Security.noSec(server.getEndpoint(Protocol.COAP).getURI().toString(), 12345)); initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); initializer.setInstancesForObject(LwM2mId.DEVICE, new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisIntegrationTestHelper.java index d8980d8b87..5dfd7c9b52 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisIntegrationTestHelper.java @@ -15,14 +15,8 @@ *******************************************************************************/ package org.eclipse.leshan.integration.tests.util; -import java.net.InetAddress; -import java.net.InetSocketAddress; - -import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; -import org.eclipse.leshan.server.model.StaticModelProvider; +import org.eclipse.leshan.server.LeshanServerBuilder; import org.eclipse.leshan.server.redis.RedisRegistrationStore; -import org.eclipse.leshan.server.security.InMemorySecurityStore; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @@ -30,24 +24,14 @@ public class RedisIntegrationTestHelper extends IntegrationTestHelper { @Override - public void createServer() { - LeshanServerBuilder builder = new LeshanServerBuilder(); - StaticModelProvider modelProvider = new StaticModelProvider(createObjectModels()); - builder.setObjectModelProvider(modelProvider); - DefaultLwM2mDecoder decoder = new DefaultLwM2mDecoder(); - builder.setDecoder(decoder); - builder.setLocalAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); - builder.setLocalSecureAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); - builder.setSecurityStore(new InMemorySecurityStore()); + protected LeshanServerBuilder createServerBuilder() { + LeshanServerBuilder builder = super.createServerBuilder(); // Create redis store Pool jedis = createJedisPool(); builder.setRegistrationStore(new RedisRegistrationStore(jedis)); - // Build server ! - server = builder.build(); - // monitor client registration - setupServerMonitoring(); + return builder; } public Pool createJedisPool() { diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java index d3ca1219e6..2332c07493 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java @@ -75,12 +75,13 @@ import org.eclipse.leshan.core.CertificateUsage; import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.californium.EndpointFactory; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.oscore.AeadAlgorithm; import org.eclipse.leshan.core.oscore.HkdfAlgorithm; import org.eclipse.leshan.core.oscore.OscoreSetting; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.X509CertUtil; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; +import org.eclipse.leshan.server.LeshanServerBuilder; import org.eclipse.leshan.server.security.DefaultAuthorizer; import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.InMemorySecurityStore; @@ -148,6 +149,8 @@ public class SecureIntegrationTestHelper extends IntegrationTestHelper { // client's initial empty trust store public final List clientEmptyTrustStore = new ArrayList<>(); + private Boolean serverOnly = null; + public SecureIntegrationTestHelper() { // create client credentials try { @@ -248,10 +251,8 @@ public void createPSKClientUsingQueueMode() { public void createPSKClient(boolean queueMode) { ObjectsInitializer initializer = new TestObjectsInitializer(); initializer.setInstancesForObject(LwM2mId.SECURITY, - Security.psk( - "coaps://" + server.getSecuredAddress().getHostString() + ":" - + server.getSecuredAddress().getPort(), - 12345, GOOD_PSK_ID.getBytes(StandardCharsets.UTF_8), GOOD_PSK_KEY)); + Security.psk(server.getEndpoint(Protocol.COAPS).getURI().toString(), 12345, + GOOD_PSK_ID.getBytes(StandardCharsets.UTF_8), GOOD_PSK_KEY)); initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setDummyInstancesForObject(LwM2mId.ACCESS_CONTROL); @@ -315,8 +316,8 @@ public void setNewPsk(String identity, byte[] key) { public void createRPKClient(boolean useServerCertificate) { ObjectsInitializer initializer = new TestObjectsInitializer(); initializer.setInstancesForObject(LwM2mId.SECURITY, Security.rpk( - "coaps://" + server.getSecuredAddress().getHostString() + ":" + server.getSecuredAddress().getPort(), - 12345, clientPublicKey.getEncoded(), clientPrivateKey.getEncoded(), + server.getEndpoint(Protocol.COAPS).getURI().toString(), 12345, clientPublicKey.getEncoded(), + clientPrivateKey.getEncoded(), useServerCertificate ? serverX509Cert.getPublicKey().getEncoded() : serverPublicKey.getEncoded())); initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345")); @@ -358,11 +359,8 @@ public void createX509CertClient(Certificate clientCertificate, PrivateKey priva Certificate serverCertificate) throws CertificateEncodingException { ObjectsInitializer initializer = new TestObjectsInitializer(); initializer.setInstancesForObject(LwM2mId.SECURITY, - Security.x509( - "coaps://" + server.getSecuredAddress().getHostString() + ":" - + server.getSecuredAddress().getPort(), - 12345, clientCertificate.getEncoded(), privatekey.getEncoded(), - serverCertificate.getEncoded())); + Security.x509(server.getEndpoint(Protocol.COAPS).getURI().toString(), 12345, + clientCertificate.getEncoded(), privatekey.getEncoded(), serverCertificate.getEncoded())); initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); @@ -390,11 +388,9 @@ public void createX509CertClient(X509Certificate[] clientCertificate, PrivateKey ObjectsInitializer initializer = new TestObjectsInitializer(); initializer.setInstancesForObject(LwM2mId.SECURITY, - Security.x509( - "coaps://" + server.getSecuredAddress().getHostString() + ":" - + server.getSecuredAddress().getPort(), - 12345, clientCertificate[0].getEncoded(), privatekey.getEncoded(), - serverCertificate.getEncoded(), certificateUsage.code)); + Security.x509(server.getEndpoint(Protocol.COAPS).getURI().toString(), 12345, + clientCertificate[0].getEncoded(), privatekey.getEncoded(), serverCertificate.getEncoded(), + certificateUsage.code)); initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345")); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); @@ -415,27 +411,25 @@ public void createX509CertClient(X509Certificate[] clientCertificate, PrivateKey setupClientMonitoring(); } - @Override - protected LeshanServerBuilder createServerBuilder() { - return createServerBuilder(null); - } - @Override protected SecurityStore createSecurityStore() { securityStore = new InMemorySecurityStore(); return securityStore; } - protected LeshanServerBuilder createServerBuilder(Boolean serverOnly) { - LeshanServerBuilder builder = super.createServerBuilder(); - Configuration configuration = LeshanServerBuilder.createDefaultCoapConfiguration(); - Builder dtlsConfig = DtlsConnectorConfig.builder(configuration); - dtlsConfig.set(DtlsConfig.DTLS_MAX_RETRANSMISSIONS, 1); - dtlsConfig.set(DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT, 300, TimeUnit.MILLISECONDS); + @Override + protected org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider.Builder createEndpointsProviderBuilder() { + org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider.Builder builder = super.createEndpointsProviderBuilder(); + Configuration configuration = builder.createDefaultConfiguration(); + configuration.set(DtlsConfig.DTLS_MAX_RETRANSMISSIONS, 1); + configuration.set(DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT, 300, TimeUnit.MILLISECONDS); if (serverOnly != null && serverOnly) { - dtlsConfig.set(DtlsConfig.DTLS_ROLE, DtlsRole.SERVER_ONLY); + configuration.set(DtlsConfig.DTLS_ROLE, DtlsRole.SERVER_ONLY); } - builder.setDtlsConfig(dtlsConfig); + builder.setConfiguration(configuration); + + builder.addEndpoint(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), Protocol.COAPS); + return builder; } @@ -464,7 +458,8 @@ public void createServerWithX509Cert(X509Certificate serverCertificateChain[], P public void createServerWithX509Cert(X509Certificate serverCertificateChain[], PrivateKey privateKey, Certificate[] trustedCertificates, Boolean serverOnly) { - LeshanServerBuilder builder = createServerBuilder(serverOnly); + this.serverOnly = serverOnly; + LeshanServerBuilder builder = createServerBuilder(); builder.setPrivateKey(privateKey); builder.setCertificateChain(serverCertificateChain); builder.setTrustedCertificates(trustedCertificates); @@ -484,8 +479,7 @@ protected boolean matchX509Identity(String endpoint, String receivedX509CommonNa public void createOscoreClient() { ObjectsInitializer initializer = new TestObjectsInitializer(); - String serverUri = "coap://" + server.getUnsecuredAddress().getHostString() + ":" - + server.getUnsecuredAddress().getPort(); + String serverUri = server.getEndpoint(Protocol.COAP).getURI().toString(); Oscore oscoreObject = new Oscore(12345, getClientOscoreSetting()); initializer.setInstancesForObject(SECURITY, oscoreOnly(serverUri, 12345, oscoreObject.getId())); diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java deleted file mode 100644 index a0be04874a..0000000000 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java +++ /dev/null @@ -1,651 +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 - * Achim Kraus (Bosch Software Innovations GmbH) - use Lwm2mEndpointContextMatcher - * for secure endpoint. - * Achim Kraus (Bosch Software Innovations GmbH) - use CoapEndpointBuilder - * Michał Wadowski (Orange) - Improved compliance with rfc6690. - *******************************************************************************/ -package org.eclipse.leshan.server.californium; - -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 java.util.concurrent.TimeUnit; - -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.DtlsEndpointContext; -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.core.observation.Observation; -import org.eclipse.leshan.core.request.exception.ClientSleepingException; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; -import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore; -import org.eclipse.leshan.server.model.LwM2mModelProvider; -import org.eclipse.leshan.server.model.StandardModelProvider; -import org.eclipse.leshan.server.queue.ClientAwakeTimeProvider; -import org.eclipse.leshan.server.queue.StaticClientAwakeTimeProvider; -import org.eclipse.leshan.server.registration.RandomStringRegistrationIdProvider; -import org.eclipse.leshan.server.registration.Registration; -import org.eclipse.leshan.server.registration.RegistrationIdProvider; -import org.eclipse.leshan.server.registration.RegistrationStore; -import org.eclipse.leshan.server.security.Authorizer; -import org.eclipse.leshan.server.security.DefaultAuthorizer; -import org.eclipse.leshan.server.security.EditableSecurityStore; -import org.eclipse.leshan.server.security.InMemorySecurityStore; -import org.eclipse.leshan.server.security.SecurityInfo; -import org.eclipse.leshan.server.security.SecurityStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Class helping you to build and configure a Californium based Leshan 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 LeshanServer} ready to operate. - */ -public class LeshanServerBuilder { - - private static final Logger LOG = LoggerFactory.getLogger(LeshanServerBuilder.class); - - private CaliforniumRegistrationStore registrationStore; - private SecurityStore securityStore; - private LwM2mModelProvider modelProvider; - private Authorizer authorizer; - private ClientAwakeTimeProvider awakeTimeProvider; - private RegistrationIdProvider registrationIdProvider; - - private InetSocketAddress localAddress; - private InetSocketAddress localSecureAddress; - - private LwM2mEncoder encoder; - private LwM2mDecoder decoder; - - private PublicKey publicKey; - private PrivateKey privateKey; - private X509Certificate[] certificateChain; - private Certificate[] trustedCertificates; - - private Configuration coapConfig; - private DtlsConnectorConfig.Builder dtlsConfigBuilder; - - private EndpointFactory endpointFactory; - - private boolean noSecuredEndpoint; - private boolean noUnsecuredEndpoint; - private boolean noQueueMode = false; - /** @since 1.1 */ - protected boolean updateRegistrationOnNotification; - private LwM2mLinkParser linkParser; - - private boolean enableOscore = false; - - /** - *

- * Set the address/port for unsecured CoAP Server. - *

- * - * 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. - */ - public LeshanServerBuilder 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 Server. - *

- * - * By default a wildcard address and the default CoAP port(5683) is used. - */ - public LeshanServerBuilder setLocalAddress(InetSocketAddress localAddress) { - this.localAddress = localAddress; - return this; - } - - /** - *

- * Set the address/port for secured CoAP Server (Using DTLS). - *

- * - * 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. - */ - public LeshanServerBuilder setLocalSecureAddress(String hostname, int port) { - if (hostname == null) { - this.localSecureAddress = new InetSocketAddress(port); - } else { - this.localSecureAddress = new InetSocketAddress(hostname, port); - } - return this; - } - - /** - *

- * Set the address for secured CoAP Server (Using DTLS). - *

- * - * By default a wildcard address and the default CoAP port(5684) is used. - */ - public LeshanServerBuilder setLocalSecureAddress(InetSocketAddress localSecureAddress) { - this.localSecureAddress = localSecureAddress; - return this; - } - - /** - *

- * Set your {@link RegistrationStore} implementation which stores {@link Registration} and {@link Observation}. - *

- * By default the {@link InMemoryRegistrationStore} implementation is used. - * - */ - public LeshanServerBuilder setRegistrationStore(CaliforniumRegistrationStore registrationStore) { - this.registrationStore = registrationStore; - return this; - } - - /** - *

- * Set your {@link SecurityStore} implementation which stores {@link SecurityInfo}. - *

- * By default no security store is set. It is needed for secured connection if you are using the defaultAuthorizer - * or if you want PSK feature activated. An {@link InMemorySecurityStore} is provided to start using secured - * connection. - * - */ - public LeshanServerBuilder setSecurityStore(SecurityStore securityStore) { - this.securityStore = securityStore; - return this; - } - - /** - *

- * Set your {@link Authorizer} implementation to define if a device if authorize to register to this server. - *

- * By default the {@link DefaultAuthorizer} implementation is used, it needs a security store to accept secured - * connection. - */ - public LeshanServerBuilder setAuthorizer(Authorizer authorizer) { - this.authorizer = authorizer; - return this; - } - - /** - *

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

- * By default the {@link StandardModelProvider}. - */ - public LeshanServerBuilder setObjectModelProvider(LwM2mModelProvider objectModelProvider) { - this.modelProvider = objectModelProvider; - return this; - } - - /** - *

- * Set the {@link PublicKey} of the server which will be used for RawPublicKey DTLS authentication. - *

- * This should be used for RPK support only. If you support RPK and X509, - * {@link LeshanServerBuilder#setCertificateChain(X509Certificate[])} should be used. - */ - public LeshanServerBuilder setPublicKey(PublicKey publicKey) { - this.publicKey = publicKey; - return this; - } - - /** - * Set the {@link PrivateKey} of the server which will be used for RawPublicKey(RPK) and X509 DTLS authentication. - */ - public LeshanServerBuilder setPrivateKey(PrivateKey privateKey) { - this.privateKey = privateKey; - return this; - } - - /** - *

- * Set the CertificateChain of the server which will be used for X509 DTLS authentication. - *

- * For RPK the public key will be extract from the first X509 certificate of the certificate chain. If you only need - * RPK support, use {@link LeshanServerBuilder#setPublicKey(PublicKey)} instead. - */ - public LeshanServerBuilder setCertificateChain(T[] certificateChain) { - this.certificateChain = certificateChain; - return this; - } - - /** - * The list of trusted certificates used to authenticate devices. - */ - public LeshanServerBuilder setTrustedCertificates(T[] trustedCertificates) { - this.trustedCertificates = trustedCertificates; - 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 LeshanServerBuilder 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 LeshanServerBuilder setDecoder(LwM2mDecoder decoder) { - this.decoder = decoder; - return this; - } - - /** - * Set the CoRE Link parser {@link LwM2mLinkParser} - *

- * By default the {@link DefaultLwM2mLinkParser} is used. - */ - public void setLinkParser(LwM2mLinkParser linkParser) { - this.linkParser = linkParser; - } - - /** - * Set the Californium/CoAP {@link Configuration}. - *

- * This is strongly recommended to create the {@link Configuration} with {@link #createDefaultCoapConfiguration()} - * before to modify it. - * - */ - public LeshanServerBuilder setCoapConfig(Configuration config) { - this.coapConfig = config; - return this; - } - - /** - * Set the Scandium/DTLS Configuration : {@link Builder}. - */ - public LeshanServerBuilder setDtlsConfig(DtlsConnectorConfig.Builder config) { - this.dtlsConfigBuilder = config; - 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 LeshanServerBuilder setEndpointFactory(EndpointFactory endpointFactory) { - this.endpointFactory = endpointFactory; - return this; - } - - /** - * deactivate unsecured CoAP endpoint - */ - public LeshanServerBuilder disableUnsecuredEndpoint() { - this.noUnsecuredEndpoint = true; - return this; - } - - /** - * deactivate secured CoAP endpoint (DTLS) - */ - public LeshanServerBuilder disableSecuredEndpoint() { - this.noSecuredEndpoint = true; - return this; - } - - /** - * deactivate PresenceService which tracks presence of devices using LWM2M Queue Mode. When Queue Mode is - * deactivated request is always sent immediately and {@link ClientSleepingException} will never be raised. - * Deactivate QueueMode can make sense if you want to handle it on your own or if you don't plan to support devices - * with queue mode. - */ - public LeshanServerBuilder disableQueueModeSupport() { - this.noQueueMode = true; - return this; - } - - /** - * Sets a new {@link ClientAwakeTimeProvider} object different from the default one. - *

- * By default a {@link StaticClientAwakeTimeProvider} will be used initialized with the - * MAX_TRANSMIT_WAIT value available in CoAP {@link Configuration} which should be by default 93s as - * defined in RFC7252. - * - * @param awakeTimeProvider the {@link ClientAwakeTimeProvider} to set. - */ - public LeshanServerBuilder setClientAwakeTimeProvider(ClientAwakeTimeProvider awakeTimeProvider) { - this.awakeTimeProvider = awakeTimeProvider; - return this; - } - - /** - * Sets a new {@link RegistrationIdProvider} object different from the default one (Random string). - * - * @param registrationIdProvider the {@link RegistrationIdProvider} to set. - */ - public void setRegistrationIdProvider(RegistrationIdProvider registrationIdProvider) { - this.registrationIdProvider = registrationIdProvider; - } - - /** - * Update Registration on notification. - *

- * There is some use cases where device can have a dynamic IP (E.g. NAT environment), the specification says to use - * an UPDATE request to notify server about IP address/ port changes. But it seems there is some rare use case where - * this update REQUEST can not be done. - *

- * With this option you can allow Leshan to update Registration on observe notification. This is clearly OUT OF - * SPECIFICATION and so this is not recommended and should be used only if there is no other way. - * - * For {@code coap://} you probably need to use a the Relaxed response matching mode. - * - *

-     * coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED");
-     * 
- * - * @since 1.1 - * - * @see Dynamic - * IP environnement documentaiton - */ - public LeshanServerBuilder setUpdateRegistrationOnNotification(boolean updateRegistrationOnNotification) { - this.updateRegistrationOnNotification = updateRegistrationOnNotification; - return this; - } - - /** - * Enable EXPERIMENTAL OSCORE feature. - *

- * By default OSCORE is not enabled. - */ - public LeshanServerBuilder setEnableOscore(boolean enableOscore) { - this.enableOscore = enableOscore; - return this; - } - - /** - * The default Californium/CoAP {@link Configuration} used by the builder. - */ - public static Configuration createDefaultCoapConfiguration() { - Configuration networkConfig = new Configuration(CoapConfig.DEFINITIONS, DtlsConfig.DEFINITIONS, - UdpConfig.DEFINITIONS, SystemConfig.DEFINITIONS); - networkConfig.set(CoapConfig.MID_TRACKER, TrackerMode.NULL); - // Do no allow Server to initiated Handshake by default, for U device request will be allowed to initiate - // handshake (see Registration.shouldInitiateConnection()) - networkConfig.set(DtlsConfig.DTLS_DEFAULT_HANDSHAKE_MODE, DtlsEndpointContext.HANDSHAKE_MODE_NONE); - networkConfig.set(DtlsConfig.DTLS_ROLE, DtlsRole.BOTH); - - return networkConfig; - } - - /** - * Create the {@link LeshanServer}. - *

- * Next step will be to start it : {@link LeshanServer#start()}. - * - * @return the LWM2M server. - * @throws IllegalStateException if builder configuration is not consistent. - */ - public LeshanServer build() { - if (localAddress == null) - localAddress = new InetSocketAddress(LwM2m.DEFAULT_COAP_PORT); - if (registrationStore == null) - registrationStore = new InMemoryRegistrationStore(); - if (authorizer == null) - authorizer = new DefaultAuthorizer(securityStore); - if (modelProvider == null) - modelProvider = new StandardModelProvider(); - if (encoder == null) - encoder = new DefaultLwM2mEncoder(); - if (decoder == null) - decoder = new DefaultLwM2mDecoder(); - if (linkParser == null) - linkParser = new DefaultLwM2mLinkParser(); - if (coapConfig == null) - coapConfig = createDefaultCoapConfiguration(); - if (awakeTimeProvider == null) { - int maxTransmitWait = coapConfig.getTimeAsInt(CoapConfig.MAX_TRANSMIT_WAIT, TimeUnit.MILLISECONDS); - if (maxTransmitWait == 0) { - LOG.warn( - "No value available for MAX_TRANSMIT_WAIT in CoAP NetworkConfig. Fallback with a default 93s value."); - awakeTimeProvider = new StaticClientAwakeTimeProvider(); - } else { - awakeTimeProvider = new StaticClientAwakeTimeProvider(maxTransmitWait); - } - } - if (registrationIdProvider == null) - registrationIdProvider = new RandomStringRegistrationIdProvider(); - if (endpointFactory == null) { - endpointFactory = new DefaultEndpointFactory("LWM2M Server", false); - } - - // 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 LwM2mPskStore(this.securityStore, registrationStore)); - } - } - - // Handle secure address - if (incompleteConfig.getAddress() == null) { - if (localSecureAddress == null) { - localSecureAddress = new InetSocketAddress(LwM2m.DEFAULT_COAP_SECURE_PORT); - } - dtlsConfigBuilder.setAddress(localSecureAddress); - } else if (localSecureAddress != null && !localSecureAddress.equals(incompleteConfig.getAddress())) { - throw new IllegalStateException(String.format( - "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for secure address: %s != %s", - localSecureAddress, 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; - OscoreContextCleaner oscoreCtxCleaner = null; - if (enableOscore) { - LOG.warn("Experimental OSCORE feature is enabled."); - if (securityStore != null) { - oscoreCtxDB = new InMemoryOscoreContextDB(new LwM2mOscoreStore(securityStore, registrationStore)); - oscoreCtxCleaner = new OscoreContextCleaner(oscoreCtxDB); - } - } - - // create endpoints - CoapEndpoint unsecuredEndpoint = null; - if (!noUnsecuredEndpoint) { - unsecuredEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, registrationStore, - oscoreCtxDB); - } - - CoapEndpoint securedEndpoint = null; - if (!noSecuredEndpoint && dtlsConfig != null) { - securedEndpoint = endpointFactory.createSecuredEndpoint(dtlsConfig, coapConfig, registrationStore, null); - } - - if (securedEndpoint == null && unsecuredEndpoint == null) { - throw new IllegalStateException( - "All CoAP enpoints are deactivated, at least one endpoint should be activated"); - } - - LeshanServer server = createServer(unsecuredEndpoint, securedEndpoint, registrationStore, securityStore, - authorizer, modelProvider, encoder, decoder, coapConfig, noQueueMode, awakeTimeProvider, - registrationIdProvider, linkParser); - - if (oscoreCtxCleaner != null) { - server.getRegistrationService().addListener(oscoreCtxCleaner); - if (securityStore instanceof EditableSecurityStore) { - ((EditableSecurityStore) securityStore).addListener(oscoreCtxCleaner); - } - } - - return server; - } - - /** - * @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 LeshanServer. - *

- * You can extend LeshanServerBuilder and override this method to create a new builder which will be - * able to build an extended LeshanServer. - * - * @param unsecuredEndpoint CoAP endpoint used for coap:// communication. - * @param securedEndpoint CoAP endpoint used for coaps:// communication. - * @param registrationStore the {@link Registration} store. - * @param securityStore the {@link SecurityInfo} store. - * @param authorizer define which devices is allow to register on this server. - * @param modelProvider provides the objects description for each client. - * @param decoder decoder used to decode response payload. - * @param encoder encode used to encode request payload. - * @param coapConfig the CoAP {@link Configuration}. - * @param noQueueMode true to disable presenceService. - * @param awakeTimeProvider to set the client awake time if queue mode is used. - * @param registrationIdProvider to provide registrationId using for location-path option values on response of - * Register operation. - * @param linkParser a parser {@link LwM2mLinkParser} used to parse a CoRE Link. - * - * @return the LWM2M server - */ - protected LeshanServer createServer(CoapEndpoint unsecuredEndpoint, CoapEndpoint securedEndpoint, - CaliforniumRegistrationStore registrationStore, SecurityStore securityStore, Authorizer authorizer, - LwM2mModelProvider modelProvider, LwM2mEncoder encoder, LwM2mDecoder decoder, Configuration coapConfig, - boolean noQueueMode, ClientAwakeTimeProvider awakeTimeProvider, - RegistrationIdProvider registrationIdProvider, LwM2mLinkParser linkParser) { - return new LeshanServer(unsecuredEndpoint, securedEndpoint, registrationStore, securityStore, authorizer, - modelProvider, encoder, decoder, coapConfig, noQueueMode, awakeTimeProvider, registrationIdProvider, - updateRegistrationOnNotification, linkParser); - } -} 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 df7b0e9bc7..389a3424f3 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 @@ -46,7 +46,7 @@ public class BootstrapResource extends LwM2mCoapResource { private final BootstrapHandler bootstrapHandler; public BootstrapResource(BootstrapHandler handler) { - super("bs"); + super("bs", null); bootstrapHandler = handler; } diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpoint.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpoint.java new file mode 100644 index 0000000000..f47f39e488 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpoint.java @@ -0,0 +1,276 @@ +/******************************************************************************* + * 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.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.coap.Token; +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.observation.Observation; +import org.eclipse.leshan.core.request.DownlinkRequest; +import org.eclipse.leshan.core.response.ErrorCallback; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.ObserveCompositeResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.core.response.ResponseCallback; +import org.eclipse.leshan.core.util.Validate; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint; +import org.eclipse.leshan.server.endpoint.ServerEndpointToolbox; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.profile.ClientProfile; +import org.eclipse.leshan.server.request.LowerLayerConfig; + +public class CaliforniumServerEndpoint implements LwM2mServerEndpoint { + + private final Protocol protocol; + private final ScheduledExecutorService executor; + private final CoapEndpoint endpoint; + private final ServerEndpointToolbox toolbox; + private final ServerCoapMessageTranslator translator; + private final LwM2mNotificationReceiver notificationReceiver; + 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 CaliforniumServerEndpoint(Protocol protocol, CoapEndpoint endpoint, ServerCoapMessageTranslator translator, + ServerEndpointToolbox toolbox, LwM2mNotificationReceiver notificationReceiver, + IdentityHandler identityHandler, ExceptionTranslator exceptionTranslator, + ScheduledExecutorService executor) { + this.protocol = protocol; + this.translator = translator; + this.toolbox = toolbox; + this.endpoint = endpoint; + this.notificationReceiver = notificationReceiver; + 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(ClientProfile destination, DownlinkRequest lwm2mRequest, + LowerLayerConfig lowerLayerConfig, long timeoutInMs) throws InterruptedException { + // Create the CoAP request from LwM2m request + final Request coapRequest = translator.createCoapRequest(destination, lwm2mRequest, toolbox, identityHandler); + + // Apply customSetting + if (lowerLayerConfig != null) { + lowerLayerConfig.apply(coapRequest); + } + + // Send CoAP request synchronously + SyncRequestObserver syncMessageObserver = new SyncRequestObserver(coapRequest, timeoutInMs, + exceptionTranslator) { + @Override + public T buildResponse(Response coapResponse) { + // Build LwM2m response + T lwM2mResponse = translator.createLwM2mResponse(destination, lwm2mRequest, coapRequest, coapResponse, + toolbox); + // Handle special observe case + if (lwM2mResponse != null && lwM2mResponse.isSuccess()) { + Observation observation = null; + if (lwM2mResponse instanceof ObserveResponse) { + observation = ((ObserveResponse) lwM2mResponse).getObservation(); + } else if (lwM2mResponse instanceof ObserveCompositeResponse) { + observation = ((ObserveCompositeResponse) lwM2mResponse).getObservation(); + } + if (observation != null) { + notificationReceiver.newObservation(observation, destination.getRegistration()); + } + } + return lwM2mResponse; + } + }; + coapRequest.addMessageObserver(syncMessageObserver); + + // Store pending request to be able to cancel it later + addOngoingRequest(destination.getRegistrationId(), coapRequest); + + // Send CoAP request asynchronously + endpoint.sendRequest(coapRequest); + + // Wait for response, then return it + return syncMessageObserver.waitForResponse(); + } + + @Override + public void send(ClientProfile destination, DownlinkRequest lwm2mRequest, + ResponseCallback responseCallback, ErrorCallback errorCallback, LowerLayerConfig lowerLayerConfig, + long timeoutInMs) { + Validate.notNull(responseCallback); + Validate.notNull(errorCallback); + + // Create the CoAP request from LwM2m request + final Request coapRequest = translator.createCoapRequest(destination, lwm2mRequest, toolbox, identityHandler); + + // Apply customSetting + if (lowerLayerConfig != null) { + lowerLayerConfig.apply(coapRequest); + } + + // Add CoAP request callback + MessageObserver obs = new AsyncRequestObserver(coapRequest, responseCallback, errorCallback, timeoutInMs, + executor, exceptionTranslator) { + @Override + public T buildResponse(Response coapResponse) { + // Build LwM2m response + T lwM2mResponse = translator.createLwM2mResponse(destination, lwm2mRequest, coapRequest, coapResponse, + toolbox); + // Handle special observe case + if (lwM2mResponse != null && lwM2mResponse.isSuccess()) { + Observation observation = null; + if (lwM2mResponse instanceof ObserveResponse) { + observation = ((ObserveResponse) lwM2mResponse).getObservation(); + } else if (lwM2mResponse instanceof ObserveCompositeResponse) { + observation = ((ObserveCompositeResponse) lwM2mResponse).getObservation(); + } + if (observation != null) { + notificationReceiver.newObservation(observation, destination.getRegistration()); + } + } + return lwM2mResponse; + } + }; + coapRequest.addMessageObserver(obs); + + // Store pending request to be able to cancel it later + addOngoingRequest(destination.getRegistrationId(), coapRequest); + + // Send CoAP request asynchronously + endpoint.sendRequest(coapRequest); + } + + @Override + public void cancelObservation(Observation observation) { + endpoint.cancelObservation(new Token(observation.getId().getBytes())); + } + + /** + * 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/endpoint/CaliforniumServerEndpointFactory.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpointFactory.java new file mode 100644 index 0000000000..c1cb1d4ab5 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpointFactory.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.californium.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.LeshanServer; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.security.ServerSecurityInfo; + +public interface CaliforniumServerEndpointFactory { + + Protocol getProtocol(); + + URI getUri(); + + CoapEndpoint createCoapEndpoint(Configuration defaultCaliforniumConfiguration, + ServerSecurityInfo serverSecurityInfo, LwM2mNotificationReceiver notificationReceiver, LeshanServer server); + + IdentityHandler createIdentityHandler(); + + ExceptionTranslator createExceptionTranslator(); +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpointsProvider.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpointsProvider.java new file mode 100644 index 0000000000..011dbd6521 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/CaliforniumServerEndpointsProvider.java @@ -0,0 +1,303 @@ +/******************************************************************************* + * 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.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.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.core.observe.NotificationListener; +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.ObserveUtil; +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.observation.CompositeObservation; +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; +import org.eclipse.leshan.core.observation.SingleObservation; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.response.AbstractLwM2mResponse; +import org.eclipse.leshan.core.response.ObserveCompositeResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.core.util.NamedThreadFactory; +import org.eclipse.leshan.server.LeshanServer; +import org.eclipse.leshan.server.californium.RootResource; +import org.eclipse.leshan.server.californium.endpoint.coap.CoapServerProtocolProvider; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpointsProvider; +import org.eclipse.leshan.server.endpoint.ServerEndpointToolbox; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.profile.ClientProfile; +import org.eclipse.leshan.server.request.UplinkRequestReceiver; +import org.eclipse.leshan.server.security.ServerSecurityInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CaliforniumServerEndpointsProvider implements LwM2mServerEndpointsProvider { + + // TODO TL : provide a COAP/Californium API ? like previous LeshanServer.coapAPI() + + private final Logger LOG = LoggerFactory.getLogger(CaliforniumServerEndpointsProvider.class); + + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, + new NamedThreadFactory("Leshan Async Request timeout")); + + private final Configuration serverConfig; + private final List endpointsFactory; + private final ServerCoapMessageTranslator messagetranslator = new ServerCoapMessageTranslator(); + private final List endpoints; + private CoapServer coapServer; + + public CaliforniumServerEndpointsProvider() { + this(new Builder().generateDefaultValue()); + } + + protected CaliforniumServerEndpointsProvider(Builder builder) { + this.serverConfig = builder.serverConfiguration; + this.endpointsFactory = builder.endpointsFactory; + this.endpoints = new ArrayList(); + } + + @Override + public List getEndpoints() { + return Collections.unmodifiableList(endpoints); + } + + @Override + public LwM2mServerEndpoint getEndpoint(URI uri) { + for (CaliforniumServerEndpoint endpoint : endpoints) { + if (endpoint.getURI().equals(uri)) + return endpoint; + } + return null; + } + + @Override + public void createEndpoints(UplinkRequestReceiver requestReceiver, LwM2mNotificationReceiver notificatonReceiver, + ServerEndpointToolbox toolbox, ServerSecurityInfo serverSecurityInfo, LeshanServer 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 (CaliforniumServerEndpointFactory endpointFactory : endpointsFactory) { + // create Californium endpoint + CoapEndpoint coapEndpoint = endpointFactory.createCoapEndpoint(serverConfig, serverSecurityInfo, + notificatonReceiver, 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 + CaliforniumServerEndpoint lwm2mEndpoint = new CaliforniumServerEndpoint(endpointFactory.getProtocol(), + coapEndpoint, messagetranslator, toolbox, notificatonReceiver, identityHandler, + exceptionTranslator, executor); + endpoints.add(lwm2mEndpoint); + + // add Californium endpoint to coap server + coapServer.addEndpoint(coapEndpoint); + + // add NotificationListener + coapEndpoint.addNotificationListener(new NotificationListener() { + + @Override + public void onNotification(Request coapRequest, Response coapResponse) { + // Get Observation + String regid = coapRequest.getUserContext().get(ObserveUtil.CTX_REGID); + Observation observation = server.getRegistrationStore().getObservation(regid, + new ObservationIdentifier(coapResponse.getToken().getBytes())); + if (observation == null) { + LOG.error("Unexpected error: Unable to find observation with token {} for registration {}", + coapResponse.getToken(), regid); + return; + } + // Get profile + Identity identity = identityHandler.getIdentity(coapResponse); + ClientProfile profile = toolbox.getProfileProvider().getProfile(identity); + + // create Observe Response + try { + AbstractLwM2mResponse response = messagetranslator.createObservation(observation, + coapResponse, toolbox, profile); + if (observation instanceof SingleObservation) { + notificatonReceiver.onNotification((SingleObservation) observation, profile, + (ObserveResponse) response); + } else if (observation instanceof CompositeObservation) { + notificatonReceiver.onNotification((CompositeObservation) observation, profile, + (ObserveCompositeResponse) response); + } + } catch (Exception e) { + notificatonReceiver.onError(observation, profile, e); + } + + } + }); + } + } + + // 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(ServerProtocolProvider... protocolProviders) { + // TODO TL : handle duplicate ? + this.protocolProviders = new ArrayList(); + if (protocolProviders.length == 0) { + this.protocolProviders.add(new CoapServerProtocolProvider()); + } 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 (ServerProtocolProvider protocolProvider : protocolProviders) { + moduleProviders.addAll(protocolProvider.getModuleDefinitionsProviders()); + } + + // create Californium Configuration + Configuration configuration = new Configuration( + moduleProviders.toArray(new ModuleDefinitionsProvider[moduleProviders.size()])); + + // apply default value + for (ServerProtocolProvider 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 (ServerProtocolProvider 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(CaliforniumServerEndpointFactory endpointFactory) { + // TODO TL: handle duplicate addr + endpointsFactory.add(endpointFactory); + return this; + } + + protected Builder generateDefaultValue() { + if (serverConfiguration == null) { + serverConfiguration = createDefaultConfiguration(); + } + + if (endpointsFactory.isEmpty()) { + for (ServerProtocolProvider protocolProvider : protocolProviders) { + // TODO TL : handle duplicates + endpointsFactory.add(protocolProvider + .createDefaultEndpointFactory(protocolProvider.getDefaultUri(serverConfiguration))); + } + } + return this; + } + + public CaliforniumServerEndpointsProvider build() { + generateDefaultValue(); + return new CaliforniumServerEndpointsProvider(this); + } + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/ServerCoapMessageTranslator.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/ServerCoapMessageTranslator.java new file mode 100644 index 0000000000..dd927f2a3e --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/ServerCoapMessageTranslator.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * 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.endpoint; + +import static org.eclipse.leshan.core.californium.ResponseCodeUtil.toLwM2mResponseCode; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.eclipse.californium.core.coap.CoAP; +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.ResponseCode; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; +import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.TimestampedLwM2mNode; +import org.eclipse.leshan.core.node.codec.CodecException; +import org.eclipse.leshan.core.observation.CompositeObservation; +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.SingleObservation; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.DownlinkRequest; +import org.eclipse.leshan.core.request.exception.InvalidResponseException; +import org.eclipse.leshan.core.response.AbstractLwM2mResponse; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.ObserveCompositeResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.server.californium.registration.RegisterResource; +import org.eclipse.leshan.server.californium.request.CoapRequestBuilder; +import org.eclipse.leshan.server.californium.request.LwM2mResponseBuilder; +import org.eclipse.leshan.server.californium.send.SendResource; +import org.eclipse.leshan.server.endpoint.ServerEndpointToolbox; +import org.eclipse.leshan.server.profile.ClientProfile; +import org.eclipse.leshan.server.request.UplinkRequestReceiver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ServerCoapMessageTranslator { + + private final Logger LOG = LoggerFactory.getLogger(ServerCoapMessageTranslator.class); + + public Request createCoapRequest(ClientProfile clientProfile, DownlinkRequest lwm2mRequest, + ServerEndpointToolbox toolbox, IdentityHandler identityHandler) { + CoapRequestBuilder builder = new CoapRequestBuilder(clientProfile.getIdentity(), clientProfile.getRootPath(), + clientProfile.getRegistrationId(), clientProfile.getEndpoint(), clientProfile.getModel(), + toolbox.getEncoder(), clientProfile.canInitiateConnection(), null, identityHandler); + lwm2mRequest.accept(builder); + return builder.getRequest(); + } + + public T createLwM2mResponse(ClientProfile clientProfile, DownlinkRequest lwm2mRequest, + Request coapRequest, Response coapResponse, ServerEndpointToolbox toolbox) { + + LwM2mResponseBuilder builder = new LwM2mResponseBuilder(coapRequest, coapResponse, + clientProfile.getEndpoint(), clientProfile.getModel(), toolbox.getDecoder(), toolbox.getLinkParser()); + lwm2mRequest.accept(builder); + return builder.getResponse(); + } + + public List createResources(UplinkRequestReceiver receiver, ServerEndpointToolbox toolbox, + IdentityHandlerProvider identityHandlerProvider) { + return Arrays.asList( // + (Resource) new RegisterResource(receiver, toolbox.getLinkParser(), identityHandlerProvider), // + (Resource) new SendResource(receiver, toolbox.getDecoder(), toolbox.getProfileProvider(), + identityHandlerProvider)); + } + + public AbstractLwM2mResponse createObservation(Observation observation, Response coapResponse, + ServerEndpointToolbox toolbox, ClientProfile profile) { + // CHANGED response is supported for backward compatibility with old spec. + if (coapResponse.getCode() != CoAP.ResponseCode.CHANGED + && coapResponse.getCode() != CoAP.ResponseCode.CONTENT) { + throw new InvalidResponseException("Unexpected response code [%s] for %s", coapResponse.getCode(), + observation); + } + + // get content format + ContentFormat contentFormat = null; + if (coapResponse.getOptions().hasContentFormat()) { + contentFormat = ContentFormat.fromCode(coapResponse.getOptions().getContentFormat()); + } + + // decode response + try { + ResponseCode responseCode = toLwM2mResponseCode(coapResponse.getCode()); + + if (observation instanceof SingleObservation) { + SingleObservation singleObservation = (SingleObservation) observation; + + List timestampedNodes = toolbox.getDecoder().decodeTimestampedData( + coapResponse.getPayload(), contentFormat, singleObservation.getPath(), profile.getModel()); + + // create lwm2m response + if (timestampedNodes.size() == 1 && !timestampedNodes.get(0).isTimestamped()) { + return new ObserveResponse(responseCode, timestampedNodes.get(0).getNode(), null, singleObservation, + null, coapResponse); + } else { + return new ObserveResponse(responseCode, null, timestampedNodes, singleObservation, null, + coapResponse); + } + } else if (observation instanceof CompositeObservation) { + + CompositeObservation compositeObservation = (CompositeObservation) observation; + + Map nodes = toolbox.getDecoder().decodeNodes(coapResponse.getPayload(), + contentFormat, compositeObservation.getPaths(), profile.getModel()); + + return new ObserveCompositeResponse(responseCode, nodes, null, coapResponse, compositeObservation); + } + + throw new IllegalStateException( + "observation must be a CompositeObservation or a SingleObservation but was " + observation == null + ? null + : observation.getClass().getSimpleName()); + } catch (CodecException e) { + if (LOG.isDebugEnabled()) { + byte[] payload = coapResponse.getPayload() == null ? new byte[0] : coapResponse.getPayload(); + LOG.debug(String.format("Unable to decode notification payload [%s] of observation [%s] ", + Hex.encodeHexString(payload), observation), e); + } + throw new InvalidResponseException(e, "Unable to decode notification payload of observation [%s] ", + observation); + } + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/ServerProtocolProvider.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/ServerProtocolProvider.java new file mode 100644 index 0000000000..c5a2d09a27 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/ServerProtocolProvider.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.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 ServerProtocolProvider { + + Protocol getProtocol(); + + List getModuleDefinitionsProviders(); + + void applyDefaultValue(Configuration configuration); + + CaliforniumServerEndpointFactory createDefaultEndpointFactory(URI uri); + + URI getDefaultUri(Configuration configuration); +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coap/CoapOscoreServerEndpointFactory.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coap/CoapOscoreServerEndpointFactory.java new file mode 100644 index 0000000000..d8ee8c2c92 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coap/CoapOscoreServerEndpointFactory.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * 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.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.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.LeshanServer; +import org.eclipse.leshan.server.californium.LwM2mOscoreStore; +import org.eclipse.leshan.server.californium.OscoreContextCleaner; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.security.EditableSecurityStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CoapOscoreServerEndpointFactory extends CoapServerEndpointFactory { + + private static final Logger LOG = LoggerFactory.getLogger(CoapOscoreServerEndpointFactory.class); + + public CoapOscoreServerEndpointFactory(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, + LwM2mNotificationReceiver notificationReceiver, LeshanServer server) { + CoapEndpoint.Builder builder = super.createEndpointBuilder(address, coapConfig, notificationReceiver, server); + + // handle oscore + if (server.getSecurityStore() != null) { + InMemoryOscoreContextDB oscoreCtxDB = new InMemoryOscoreContextDB( + new LwM2mOscoreStore(server.getSecurityStore(), server.getRegistrationStore())); + builder.setCustomCoapStackArgument(oscoreCtxDB).setCoapStackFactory(new OSCoreCoapStackFactory()); + + OscoreContextCleaner oscoreCtxCleaner = new OscoreContextCleaner(oscoreCtxDB); + server.getRegistrationService().addListener(oscoreCtxCleaner); + + if (server.getSecurityStore() instanceof EditableSecurityStore) { + ((EditableSecurityStore) server.getSecurityStore()).addListener(oscoreCtxCleaner); + } + } + + 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/endpoint/coap/CoapServerEndpointFactory.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coap/CoapServerEndpointFactory.java new file mode 100644 index 0000000000..46567e2f84 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coap/CoapServerEndpointFactory.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.server.californium.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.core.network.serialization.UdpDataParser; +import org.eclipse.californium.core.network.serialization.UdpDataSerializer; +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.LeshanServer; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointFactory; +import org.eclipse.leshan.server.californium.observation.LwM2mObservationStore; +import org.eclipse.leshan.server.californium.observation.ObservationSerDes; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.security.ServerSecurityInfo; + +public class CoapServerEndpointFactory implements CaliforniumServerEndpointFactory { + + protected final String loggingTagPrefix; + protected URI endpointUri = null; + + public CoapServerEndpointFactory(URI uri) { + this(uri, "LWM2M Server"); + } + + public CoapServerEndpointFactory(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, + LwM2mNotificationReceiver notificationReceiver, LeshanServer server) { + return createEndpointBuilder(EndpointUriUtil.getSocketAddr(endpointUri), defaultConfiguration, + notificationReceiver, 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, + LwM2mNotificationReceiver notificationReceiver, LeshanServer server) { + CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); + + builder.setConnector(createConnector(address, coapConfig)); + builder.setConfiguration(coapConfig); + builder.setLoggingTag(getLoggingTag()); + + builder.setObservationStore(new LwM2mObservationStore(server.getRegistrationStore(), notificationReceiver, + new ObservationSerDes(new UdpDataParser(), new UdpDataSerializer()))); + 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/endpoint/coap/CoapServerProtocolProvider.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coap/CoapServerProtocolProvider.java new file mode 100644 index 0000000000..38a7bb39a0 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coap/CoapServerProtocolProvider.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.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.endpoint.CaliforniumServerEndpointFactory; +import org.eclipse.leshan.server.californium.endpoint.ServerProtocolProvider; + +public class CoapServerProtocolProvider implements ServerProtocolProvider { + + @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 CaliforniumServerEndpointFactory createDefaultEndpointFactory(URI uri) { + return new CoapServerEndpointFactory(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/endpoint/coaps/CoapsServerEndpointFactory.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coaps/CoapsServerEndpointFactory.java new file mode 100644 index 0000000000..f0c416415d --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coaps/CoapsServerEndpointFactory.java @@ -0,0 +1,346 @@ +/******************************************************************************* + * 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.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.core.network.serialization.UdpDataParser; +import org.eclipse.californium.core.network.serialization.UdpDataSerializer; +import org.eclipse.californium.core.observe.ObservationStore; +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.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.LeshanServer; +import org.eclipse.leshan.server.californium.ConnectionCleaner; +import org.eclipse.leshan.server.californium.LwM2mPskStore; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointFactory; +import org.eclipse.leshan.server.californium.observation.LwM2mObservationStore; +import org.eclipse.leshan.server.californium.observation.ObservationSerDes; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.security.EditableSecurityStore; +import org.eclipse.leshan.server.security.SecurityInfo; +import org.eclipse.leshan.server.security.SecurityStore; +import org.eclipse.leshan.server.security.SecurityStoreListener; +import org.eclipse.leshan.server.security.ServerSecurityInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CoapsServerEndpointFactory implements CaliforniumServerEndpointFactory { + + private static final Logger LOG = LoggerFactory.getLogger(CoapsServerEndpointFactory.class); + + protected final String loggingTagPrefix; + protected URI endpointUri = null; + + public CoapsServerEndpointFactory(URI uri) { + this(uri, "LWM2M Server"); + } + + public CoapsServerEndpointFactory(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, + LwM2mNotificationReceiver notificationReceiver, LeshanServer 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 LWM2M Observation Store + LwM2mObservationStore observationStore = createObservationStore(server, notificationReceiver); + + // create CoAP endpoint + CoapEndpoint endpoint = createEndpointBuilder(dtlsConfig, defaultConfiguration, observationStore).build(); + + // create DTLS connection cleaner + createConnectionCleaner(server.getSecurityStore(), endpoint); + return endpoint; + } + + protected DtlsConnectorConfig.Builder createDtlsConnectorConfigBuilder(Configuration endpointConfiguration) { + return new DtlsConnectorConfig.Builder(endpointConfiguration); + } + + protected void setUpDtlsConfig(DtlsConnectorConfig.Builder dtlsConfigBuilder, InetSocketAddress address, + ServerSecurityInfo serverSecurityInfo, LeshanServer 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 LwM2mPskStore(server.getSecurityStore(), server.getRegistrationStore())); + } + } + + // 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 Endpoint Factory and DtlsConnectorConfig.Builder for 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()); + } + } + + protected LwM2mObservationStore createObservationStore(LeshanServer server, + LwM2mNotificationReceiver notificationReceiver) { + return new LwM2mObservationStore(server.getRegistrationStore(), notificationReceiver, + new ObservationSerDes(new UdpDataParser(), new UdpDataSerializer())); + } + + /** + * This method is intended to be overridden. + * + * @param dtlsConfig the DTLS config used to create the DTLS Connector. + * @param endpointConfiguration the config used to create this endpoint. + * @param store the CoAP observation store used to create this endpoint. + * @return the {@link Builder} used for secured communication. + */ + protected CoapEndpoint.Builder createEndpointBuilder(DtlsConnectorConfig dtlsConfig, + Configuration endpointConfiguration, ObservationStore store) { + CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); + + builder.setConnector(createConnector(dtlsConfig)); + builder.setConfiguration(endpointConfiguration); + builder.setLoggingTag(getLoggingTag()); + builder.setEndpointContextMatcher(createEndpointContextMatcher()); + builder.setObservationStore(store); + + return builder; + } + + 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); + } + + protected void createConnectionCleaner(SecurityStore securityStore, CoapEndpoint securedEndpoint) { + if (securedEndpoint != null && securedEndpoint.getConnector() instanceof DTLSConnector + && securityStore instanceof EditableSecurityStore) { + + final ConnectionCleaner connectionCleaner = new ConnectionCleaner( + (DTLSConnector) securedEndpoint.getConnector()); + + ((EditableSecurityStore) securityStore).addListener(new SecurityStoreListener() { + @Override + public void securityInfoRemoved(boolean infosAreCompromised, SecurityInfo... infos) { + if (infosAreCompromised) { + connectionCleaner.cleanConnectionFor(infos); + } + } + }); + } + } + + @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/endpoint/coaps/CoapsServerProtocolProvider.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coaps/CoapsServerProtocolProvider.java new file mode 100644 index 0000000000..fe76fc1c0e --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/endpoint/coaps/CoapsServerProtocolProvider.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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.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.DtlsEndpointContext; +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.endpoint.CaliforniumServerEndpointFactory; +import org.eclipse.leshan.server.californium.endpoint.ServerProtocolProvider; + +public class CoapsServerProtocolProvider implements ServerProtocolProvider { + + @Override + public Protocol getProtocol() { + return Protocol.COAPS; + } + + @Override + public void applyDefaultValue(Configuration configuration) { + configuration.set(CoapConfig.MID_TRACKER, TrackerMode.NULL); + // Do no allow Server to initiated Handshake by default, for U device request will be allowed to initiate + // handshake (see Registration.shouldInitiateConnection()) + configuration.set(DtlsConfig.DTLS_DEFAULT_HANDSHAKE_MODE, DtlsEndpointContext.HANDSHAKE_MODE_NONE); + configuration.set(DtlsConfig.DTLS_ROLE, DtlsRole.BOTH); + } + + @Override + public List getModuleDefinitionsProviders() { + return Arrays.asList(SystemConfig.DEFINITIONS, CoapConfig.DEFINITIONS, UdpConfig.DEFINITIONS, + DtlsConfig.DEFINITIONS); + } + + @Override + public CaliforniumServerEndpointFactory createDefaultEndpointFactory(URI uri) { + return new CoapsServerEndpointFactory(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-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/EndpointContextSerDes.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/EndpointContextSerDes.java similarity index 99% rename from leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/EndpointContextSerDes.java rename to leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/EndpointContextSerDes.java index 2e785e9adc..18a1b9ba90 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/EndpointContextSerDes.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/EndpointContextSerDes.java @@ -14,7 +14,7 @@ * Achim Kraus (Bosch Software Innovations GmbH) - initial implementation. * Orange - keep one JSON dependency ******************************************************************************/ -package org.eclipse.leshan.server.redis.serialization; +package org.eclipse.leshan.server.californium.observation; import java.net.InetSocketAddress; import java.security.KeyFactory; diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/LwM2mObservationStore.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/LwM2mObservationStore.java new file mode 100644 index 0000000000..801946fb36 --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/LwM2mObservationStore.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * 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.observation; + +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.ScheduledExecutorService; + +import org.eclipse.californium.core.coap.Token; +import org.eclipse.californium.core.observe.Observation; +import org.eclipse.californium.core.observe.ObservationStore; +import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.leshan.core.californium.ObserveUtil; +import org.eclipse.leshan.core.observation.ObservationIdentifier; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.registration.RegistrationStore; + +public class LwM2mObservationStore implements ObservationStore { + + private final RegistrationStore registrationStore; + private final LwM2mNotificationReceiver notificationListener; + private final ObservationSerDes observationSerDes; + + public LwM2mObservationStore(RegistrationStore registrationStore, LwM2mNotificationReceiver notificationListener, + ObservationSerDes observationSerDes) { + this.registrationStore = registrationStore; + this.notificationListener = notificationListener; + this.observationSerDes = observationSerDes; + } + + @Override + public Observation putIfAbsent(Token token, Observation obs) { + org.eclipse.leshan.core.observation.Observation lwm2mObservation = buildLwM2mObservation(obs); + Collection removed = registrationStore + .addObservation(lwm2mObservation.getRegistrationId(), lwm2mObservation, true); + + Observation previousObservation = null; + if (removed != null && !removed.isEmpty()) { + for (org.eclipse.leshan.core.observation.Observation observation : removed) { + if (Arrays.equals(observation.getId().getBytes(), token.getBytes())) { + previousObservation = buildCoapObservation(observation); + break; + } + } + } + for (org.eclipse.leshan.core.observation.Observation observation : removed) { + notificationListener.cancelled(observation); + } + return previousObservation; + } + + @Override + public Observation put(Token token, Observation obs) { + org.eclipse.leshan.core.observation.Observation lwm2mObservation = buildLwM2mObservation(obs); + Collection removed = registrationStore + .addObservation(lwm2mObservation.getRegistrationId(), buildLwM2mObservation(obs), false); + + Observation previousObservation = null; + if (removed != null && !removed.isEmpty()) { + for (org.eclipse.leshan.core.observation.Observation observation : removed) { + if (Arrays.equals(observation.getId().getBytes(), token.getBytes())) { + previousObservation = buildCoapObservation(observation); + break; + } + } + } + for (org.eclipse.leshan.core.observation.Observation observation : removed) { + notificationListener.cancelled(observation); + } + return previousObservation; + } + + @Override + public void remove(Token token) { + org.eclipse.leshan.core.observation.Observation removedObservation = registrationStore.removeObservation(null, + new ObservationIdentifier(token.getBytes())); + notificationListener.cancelled(removedObservation); + } + + @Override + public Observation get(Token token) { + org.eclipse.leshan.core.observation.Observation observation = registrationStore.getObservation(null, + new ObservationIdentifier(token.getBytes())); + if (observation == null) { + return null; + } else { + return buildCoapObservation(observation); + } + } + + @Override + public void setContext(Token token, EndpointContext endpointContext) { + // In Leshan we always set context when we send the request, so this should not be needed to implement this. + } + + @Override + public void setExecutor(ScheduledExecutorService executor) { + // registrationStore has its own executor. + } + + @Override + public void start() { + // Internal RegistrationStore is started by Leshan. + } + + @Override + public void stop() { + // Internal RegistrationStore is stopped by Leshan. + } + + private org.eclipse.leshan.core.observation.Observation buildLwM2mObservation(Observation observation) { + String obs = observationSerDes.serialize(observation); + return ObserveUtil.createLwM2mObservation(observation, obs); + } + + private Observation buildCoapObservation(org.eclipse.leshan.core.observation.Observation observation) { + String serializedObservation = ObserveUtil.extractSerializedObservation(observation); + if (serializedObservation == null) + return null; + + return observationSerDes.deserialize(serializedObservation); + } +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/ObservationSerDes.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/ObservationSerDes.java new file mode 100644 index 0000000000..44d07eac8b --- /dev/null +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/ObservationSerDes.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2016 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) - add support for californium + * endpoint context + * Orange - keep one JSON dependency + *******************************************************************************/ +package org.eclipse.leshan.server.californium.observation; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.network.serialization.DataParser; +import org.eclipse.californium.core.network.serialization.DataSerializer; +import org.eclipse.californium.core.network.serialization.UdpDataParser; +import org.eclipse.californium.core.network.serialization.UdpDataSerializer; +import org.eclipse.californium.core.observe.Observation; +import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.leshan.core.util.Hex; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Functions for serializing and deserializing a Californium {@link Observation} in JSON. + * + * The embedded CoAP request is serialized using the Californium network serialization (see {@link UdpDataParser} and + * {@link UdpDataSerializer}). + */ +public class ObservationSerDes { + + private final DataSerializer serializer; + private final DataParser parser; + + public ObservationSerDes(DataParser parser, DataSerializer serializer) { + this.parser = parser; + this.serializer = serializer; + } + + public String serialize(Observation obs) { + ObjectNode o = JsonNodeFactory.instance.objectNode(); + + o.put("request", Hex.encodeHexString(serializer.serializeRequest(obs.getRequest()).bytes)); + if (obs.getContext() != null) + o.set("peer", EndpointContextSerDes.serialize(obs.getContext())); + else + o.set("peer", EndpointContextSerDes.serialize(obs.getRequest().getDestinationContext())); + + if (obs.getRequest().getUserContext() != null) { + ObjectNode ctxObject = JsonNodeFactory.instance.objectNode(); + for (Entry e : obs.getRequest().getUserContext().entrySet()) { + ctxObject.put(e.getKey(), e.getValue()); + } + o.set("context", ctxObject); + } + return o.toString(); + } + + public Observation deserialize(String data) { + try { + JsonNode v = new ObjectMapper().readTree(data); + + EndpointContext endpointContext = EndpointContextSerDes.deserialize(v.get("peer")); + byte[] req = Hex.decodeHex(v.get("request").asText().toCharArray()); + + Request request = (Request) parser.parseMessage(req); + request.setDestinationContext(endpointContext); + + JsonNode ctxValue = v.get("context"); + if (ctxValue != null) { + Map context = new HashMap<>(); + for (Iterator it = ctxValue.fieldNames(); it.hasNext();) { + String name = it.next(); + context.put(name, ctxValue.get(name).asText()); + } + request.setUserContext(context); + } + + return new Observation(request, endpointContext); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException(String.format("Unable to deserialize Observation %s", data), e); + } + } + +} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/ObservationServiceImpl.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/ObservationServiceImpl.java deleted file mode 100644 index eff5668c3e..0000000000 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/observation/ObservationServiceImpl.java +++ /dev/null @@ -1,400 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 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. - *******************************************************************************/ -package org.eclipse.leshan.server.californium.observation; - -import static org.eclipse.leshan.core.californium.ResponseCodeUtil.toLwM2mResponseCode; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.eclipse.californium.core.coap.Token; -import org.eclipse.californium.core.network.Endpoint; -import org.eclipse.californium.core.observe.NotificationListener; -import org.eclipse.californium.core.observe.ObservationStore; -import org.eclipse.leshan.core.ResponseCode; -import org.eclipse.leshan.core.californium.EndpointContextUtil; -import org.eclipse.leshan.core.californium.ObserveUtil; -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.TimestampedLwM2mNode; -import org.eclipse.leshan.core.node.codec.CodecException; -import org.eclipse.leshan.core.node.codec.LwM2mDecoder; -import org.eclipse.leshan.core.observation.CompositeObservation; -import org.eclipse.leshan.core.observation.Observation; -import org.eclipse.leshan.core.observation.SingleObservation; -import org.eclipse.leshan.core.request.ContentFormat; -import org.eclipse.leshan.core.request.Identity; -import org.eclipse.leshan.core.request.exception.InvalidResponseException; -import org.eclipse.leshan.core.response.AbstractLwM2mResponse; -import org.eclipse.leshan.core.response.ObserveCompositeResponse; -import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.core.util.Hex; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; -import org.eclipse.leshan.server.model.LwM2mModelProvider; -import org.eclipse.leshan.server.observation.ObservationListener; -import org.eclipse.leshan.server.observation.ObservationService; -import org.eclipse.leshan.server.registration.Registration; -import org.eclipse.leshan.server.registration.RegistrationUpdate; -import org.eclipse.leshan.server.registration.UpdatedRegistration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Implementation of the {@link ObservationService} accessing the persisted observation via the provided - * {@link CaliforniumRegistrationStore}. - * - * When a new observation is added or changed or canceled, the registered listeners are notified. - */ -public class ObservationServiceImpl implements ObservationService, NotificationListener { - - private final Logger LOG = LoggerFactory.getLogger(ObservationServiceImpl.class); - - private final CaliforniumRegistrationStore registrationStore; - private final LwM2mModelProvider modelProvider; - private final LwM2mDecoder decoder; - private Endpoint secureEndpoint; - private Endpoint nonSecureEndpoint; - private final boolean updateRegistrationOnNotification; - - private final List listeners = new CopyOnWriteArrayList<>();; - - /** - * Creates an instance of {@link ObservationServiceImpl} - * - * @param store instance of californium's {@link ObservationStore} - * @param modelProvider instance of {@link LwM2mModelProvider} - * @param decoder instance of {@link LwM2mDecoder} - */ - public ObservationServiceImpl(CaliforniumRegistrationStore store, LwM2mModelProvider modelProvider, - LwM2mDecoder decoder) { - this(store, modelProvider, decoder, false); - } - - /** - * Creates an instance of {@link ObservationServiceImpl} - * - * @param store instance of californium's {@link ObservationStore} - * @param modelProvider instance of {@link LwM2mModelProvider} - * @param decoder instance of {@link LwM2mDecoder} - * @param updateRegistrationOnNotification will activate registration update on observe notification. - * - * @since 1.1 - */ - public ObservationServiceImpl(CaliforniumRegistrationStore store, LwM2mModelProvider modelProvider, - LwM2mDecoder decoder, boolean updateRegistrationOnNotification) { - this.registrationStore = store; - this.modelProvider = modelProvider; - this.decoder = decoder; - this.updateRegistrationOnNotification = updateRegistrationOnNotification; - } - - public void addObservation(Registration registration, Observation observation) { - for (Observation existing : registrationStore.addObservation(registration.getId(), observation)) { - cancel(existing); - } - - for (ObservationListener listener : listeners) { - listener.newObservation(observation, registration); - } - } - - public void setNonSecureEndpoint(Endpoint endpoint) { - nonSecureEndpoint = endpoint; - } - - public void setSecureEndpoint(Endpoint endpoint) { - secureEndpoint = endpoint; - } - - @Override - public int cancelObservations(Registration registration) { - // check registration id - String registrationId = registration.getId(); - if (registrationId == null) - return 0; - - Collection observations = registrationStore.removeObservations(registrationId); - if (observations == null) - return 0; - - for (Observation observation : observations) { - cancel(observation); - } - - return observations.size(); - } - - @Override - public int cancelObservations(Registration registration, String nodePath) { - if (registration == null || registration.getId() == null || nodePath == null || nodePath.isEmpty()) - return 0; - - Set observations = getObservations(registration.getId(), nodePath); - for (Observation observation : observations) { - cancelObservation(observation); - } - return observations.size(); - } - - @Override - public int cancelCompositeObservations(Registration registration, String[] nodePaths) { - if (registration == null || registration.getId() == null || nodePaths == null || nodePaths.length == 0) - return 0; - - Set observations = getCompositeObservations(registration.getId(), nodePaths); - for (Observation observation : observations) { - cancelObservation(observation); - } - return observations.size(); - } - - @Override - public void cancelObservation(Observation observation) { - if (observation == null) - return; - - registrationStore.removeObservation(observation.getRegistrationId(), observation.getId()); - cancel(observation); - } - - private void cancel(Observation observation) { - Token token = new Token(observation.getId()); - if (secureEndpoint != null) - secureEndpoint.cancelObservation(token); - if (nonSecureEndpoint != null) - nonSecureEndpoint.cancelObservation(token); - - for (ObservationListener listener : listeners) { - listener.cancelled(observation); - } - } - - @Override - public Set getObservations(Registration registration) { - return getObservations(registration.getId()); - } - - private Set getObservations(String registrationId) { - if (registrationId == null) - return Collections.emptySet(); - - return new HashSet<>(registrationStore.getObservations(registrationId)); - } - - private Set getCompositeObservations(String registrationId, String[] nodePaths) { - if (registrationId == null || nodePaths == null) - return Collections.emptySet(); - - // array of String to array of LWM2M path - List lwPaths = new ArrayList<>(nodePaths.length); - for (int i = 0; i < nodePaths.length; i++) { - lwPaths.add(new LwM2mPath(nodePaths[i])); - } - - // search composite-observation - Set result = new HashSet<>(); - for (Observation obs : getObservations(registrationId)) { - if (obs instanceof CompositeObservation) { - if (lwPaths.equals(((CompositeObservation) obs).getPaths())) { - result.add(obs); - } - } - } - return result; - } - - private Set getObservations(String registrationId, String nodePath) { - if (registrationId == null || nodePath == null) - return Collections.emptySet(); - - Set result = new HashSet<>(); - LwM2mPath lwPath = new LwM2mPath(nodePath); - for (Observation obs : getObservations(registrationId)) { - if (obs instanceof SingleObservation) { - if (lwPath.equals(((SingleObservation) obs).getPath())) { - result.add(obs); - } - } - } - return result; - } - - /** - * @return the Californium {@link ObservationStore} - */ - public ObservationStore getObservationStore() { - return registrationStore; - } - - @Override - public void addListener(ObservationListener listener) { - listeners.add(listener); - } - - @Override - public void removeListener(ObservationListener listener) { - listeners.remove(listener); - } - - // ********** NotificationListener interface **********// - - @Override - public void onNotification(Request coapRequest, Response coapResponse) { - LOG.trace("notification received for request {}: {}", coapRequest, coapResponse); - - if (listeners.isEmpty()) - return; - - // get registration Id - String regid = coapRequest.getUserContext().get(ObserveUtil.CTX_REGID); - - // get observation for this request - Observation observation = registrationStore.getObservation(regid, coapResponse.getToken().getBytes()); - if (observation == null) { - LOG.error("Unexpected error: Unable to find observation with token {} for registration {}", - coapResponse.getToken(), regid); - return; - } - - // get registration - Registration registration; - if (updateRegistrationOnNotification) { - Identity obsIdentity = EndpointContextUtil.extractIdentity(coapResponse.getSourceContext()); - RegistrationUpdate regUpdate = new RegistrationUpdate(observation.getRegistrationId(), obsIdentity, null, - null, null, null, null, null); - UpdatedRegistration updatedRegistration = registrationStore.updateRegistration(regUpdate); - if (updatedRegistration == null || updatedRegistration.getUpdatedRegistration() == null) { - LOG.error("Unexpected error: There is no registration with id {} for this observation {}", - observation.getRegistrationId(), observation); - return; - } - registration = updatedRegistration.getUpdatedRegistration(); - } else { - registration = registrationStore.getRegistration(observation.getRegistrationId()); - if (registration == null) { - LOG.error("Unexpected error: There is no registration with id {} for this observation {}", - observation.getRegistrationId(), observation); - return; - } - } - - try { - // get model for this registration - LwM2mModel model = modelProvider.getObjectModel(registration); - - // create response - AbstractLwM2mResponse response = createObserveResponse(observation, model, coapResponse); - - if (response != null) { - // notify all listeners - for (ObservationListener listener : listeners) { - if (observation instanceof SingleObservation && response instanceof ObserveResponse) { - listener.onResponse((SingleObservation) observation, registration, (ObserveResponse) response); - } - if (observation instanceof CompositeObservation && response instanceof ObserveCompositeResponse) { - listener.onResponse((CompositeObservation) observation, registration, - (ObserveCompositeResponse) response); - } - } - } - } catch (InvalidResponseException e) { - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("Invalid notification for observation [%s]", observation), e); - } - - for (ObservationListener listener : listeners) { - listener.onError(observation, registration, e); - } - } catch (RuntimeException e) { - if (LOG.isErrorEnabled()) { - LOG.error(String.format("Unable to handle notification for observation [%s]", observation), e); - } - - for (ObservationListener listener : listeners) { - listener.onError(observation, registration, e); - } - } - } - - private AbstractLwM2mResponse createObserveResponse(Observation observation, LwM2mModel model, - Response coapResponse) { - // CHANGED response is supported for backward compatibility with old spec. - if (coapResponse.getCode() != CoAP.ResponseCode.CHANGED - && coapResponse.getCode() != CoAP.ResponseCode.CONTENT) { - throw new InvalidResponseException("Unexpected response code [%s] for %s", coapResponse.getCode(), - observation); - } - - // get content format - ContentFormat contentFormat = null; - if (coapResponse.getOptions().hasContentFormat()) { - contentFormat = ContentFormat.fromCode(coapResponse.getOptions().getContentFormat()); - } - - // decode response - try { - ResponseCode responseCode = toLwM2mResponseCode(coapResponse.getCode()); - - if (observation instanceof SingleObservation) { - SingleObservation singleObservation = (SingleObservation) observation; - - List timestampedNodes = decoder.decodeTimestampedData(coapResponse.getPayload(), - contentFormat, singleObservation.getPath(), model); - - // create lwm2m response - if (timestampedNodes.size() == 1 && !timestampedNodes.get(0).isTimestamped()) { - return new ObserveResponse(responseCode, timestampedNodes.get(0).getNode(), null, singleObservation, - null, coapResponse); - } else { - return new ObserveResponse(responseCode, null, timestampedNodes, singleObservation, null, - coapResponse); - } - } else if (observation instanceof CompositeObservation) { - - CompositeObservation compositeObservation = (CompositeObservation) observation; - - Map nodes = decoder.decodeNodes(coapResponse.getPayload(), contentFormat, - compositeObservation.getPaths(), model); - - return new ObserveCompositeResponse(responseCode, nodes, null, coapResponse, compositeObservation); - } - - throw new IllegalStateException( - "observation must be a CompositeObservation or a SingleObservation but was " + observation == null - ? null - : observation.getClass().getSimpleName()); - } catch (CodecException e) { - if (LOG.isDebugEnabled()) { - byte[] payload = coapResponse.getPayload() == null ? new byte[0] : coapResponse.getPayload(); - LOG.debug(String.format("Unable to decode notification payload [%s] of observation [%s] ", - Hex.encodeHexString(payload), observation), e); - } - throw new InvalidResponseException(e, "Unable to decode notification payload of observation [%s] ", - observation); - } - } -} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java index 275bb7e570..6d5d61a2fa 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java @@ -29,6 +29,7 @@ import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.leshan.core.californium.LwM2mCoapResource; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.link.LinkParseException; import org.eclipse.leshan.core.link.LinkParser; @@ -41,8 +42,8 @@ import org.eclipse.leshan.core.response.RegisterResponse; import org.eclipse.leshan.core.response.SendableResponse; import org.eclipse.leshan.core.response.UpdateResponse; -import org.eclipse.leshan.server.registration.RegistrationHandler; import org.eclipse.leshan.server.registration.RegistrationService; +import org.eclipse.leshan.server.request.UplinkRequestReceiver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,14 +72,14 @@ public class RegisterResource extends LwM2mCoapResource { public static final String RESOURCE_NAME = "rd"; - private final RegistrationHandler registrationHandler; - + private final UplinkRequestReceiver receiver; private final LinkParser linkParser; - public RegisterResource(RegistrationHandler registrationHandler, LinkParser linkParser) { - super(RESOURCE_NAME); + public RegisterResource(UplinkRequestReceiver receiver, LinkParser linkParser, + IdentityHandlerProvider identityHandlerProvider) { + super(RESOURCE_NAME, identityHandlerProvider); - this.registrationHandler = registrationHandler; + this.receiver = receiver; this.linkParser = linkParser; getAttributes().addResourceType("core.rd"); } @@ -128,7 +129,7 @@ public void handleDELETE(CoapExchange exchange) { protected void handleRegister(CoapExchange exchange, Request request) { // Get identity // -------------------------------- - Identity sender = extractIdentity(request.getSourceContext()); + Identity sender = getForeignPeerIdentity(exchange.advanced(), request); // Create LwM2m request from CoAP request // -------------------------------- @@ -180,8 +181,8 @@ protected void handleRegister(CoapExchange exchange, Request request) { // Handle request // ------------------------------- - final SendableResponse sendableResponse = registrationHandler.register(sender, - registerRequest); + final SendableResponse sendableResponse = receiver.requestReceived(sender, null, + registerRequest, exchange.advanced().getEndpoint().getUri()); RegisterResponse response = sendableResponse.getResponse(); // Create CoAP Response from LwM2m request @@ -197,7 +198,7 @@ protected void handleRegister(CoapExchange exchange, Request request) { protected void handleUpdate(CoapExchange exchange, Request request, String registrationId) { // Get identity - Identity sender = extractIdentity(request.getSourceContext()); + Identity sender = getForeignPeerIdentity(exchange.advanced(), request); // Create LwM2m request from CoAP request Long lifetime = null; @@ -233,7 +234,8 @@ protected void handleUpdate(CoapExchange exchange, Request request, String regis additionalParams, coapRequest); // Handle request - final SendableResponse sendableResponse = registrationHandler.update(sender, updateRequest); + final SendableResponse sendableResponse = receiver.requestReceived(sender, null, updateRequest, + exchange.advanced().getEndpoint().getUri()); UpdateResponse updateResponse = sendableResponse.getResponse(); // Create CoAP Response from LwM2m request @@ -247,15 +249,15 @@ protected void handleUpdate(CoapExchange exchange, Request request, String regis protected void handleDeregister(CoapExchange exchange, String registrationId) { // Get identity - Identity sender = extractIdentity(exchange.advanced().getRequest().getSourceContext()); + Request coapRequest = exchange.advanced().getRequest(); + Identity sender = getForeignPeerIdentity(exchange.advanced(), coapRequest); // Create request - Request coapRequest = exchange.advanced().getRequest(); DeregisterRequest deregisterRequest = new DeregisterRequest(registrationId, coapRequest); // Handle request - final SendableResponse sendableResponse = registrationHandler.deregister(sender, - deregisterRequest); + final SendableResponse sendableResponse = receiver.requestReceived(sender, null, + deregisterRequest, exchange.advanced().getEndpoint().getUri()); DeregisterResponse deregisterResponse = sendableResponse.getResponse(); // Create CoAP Response from LwM2m request diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumLwM2mRequestSender.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumLwM2mRequestSender.java deleted file mode 100644 index efc39f1863..0000000000 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumLwM2mRequestSender.java +++ /dev/null @@ -1,253 +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 - * Achim Kraus (Bosch Software Innovations GmbH) - use Identity as destination - * Michał Wadowski (Orange) - Improved compliance with rfc6690 - *******************************************************************************/ -package org.eclipse.leshan.server.californium.request; - -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.eclipse.californium.core.network.Endpoint; -import org.eclipse.leshan.core.Destroyable; -import org.eclipse.leshan.core.californium.CoapResponseCallback; -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.codec.CodecException; -import org.eclipse.leshan.core.node.codec.LwM2mDecoder; -import org.eclipse.leshan.core.node.codec.LwM2mEncoder; -import org.eclipse.leshan.core.observation.Observation; -import org.eclipse.leshan.core.request.DownlinkRequest; -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.ObserveCompositeResponse; -import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.core.response.ResponseCallback; -import org.eclipse.leshan.core.util.Validate; -import org.eclipse.leshan.server.californium.observation.ObservationServiceImpl; -import org.eclipse.leshan.server.model.LwM2mModelProvider; -import org.eclipse.leshan.server.registration.Registration; -import org.eclipse.leshan.server.request.LowerLayerConfig; -import org.eclipse.leshan.server.request.LwM2mRequestSender; - -/** - * An implementation of {@link LwM2mRequestSender} and {@link CoapRequestSender} based on Californium. - */ -public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender, CoapRequestSender, Destroyable { - - private final ObservationServiceImpl observationService; - private final LwM2mModelProvider modelProvider; - private final RequestSender sender; - - /** - * @param secureEndpoint The endpoint used to send coaps request. - * @param nonSecureEndpoint The endpoint used to send coap request. - * @param observationService The service used to store observation. - * @param modelProvider the {@link LwM2mModelProvider} used retrieve the {@link LwM2mModel} used to encode/decode - * {@link LwM2mNode}. - * @param encoder The {@link LwM2mEncoder} used to encode {@link LwM2mNode}. - * @param decoder The {@link LwM2mDecoder} used to encode {@link LwM2mNode}. - * @param linkParser a parser {@link LwM2mLinkParser} used to parse a CoRE Link. - */ - public CaliforniumLwM2mRequestSender(Endpoint secureEndpoint, Endpoint nonSecureEndpoint, - ObservationServiceImpl observationService, LwM2mModelProvider modelProvider, LwM2mEncoder encoder, - LwM2mDecoder decoder, LwM2mLinkParser linkParser) { - Validate.notNull(observationService); - Validate.notNull(modelProvider); - this.observationService = observationService; - this.modelProvider = modelProvider; - this.sender = new RequestSender(secureEndpoint, nonSecureEndpoint, encoder, decoder, linkParser); - } - - /** - * 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 Registration} associate to the device we want to sent the request. - * @param request The request to send to the client. - * @param lowerLayerConfig to tweak lower layer request (e.g. coap request) - * @param timeoutInMs The global timeout to wait in milliseconds (see - * https://github.com/eclipse/leshan/wiki/Request-Timeout) - * @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). - */ - @Override - public T send(Registration destination, DownlinkRequest request, - LowerLayerConfig lowerLayerConfig, long timeoutInMs) throws InterruptedException { - - // Retrieve the objects definition - final LwM2mModel model = modelProvider.getObjectModel(destination); - - // Send requests synchronously - T response = sender.sendLwm2mRequest(destination.getEndpoint(), destination.getIdentity(), destination.getId(), - model, destination.getRootPath(), request, lowerLayerConfig, timeoutInMs, - destination.canInitiateConnection()); - - // Handle special observe case - if (response != null && response.isSuccess()) { - Observation observation = null; - if (response instanceof ObserveResponse) { - observation = ((ObserveResponse) response).getObservation(); - } else if (response instanceof ObserveCompositeResponse) { - observation = ((ObserveCompositeResponse) response).getObservation(); - } - if (observation != null) { - observationService.addObservation(destination, observation); - } - } - return response; - } - - /** - * Send a Lightweight M2M {@link DownlinkRequest} asynchronously to a LWM2M client. - * - * The Californium API does not ensure that message callback are exclusive. E.g. In some race condition, you can get - * a onReponse call and a onCancel one. This method ensures that you will receive only one event. Meaning, you get - * either 1 response or 1 error. - * - * @param destination The {@link Registration} associate to the device we want to sent the request. - * @param request The request to send to the client. - * @param lowerLayerConfig to tweak lower layer request (e.g. coap request) - * @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 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 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. - */ - @Override - public void send(final Registration destination, DownlinkRequest request, - LowerLayerConfig lowerLayerConfig, long timeoutInMs, final ResponseCallback responseCallback, - ErrorCallback errorCallback) { - // Retrieve the objects definition - final LwM2mModel model = modelProvider.getObjectModel(destination); - - // Send requests asynchronously - sender.sendLwm2mRequest(destination.getEndpoint(), destination.getIdentity(), destination.getId(), model, - destination.getRootPath(), request, lowerLayerConfig, timeoutInMs, new ResponseCallback() { - @Override - public void onResponse(T response) { - if (response != null && response.getClass() == ObserveResponse.class && response.isSuccess()) { - observationService.addObservation(destination, - ((ObserveResponse) response).getObservation()); - } - responseCallback.onResponse(response); - } - }, errorCallback, destination.canInitiateConnection()); - } - - /** - * Send a CoAP {@link Request} synchronously to a LWM2M client. Will block until a response is received from the - * remote client. - *

- * 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 Registration} associate to the device we want to sent the request. s - * @param coapRequest The request to send to the client. - * @param timeoutInMs The response timeout to wait in milliseconds (see - * https://github.com/eclipse/leshan/wiki/Request-Timeout) - * @return the response or null if the timeout expires (see - * https://github.com/eclipse/leshan/wiki/Request-Timeout). - * - * @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 UnconnectedPeerException if client is not connected (no dtls connection available). - */ - @Override - public Response sendCoapRequest(Registration destination, Request coapRequest, long timeoutInMs) - throws InterruptedException { - return sender.sendCoapRequest(destination.getIdentity(), destination.getId(), coapRequest, timeoutInMs, - destination.canInitiateConnection()); - } - - /** - * Sends a CoAP {@link Request} asynchronously to a LWM2M client. - * - * The Californium API does not ensure that message callback are exclusive. E.g. In some race condition, you can get - * a onReponse call and a onCancel one. This method ensures that you will receive only one event. Meaning, you get - * either 1 response or 1 error. - * - * @param destination The {@link Registration} associate to the device we want to sent the request. - * @param coapRequest The request to send to the client. - * @param timeoutInMs The response 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 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 UnconnectedPeerException} if client is not connected (no dtls connection available).
  • - *
  • {@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. - */ - @Override - public void sendCoapRequest(Registration destination, Request coapRequest, long timeoutInMs, - CoapResponseCallback responseCallback, ErrorCallback errorCallback) { - sender.sendCoapRequest(destination.getIdentity(), destination.getId(), coapRequest, timeoutInMs, - responseCallback, errorCallback, destination.canInitiateConnection()); - } - - /** - * 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 registration client registration meta data of a LWM2M client. - */ - @Override - public void cancelOngoingRequests(Registration registration) { - Validate.notNull(registration); - sender.cancelRequests(registration.getId()); - } - - @Override - public void destroy() { - sender.destroy(); - } -} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumQueueModeRequestSender.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumQueueModeRequestSender.java deleted file mode 100644 index aab5cf9ff9..0000000000 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumQueueModeRequestSender.java +++ /dev/null @@ -1,139 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2018 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.request; - -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.eclipse.leshan.core.Destroyable; -import org.eclipse.leshan.core.californium.CoapResponseCallback; -import org.eclipse.leshan.core.request.exception.ClientSleepingException; -import org.eclipse.leshan.core.request.exception.TimeoutException; -import org.eclipse.leshan.core.response.ErrorCallback; -import org.eclipse.leshan.server.queue.PresenceServiceImpl; -import org.eclipse.leshan.server.queue.QueueModeLwM2mRequestSender; -import org.eclipse.leshan.server.registration.Registration; -import org.eclipse.leshan.server.request.LwM2mRequestSender; - -/** - * A {@link LwM2mRequestSender} and {@link CoapRequestSender} which supports LWM2M Queue Mode. - */ -public class CaliforniumQueueModeRequestSender extends QueueModeLwM2mRequestSender - implements CoapRequestSender, Destroyable { - - /** - * @param presenceService the presence service object for setting the client into sleeping state when request - * Timeout expires and into awake state when a response arrives. - * @param delegatedSender internal sender that it is used for sending the requests, using delegation. - */ - public CaliforniumQueueModeRequestSender(PresenceServiceImpl presenceService, LwM2mRequestSender delegatedSender) { - super(presenceService, delegatedSender); - } - - /** - * {@inheritDoc} - */ - @Override - public Response sendCoapRequest(Registration destination, Request coapRequest, long timeout) - throws InterruptedException { - - // Ensure that delegated sender is able to send CoAP request - if (!(delegatedSender instanceof CoapRequestSender)) { - throw new UnsupportedOperationException("This sender does not support to send CoAP request"); - } - CoapRequestSender sender = (CoapRequestSender) delegatedSender; - - // If the client does not use Q-Mode, just send - if (!destination.usesQueueMode()) { - return sender.sendCoapRequest(destination, coapRequest, timeout); - } - - // If the client uses Q-Mode... - - // If the client is sleeping, warn the user and return - if (!presenceService.isClientAwake(destination)) { - throw new ClientSleepingException("The destination client is sleeping, request cannot be sent."); - } - - // Use delegation to send the request - Response response = sender.sendCoapRequest(destination, coapRequest, timeout); - if (response != null) { - // Set the client awake. This will restart the timer. - presenceService.setAwake(destination); - } else { - // If the timeout expires, this means the client does not respond. - presenceService.setSleeping(destination); - } - // Wait for response, then return it - return response; - } - - /** - * {@inheritDoc} - */ - @Override - public void sendCoapRequest(final Registration destination, Request coapRequest, long timeout, - final CoapResponseCallback responseCallback, final ErrorCallback errorCallback) { - - // Ensure that delegated sender is able to send CoAP request - if (!(delegatedSender instanceof CoapRequestSender)) { - throw new UnsupportedOperationException("This sender does not support to send CoAP request"); - } - CoapRequestSender sender = (CoapRequestSender) delegatedSender; - - // If the client does not use Q-Mode, just send - if (!destination.usesQueueMode()) { - sender.sendCoapRequest(destination, coapRequest, timeout, responseCallback, errorCallback); - return; - } - - // If the client uses Q-Mode... - - // If the client is sleeping, warn the user and return - if (!presenceService.isClientAwake(destination)) { - throw new ClientSleepingException("The destination client is sleeping, request cannot be sent."); - } - - // Use delegation to send the request, with specific callbacks to perform Queue Mode operation - sender.sendCoapRequest(destination, coapRequest, timeout, new CoapResponseCallback() { - @Override - public void onResponse(Response response) { - // Set the client awake. This will restart the timer. - presenceService.setAwake(destination); - - // Call the user's callback - responseCallback.onResponse(response); - } - }, new ErrorCallback() { - @Override - public void onError(Exception e) { - if (e instanceof TimeoutException) { - // If the timeout expires, this means the client does not respond. - presenceService.setSleeping(destination); - } - - // Call the user's callback - errorCallback.onError(e); - } - }); - } - - @Override - public void destroy() { - if (delegatedSender instanceof Destroyable) { - ((Destroyable) delegatedSender).destroy(); - } - } -} diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilder.java index 431c634180..fd8aa52275 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilder.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilder.java @@ -26,6 +26,7 @@ import org.eclipse.californium.elements.util.Bytes; import org.eclipse.leshan.core.californium.EndpointContextUtil; import org.eclipse.leshan.core.californium.ObserveUtil; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mObject; @@ -76,11 +77,13 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor { private final LwM2mModel model; private final LwM2mEncoder encoder; - private LowerLayerConfig lowerLayerConfig; + private final LowerLayerConfig lowerLayerConfig; + + private final IdentityHandler identityHandler; public CoapRequestBuilder(Identity destination, String rootPath, String registrationId, String endpoint, LwM2mModel model, LwM2mEncoder encoder, boolean allowConnectionInitiation, - LowerLayerConfig lowerLayerConfig) { + LowerLayerConfig lowerLayerConfig, IdentityHandler identityHandler) { this.destination = destination; this.rootPath = rootPath; this.endpoint = endpoint; @@ -89,6 +92,7 @@ public CoapRequestBuilder(Identity destination, String rootPath, String registra this.encoder = encoder; this.allowConnectionInitiation = allowConnectionInitiation; this.lowerLayerConfig = lowerLayerConfig; + this.identityHandler = identityHandler; } @Override @@ -188,7 +192,7 @@ public void visit(ObserveRequest request) { public void visit(CancelObservationRequest request) { coapRequest = Request.newGet(); coapRequest.setObserveCancel(); - coapRequest.setToken(request.getObservation().getId()); + coapRequest.setToken(request.getObservation().getId().getBytes()); if (request.getContentFormat() != null) coapRequest.getOptions().setAccept(request.getContentFormat().getCode()); setURI(coapRequest, request.getPath()); @@ -233,7 +237,7 @@ public void visit(ObserveCompositeRequest request) { public void visit(CancelCompositeObservationRequest request) { coapRequest = Request.newFetch(); coapRequest.setObserveCancel(); - coapRequest.setToken(request.getObservation().getId()); + coapRequest.setToken(request.getObservation().getId().getBytes()); coapRequest.getOptions().setContentFormat(request.getRequestContentFormat().getCode()); coapRequest.setPayload(encoder.encodePaths(request.getPaths(), request.getRequestContentFormat())); @@ -351,9 +355,15 @@ protected void setURI(Request coapRequest, LwM2mPath path) { } protected void setSecurityContext(Request coapRequest) { - EndpointContext context = EndpointContextUtil.extractContext(destination, allowConnectionInitiation); - coapRequest.setDestinationContext(context); + if (identityHandler != null) { + EndpointContext context = identityHandler.createEndpointContext(destination, allowConnectionInitiation); + coapRequest.setDestinationContext(context); + + } else { + EndpointContext context = EndpointContextUtil.extractContext(destination, allowConnectionInitiation); + coapRequest.setDestinationContext(context); + } if (destination.isOSCORE()) { coapRequest.getOptions().setOscore(Bytes.EMPTY); } diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java index d1a9462556..dff2280fd5 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java @@ -38,6 +38,7 @@ import org.eclipse.leshan.core.californium.CoapSyncRequestObserver; import org.eclipse.leshan.core.californium.EndpointContextUtil; import org.eclipse.leshan.core.californium.SyncRequestObserver; +import org.eclipse.leshan.core.californium.TemporaryExceptionTranslator; import org.eclipse.leshan.core.link.lwm2m.LwM2mLinkParser; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.node.LwM2mNode; @@ -136,7 +137,7 @@ public T sendLwm2mRequest(final String endpointName, I // Create the CoAP request from LwM2m request CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(destination, rootPath, sessionId, - endpointName, model, encoder, allowConnectionInitiation, lowerLayerConfig); + endpointName, model, encoder, allowConnectionInitiation, lowerLayerConfig, null); request.accept(coapClientRequestBuilder); final Request coapRequest = coapClientRequestBuilder.getRequest(); @@ -212,7 +213,7 @@ public void sendLwm2mRequest(final String endpointName // Create the CoAP request from LwM2m request CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(destination, rootPath, sessionId, - endpointName, model, encoder, allowConnectionInitiation, lowerLayerConfig); + endpointName, model, encoder, allowConnectionInitiation, lowerLayerConfig, null); request.accept(coapClientRequestBuilder); final Request coapRequest = coapClientRequestBuilder.getRequest(); @@ -279,7 +280,8 @@ public Response sendCoapRequest(Identity destination, String sessionId, Request // TODO OSCORE : should we add the OSCORE option automatically here too ? // Send CoAP request synchronously - CoapSyncRequestObserver syncMessageObserver = new CoapSyncRequestObserver(coapRequest, timeoutInMs); + CoapSyncRequestObserver syncMessageObserver = new CoapSyncRequestObserver(coapRequest, timeoutInMs, + new TemporaryExceptionTranslator()); coapRequest.addMessageObserver(syncMessageObserver); // Store pending request to be able to cancel it later @@ -343,7 +345,7 @@ public void sendCoapRequest(Identity destination, String sessionId, Request coap // Add CoAP request callback MessageObserver obs = new CoapAsyncRequestObserver(coapRequest, responseCallback, errorCallback, timeoutInMs, - executor); + executor, new TemporaryExceptionTranslator()); coapRequest.addMessageObserver(obs); // Store pending request to be able to cancel it later @@ -399,7 +401,7 @@ private void removeOngoingRequest(String key, Request coapRequest) { ongoingRequests.remove(key, coapRequest); } - private AtomicLong idGenerator = new AtomicLong(0l); + private final AtomicLong idGenerator = new AtomicLong(0l); private class CleanerMessageObserver extends MessageObserverAdapter { diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java index 44499ed1e6..5822b89086 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/send/SendResource.java @@ -21,7 +21,7 @@ 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.model.LwM2mModel; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.LwM2mDecoder; @@ -31,10 +31,9 @@ import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.eclipse.leshan.core.response.SendResponse; import org.eclipse.leshan.core.response.SendableResponse; -import org.eclipse.leshan.server.model.LwM2mModelProvider; -import org.eclipse.leshan.server.registration.Registration; -import org.eclipse.leshan.server.registration.RegistrationStore; -import org.eclipse.leshan.server.send.SendHandler; +import org.eclipse.leshan.server.profile.ClientProfile; +import org.eclipse.leshan.server.profile.ClientProfileProvider; +import org.eclipse.leshan.server.request.UplinkRequestReceiver; /** * A CoAP Resource used to handle "Send" request sent by LWM2M devices. @@ -42,49 +41,50 @@ * @see SendRequest */ public class SendResource extends LwM2mCoapResource { - private RegistrationStore registrationStore; - private LwM2mDecoder decoder; - private LwM2mModelProvider modelProvider; - private SendHandler sendHandler; + private final LwM2mDecoder decoder; + private final UplinkRequestReceiver receiver; + private final ClientProfileProvider profileProvider; - public SendResource(SendHandler sendHandler, LwM2mModelProvider modelProvider, LwM2mDecoder decoder, - RegistrationStore registrationStore) { - super("dp"); - this.registrationStore = registrationStore; + public SendResource(UplinkRequestReceiver receiver, LwM2mDecoder decoder, ClientProfileProvider profileProvider, + IdentityHandlerProvider identityHandlerProvider) { + super("dp", identityHandlerProvider); this.decoder = decoder; - this.modelProvider = modelProvider; - this.sendHandler = sendHandler; + this.receiver = receiver; + this.profileProvider = profileProvider; } @Override public void handlePOST(CoapExchange exchange) { Request coapRequest = exchange.advanced().getRequest(); - Identity sender = extractIdentity(coapRequest.getSourceContext()); - Registration registration = registrationStore.getRegistrationByIdentity(sender); + Identity sender = getForeignPeerIdentity(exchange.advanced(), coapRequest); + ClientProfile clientProfile = profileProvider.getProfile(sender); // check we have a registration for this identity - if (registration == null) { + if (clientProfile == null) { exchange.respond(ResponseCode.NOT_FOUND, "no registration found"); return; } try { // Decode payload - LwM2mModel model = modelProvider.getObjectModel(registration); byte[] payload = exchange.getRequestPayload(); ContentFormat contentFormat = ContentFormat.fromCode(exchange.getRequestOptions().getContentFormat()); if (!decoder.isSupported(contentFormat)) { exchange.respond(ResponseCode.BAD_REQUEST, "Unsupported content format"); - sendHandler.onError(registration, new InvalidRequestException( - "Unsupported content format [%s] in [%s] from [%s]", contentFormat, coapRequest, sender)); + receiver.onError(sender, clientProfile, + new InvalidRequestException("Unsupported content format [%s] in [%s] from [%s]", contentFormat, + coapRequest, sender), + SendRequest.class, exchange.advanced().getEndpoint().getUri()); return; } - TimestampedLwM2mNodes data = decoder.decodeTimestampedNodes(payload, contentFormat, model); + TimestampedLwM2mNodes data = decoder.decodeTimestampedNodes(payload, contentFormat, + clientProfile.getModel()); // Handle "send op request SendRequest sendRequest = new SendRequest(contentFormat, data, coapRequest); - SendableResponse sendableResponse = sendHandler.handleSend(registration, sendRequest); + SendableResponse sendableResponse = receiver.requestReceived(sender, clientProfile, + sendRequest, exchange.advanced().getEndpoint().getUri()); SendResponse response = sendableResponse.getResponse(); // send reponse @@ -99,11 +99,12 @@ public void handlePOST(CoapExchange exchange) { } } catch (CodecException e) { exchange.respond(ResponseCode.BAD_REQUEST, "Invalid Payload"); - sendHandler.onError(registration, - new InvalidRequestException(e, "Invalid payload in [%s] from [%s]", coapRequest, sender)); + receiver.onError(sender, clientProfile, + new InvalidRequestException(e, "Invalid payload in [%s] from [%s]", coapRequest, sender), + SendRequest.class, exchange.advanced().getEndpoint().getUri()); return; } catch (RuntimeException e) { - sendHandler.onError(registration, e); + receiver.onError(sender, clientProfile, e, SendRequest.class, exchange.advanced().getEndpoint().getUri()); throw e; } } diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/CaliforniumTestSupport.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/CaliforniumTestSupport.java deleted file mode 100644 index d510956d29..0000000000 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/CaliforniumTestSupport.java +++ /dev/null @@ -1,53 +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 - *******************************************************************************/ -package org.eclipse.leshan.server.californium; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; - -import org.eclipse.leshan.core.LwM2m; -import org.eclipse.leshan.core.request.Identity; -import org.eclipse.leshan.server.registration.Registration; - -public class CaliforniumTestSupport { - - public Registration registration; - public InetAddress destination; - public int destinationPort = 5000; - public InetSocketAddress registrationAddress; - - public void givenASimpleClient() throws UnknownHostException { - registrationAddress = InetSocketAddress.createUnresolved("localhost", LwM2m.DEFAULT_COAP_PORT); - - Registration.Builder builder = new Registration.Builder("ID", "urn:client", - Identity.unsecure(Inet4Address.getLoopbackAddress(), 1000)); - - registration = builder.build(); - } - - public static byte[] createToken() { - Random random = ThreadLocalRandom.current(); - byte[] token; - token = new byte[random.nextInt(8) + 1]; - // random value - random.nextBytes(token); - return token; - } -} diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerBuilderTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerBuilderTest.java index 3b1e06327b..97987f707e 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerBuilderTest.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerBuilderTest.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.eclipse.leshan.server.californium; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -36,7 +37,14 @@ 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.util.Hex; +import org.eclipse.leshan.server.LeshanServer; +import org.eclipse.leshan.server.LeshanServerBuilder; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider.Builder; +import org.eclipse.leshan.server.californium.endpoint.coap.CoapServerProtocolProvider; +import org.eclipse.leshan.server.californium.endpoint.coaps.CoapsServerProtocolProvider; import org.eclipse.leshan.server.security.InMemorySecurityStore; import org.junit.Before; import org.junit.Test; @@ -82,55 +90,68 @@ public void start() { } @Test - public void create_server_without_any_parameter() { + public void create_server_with_default_californiumEndpointsProvider() { + builder.setEndpointsProvider(new CaliforniumServerEndpointsProvider()); server = builder.build(); - assertNull(server.getSecuredAddress()); - assertNotNull(server.getUnsecuredAddress()); - assertNull(server.getSecurityStore()); + assertEquals(1, server.getEndpoints().size()); + assertEquals(Protocol.COAP, server.getEndpoints().get(0).getProtocol()); } @Test - public void create_server_with_securityStore() { - builder.setSecurityStore(new InMemorySecurityStore()); + public void create_server_without_securityStore() { + Builder endpointsBuilder = new CaliforniumServerEndpointsProvider.Builder(new CoapServerProtocolProvider(), + new CoapsServerProtocolProvider()); + builder.setEndpointsProvider(endpointsBuilder.build()); server = builder.build(); - assertNotNull(server.getSecuredAddress()); - assertNotNull(server.getUnsecuredAddress()); - assertNotNull(server.getSecurityStore()); + 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 CaliforniumServerEndpointsProvider.Builder(new CoapServerProtocolProvider(), + new CoapsServerProtocolProvider()); + builder.setEndpointsProvider(endpointsBuilder.build()); builder.setSecurityStore(new InMemorySecurityStore()); - 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 CaliforniumServerEndpointsProvider.Builder(new CoapsServerProtocolProvider()); + builder.setEndpointsProvider(endpointsBuilder.build()); builder.setSecurityStore(new InMemorySecurityStore()); - 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 = LeshanServerBuilder.createDefaultCoapConfiguration(); + Builder endpointsBuilder = new CaliforniumServerEndpointsProvider.Builder(new CoapsServerProtocolProvider()); + + 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.setPrivateKey(privateKey); builder.setPublicKey(publicKey); builder.setSecurityStore(new InMemorySecurityStore()); + builder.setEndpointsProvider(endpointsBuilder.build()); 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/LeshanServerTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java index e816e12075..ed7e68dfce 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java @@ -20,12 +20,17 @@ import java.net.InetSocketAddress; import java.util.EnumSet; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.response.ErrorCallback; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.ResponseCallback; +import org.eclipse.leshan.server.LeshanServer; +import org.eclipse.leshan.server.LeshanServerBuilder; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider.Builder; import org.eclipse.leshan.server.queue.PresenceServiceImpl; import org.eclipse.leshan.server.registration.Registration; import org.junit.Test; @@ -34,7 +39,9 @@ public class LeshanServerTest { @Test public void testStartStopStart() throws InterruptedException { - LeshanServer server = new LeshanServerBuilder().setLocalAddress(new InetSocketAddress(0)).build(); + Builder EndpointProviderbuilder = new CaliforniumServerEndpointsProvider.Builder(); + EndpointProviderbuilder.addEndpoint(new InetSocketAddress(0), Protocol.COAP); + LeshanServer server = new LeshanServerBuilder().setEndpointsProvider(EndpointProviderbuilder.build()).build(); server.start(); Thread.sleep(100); @@ -48,7 +55,10 @@ public void testStartDestroy() throws InterruptedException { // look at nb active thread before. int numberOfThreadbefore = Thread.activeCount(); - LeshanServer server = new LeshanServerBuilder().setLocalAddress(new InetSocketAddress(0)).build(); + Builder EndpointProviderbuilder = new CaliforniumServerEndpointsProvider.Builder(); + EndpointProviderbuilder.addEndpoint(new InetSocketAddress(0), Protocol.COAP); + LeshanServer server = new LeshanServerBuilder().setEndpointsProvider(EndpointProviderbuilder.build()).build(); + server.start(); Thread.sleep(100); // HACK force creation thread creation. @@ -66,7 +76,10 @@ public void testStartStopDestroy() throws InterruptedException { // look at nb active thread before. int numberOfThreadbefore = Thread.activeCount(); - LeshanServer server = new LeshanServerBuilder().setLocalAddress(new InetSocketAddress(0)).build(); + Builder EndpointProviderbuilder = new CaliforniumServerEndpointsProvider.Builder(); + EndpointProviderbuilder.addEndpoint(new InetSocketAddress(0), Protocol.COAP); + LeshanServer server = new LeshanServerBuilder().setEndpointsProvider(EndpointProviderbuilder.build()).build(); + server.start(); Thread.sleep(100); // HACK force creation thread creation. @@ -83,7 +96,8 @@ public void testStartStopDestroy() throws InterruptedException { private void forceThreadsCreation(LeshanServer server) { Registration reg = new Registration.Builder("id", "endpoint", Identity.unsecure(new InetSocketAddress(5555))) - .bindingMode(EnumSet.of(BindingMode.U, BindingMode.Q)).build(); + .bindingMode(EnumSet.of(BindingMode.U, BindingMode.Q)) + .lastEndpointUsed(server.getEndpoint(Protocol.COAP).getURI()).build(); // Force timer thread creation of preference service. ((PresenceServiceImpl) server.getPresenceService()).setAwake(reg); // Force time thread creation of CoapAsyncRequestObserver 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 61b569078b..fe6220a5a6 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 @@ -43,7 +43,6 @@ 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.californium.LeshanServerBuilder; import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.SecurityInfo; import org.junit.Before; @@ -181,7 +180,7 @@ public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { @Test public void create_server_without_psk_cipher() { - Configuration coapConfiguration = LeshanServerBuilder.createDefaultCoapConfiguration(); + Configuration coapConfiguration = LeshanBootstrapServerBuilder.createDefaultCoapConfiguration(); coapConfiguration.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8); builder.setCoapConfig(coapConfiguration); builder.setPrivateKey(privateKey); diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/registration/InMemoryRegistrationStoreTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/LwM2mObservationStoreTest.java similarity index 69% rename from leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/registration/InMemoryRegistrationStoreTest.java rename to leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/LwM2mObservationStoreTest.java index 842282126c..d0bc7e76ac 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/registration/InMemoryRegistrationStoreTest.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/LwM2mObservationStoreTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2013-2015 Sierra Wireless and others. + * 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 @@ -12,16 +12,15 @@ * * Contributors: * Sierra Wireless - initial API and implementation - * Michał Wadowski (Orange) - Add Observe-Composite feature. - * Michał Wadowski (Orange) - Improved compliance with rfc6690. *******************************************************************************/ -package org.eclipse.leshan.server.californium.registration; +package org.eclipse.leshan.server.californium.observation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.EnumSet; @@ -31,26 +30,32 @@ import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Token; +import org.eclipse.californium.core.network.serialization.UdpDataParser; +import org.eclipse.californium.core.network.serialization.UdpDataSerializer; +import org.eclipse.californium.elements.AddressEndpointContext; import org.eclipse.leshan.core.californium.ObserveUtil; import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.ContentFormat; 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.response.ObserveCompositeResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.profile.ClientProfile; +import org.eclipse.leshan.server.registration.InMemoryRegistrationStore; import org.eclipse.leshan.server.registration.Registration; -import org.eclipse.leshan.server.registration.RegistrationUpdate; -import org.eclipse.leshan.server.registration.UpdatedRegistration; -import org.junit.Assert; +import org.eclipse.leshan.server.registration.RegistrationStore; import org.junit.Before; import org.junit.Test; -public class InMemoryRegistrationStoreTest { - +public class LwM2mObservationStoreTest { private final String ep = "urn:endpoint"; private final int port = 23452; private final Long lifetime = 10000L; @@ -62,7 +67,8 @@ public class InMemoryRegistrationStoreTest { private final String examplePath = "/1/2/3"; private final List examplePaths = Arrays.asList(new LwM2mPath("/1/2/3"), new LwM2mPath("/4/5/6")); - CaliforniumRegistrationStore store; + RegistrationStore store; + LwM2mObservationStore observationStore; InetAddress address; Registration registration; @@ -70,48 +76,28 @@ public class InMemoryRegistrationStoreTest { public void setUp() throws UnknownHostException { address = InetAddress.getLocalHost(); store = new InMemoryRegistrationStore(); - } - - @Test - public void update_registration_keeps_properties_unchanged() { - givenASimpleRegistration(lifetime); - store.addRegistration(registration); - - RegistrationUpdate update = new RegistrationUpdate(registrationId, Identity.unsecure(address, port), null, null, - null, null, null, null); - UpdatedRegistration updatedRegistration = store.updateRegistration(update); - assertEquals(lifetime, updatedRegistration.getUpdatedRegistration().getLifeTimeInSec()); - Assert.assertSame(binding, updatedRegistration.getUpdatedRegistration().getBindingMode()); - assertEquals(sms, updatedRegistration.getUpdatedRegistration().getSmsNumber()); - - assertEquals(registration, updatedRegistration.getPreviousRegistration()); - - Registration reg = store.getRegistrationByEndpoint(ep); - assertEquals(lifetime, reg.getLifeTimeInSec()); - Assert.assertSame(binding, reg.getBindingMode()); - assertEquals(sms, reg.getSmsNumber()); - } - - @Test - public void client_registration_sets_time_to_live() { - givenASimpleRegistration(lifetime); - store.addRegistration(registration); - Assert.assertTrue(registration.isAlive()); - } - - @Test - public void update_registration_to_extend_time_to_live() { - givenASimpleRegistration(0L); - store.addRegistration(registration); - Assert.assertFalse(registration.isAlive()); - - RegistrationUpdate update = new RegistrationUpdate(registrationId, Identity.unsecure(address, port), lifetime, - null, null, null, null, null); - UpdatedRegistration updatedRegistration = store.updateRegistration(update); - Assert.assertTrue(updatedRegistration.getUpdatedRegistration().isAlive()); - - Registration reg = store.getRegistrationByEndpoint(ep); - Assert.assertTrue(reg.isAlive()); + observationStore = new LwM2mObservationStore(store, new LwM2mNotificationReceiver() { + @Override + public void onNotification(CompositeObservation observation, ClientProfile profile, + ObserveCompositeResponse response) { + } + + @Override + public void onNotification(SingleObservation observation, ClientProfile profile, ObserveResponse response) { + } + + @Override + public void onError(Observation observation, ClientProfile profile, Exception error) { + } + + @Override + public void newObservation(Observation observation, Registration registration) { + } + + @Override + public void cancelled(Observation observation) { + } + }, new ObservationSerDes(new UdpDataParser(), new UdpDataSerializer())); } @Test @@ -123,10 +109,10 @@ public void put_coap_observation_with_valid_request() { org.eclipse.californium.core.observe.Observation observationToStore = prepareCoapObservation(); // when - store.put(exampleToken, observationToStore); + observationStore.put(exampleToken, observationToStore); // then - org.eclipse.californium.core.observe.Observation observationFetched = store.get(exampleToken); + org.eclipse.californium.core.observe.Observation observationFetched = observationStore.get(exampleToken); assertNotNull(observationFetched); assertEquals(observationToStore.toString(), observationFetched.toString()); @@ -141,10 +127,11 @@ public void get_observation_from_request() { org.eclipse.californium.core.observe.Observation observationToStore = prepareCoapObservation(); // when - store.put(exampleToken, observationToStore); + observationStore.put(exampleToken, observationToStore); // then - Observation leshanObservation = store.getObservation(registrationId, exampleToken.getBytes()); + Observation leshanObservation = store.getObservation(registrationId, + new ObservationIdentifier(exampleToken.getBytes())); assertNotNull(leshanObservation); assertTrue(leshanObservation instanceof SingleObservation); SingleObservation observation = (SingleObservation) leshanObservation; @@ -160,10 +147,11 @@ public void get_composite_observation_from_request() { org.eclipse.californium.core.observe.Observation observationToStore = prepareCoapCompositeObservation(); // when - store.put(exampleToken, observationToStore); + observationStore.put(exampleToken, observationToStore); // then - Observation leshanObservation = store.getObservation(registrationId, exampleToken.getBytes()); + Observation leshanObservation = store.getObservation(registrationId, + new ObservationIdentifier(exampleToken.getBytes())); assertNotNull(leshanObservation); assertTrue(leshanObservation instanceof CompositeObservation); CompositeObservation observation = (CompositeObservation) leshanObservation; @@ -177,10 +165,12 @@ private org.eclipse.californium.core.observe.Observation prepareCoapObservation( observeRequest); Request coapRequest = new Request(CoAP.Code.GET); + coapRequest.setMID(123); coapRequest.setUserContext(userContext); coapRequest.setToken(exampleToken); coapRequest.setObserve(); coapRequest.getOptions().setAccept(ContentFormat.DEFAULT.getCode()); + coapRequest.setDestinationContext(new AddressEndpointContext(new InetSocketAddress("localhost", 5683))); return new org.eclipse.californium.core.observe.Observation(coapRequest, null); } @@ -192,10 +182,12 @@ private org.eclipse.californium.core.observe.Observation prepareCoapCompositeObs observeRequest); Request coapRequest = new Request(CoAP.Code.FETCH); + coapRequest.setMID(123); coapRequest.setUserContext(userContext); coapRequest.setToken(exampleToken); coapRequest.setObserve(); coapRequest.getOptions().setAccept(ContentFormat.DEFAULT.getCode()); + coapRequest.setDestinationContext(new AddressEndpointContext(new InetSocketAddress("localhost", 5683))); return new org.eclipse.californium.core.observe.Observation(coapRequest, null); } diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/ObservationServiceTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/ObservationServiceTest.java index 0e1a44b7dc..3897c47d2f 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/ObservationServiceTest.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/ObservationServiceTest.java @@ -16,64 +16,77 @@ *******************************************************************************/ package org.eclipse.leshan.server.californium.observation; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.net.InetAddress; +import java.net.URI; import java.net.UnknownHostException; -import java.util.Map; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Random; import java.util.Set; -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.eclipse.leshan.core.californium.EndpointContextUtil; -import org.eclipse.leshan.core.californium.ObserveUtil; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; -import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; +import org.eclipse.leshan.core.request.BindingMode; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.DownlinkRequest; 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.response.AbstractLwM2mResponse; -import org.eclipse.leshan.core.response.ObserveCompositeResponse; -import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.server.californium.CaliforniumTestSupport; -import org.eclipse.leshan.server.californium.DummyDecoder; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; -import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore; -import org.eclipse.leshan.server.model.StandardModelProvider; -import org.eclipse.leshan.server.observation.ObservationListener; +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.LeshanServer; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpointsProvider; +import org.eclipse.leshan.server.endpoint.ServerEndpointToolbox; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.observation.ObservationServiceImpl; +import org.eclipse.leshan.server.profile.ClientProfile; +import org.eclipse.leshan.server.registration.InMemoryRegistrationStore; import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationStore; +import org.eclipse.leshan.server.request.LowerLayerConfig; +import org.eclipse.leshan.server.request.UplinkRequestReceiver; +import org.eclipse.leshan.server.security.ServerSecurityInfo; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class ObservationServiceTest { - Request coapRequest; ObservationServiceImpl observationService; - CaliforniumRegistrationStore store; - - private final CaliforniumTestSupport support = new CaliforniumTestSupport(); + RegistrationStore store; + Registration registration; + Random r; @Before public void setUp() throws Exception { - support.givenASimpleClient(); + registration = givenASimpleRegistration(); store = new InMemoryRegistrationStore(); + r = new Random(); + } + + private Registration givenASimpleRegistration() throws UnknownHostException { + Registration.Builder builder = new Registration.Builder("4711", "urn:endpoint", + Identity.unsecure(InetAddress.getLocalHost(), 23452)); + return builder.lifeTimeInSec(10000L).bindingMode(EnumSet.of(BindingMode.U)) + .objectLinks(new Link[] { new Link("/3") }).build(); } @Test public void observe_twice_cancels_first() { createDefaultObservationService(); - givenAnObservation(support.registration.getId(), new LwM2mPath(3, 0, 12)); - givenAnObservation(support.registration.getId(), new LwM2mPath(3, 0, 12)); + givenAnObservation(registration.getId(), new LwM2mPath(3, 0, 12)); + givenAnObservation(registration.getId(), new LwM2mPath(3, 0, 12)); // check the presence of only one observation. - Set observations = observationService.getObservations(support.registration); + Set observations = observationService.getObservations(registration); Assert.assertEquals(1, observations.size()); } @@ -82,21 +95,21 @@ public void cancel_by_client() { createDefaultObservationService(); // create some observations - givenAnObservation(support.registration.getId(), new LwM2mPath(3, 0, 13)); - givenAnObservation(support.registration.getId(), new LwM2mPath(3, 0, 12)); + givenAnObservation(registration.getId(), new LwM2mPath(3, 0, 13)); + givenAnObservation(registration.getId(), new LwM2mPath(3, 0, 12)); givenAnObservation("anotherClient", new LwM2mPath(3, 0, 12)); // check its presence - Set observations = observationService.getObservations(support.registration); + Set observations = observationService.getObservations(registration); Assert.assertEquals(2, observations.size()); // cancel it - int nbCancelled = observationService.cancelObservations(support.registration); + int nbCancelled = observationService.cancelObservations(registration); Assert.assertEquals(2, nbCancelled); // check its absence - observations = observationService.getObservations(support.registration); + observations = observationService.getObservations(registration); assertTrue(observations.isEmpty()); } @@ -105,22 +118,22 @@ public void cancel_by_path() { createDefaultObservationService(); // create some observations - givenAnObservation(support.registration.getId(), new LwM2mPath(3, 0, 13)); - givenAnObservation(support.registration.getId(), new LwM2mPath(3, 0, 12)); - givenAnObservation(support.registration.getId(), new LwM2mPath(3, 0, 12)); + givenAnObservation(registration.getId(), new LwM2mPath(3, 0, 13)); + givenAnObservation(registration.getId(), new LwM2mPath(3, 0, 12)); + givenAnObservation(registration.getId(), new LwM2mPath(3, 0, 12)); givenAnObservation("anotherClient", new LwM2mPath(3, 0, 12)); // check its presence - Set observations = observationService.getObservations(support.registration); + Set observations = observationService.getObservations(registration); Assert.assertEquals(2, observations.size()); // cancel it - int nbCancelled = observationService.cancelObservations(support.registration, "/3/0/12"); + int nbCancelled = observationService.cancelObservations(registration, "/3/0/12"); Assert.assertEquals(1, nbCancelled); // check its absence - observations = observationService.getObservations(support.registration); + observations = observationService.getObservations(registration); Assert.assertEquals(1, observations.size()); } @@ -129,78 +142,26 @@ public void cancel_by_observation() { createDefaultObservationService(); // create some observations - givenAnObservation(support.registration.getId(), new LwM2mPath(3, 0, 13)); - givenAnObservation(support.registration.getId(), new LwM2mPath(3, 0, 12)); + givenAnObservation(registration.getId(), new LwM2mPath(3, 0, 13)); + givenAnObservation(registration.getId(), new LwM2mPath(3, 0, 12)); givenAnObservation("anotherClient", new LwM2mPath(3, 0, 12)); - Observation observationToCancel = givenAnObservation(support.registration.getId(), new LwM2mPath(3, 0, 12)); + Observation observationToCancel = givenAnObservation(registration.getId(), new LwM2mPath(3, 0, 12)); // check its presence - Set observations = observationService.getObservations(support.registration); + Set observations = observationService.getObservations(registration); Assert.assertEquals(2, observations.size()); // cancel it observationService.cancelObservation(observationToCancel); // check its absence - observations = observationService.getObservations(support.registration); + observations = observationService.getObservations(registration); Assert.assertEquals(1, observations.size()); } - @Test - public void on_notification_observe_response() { - // given - createDummyDecoderObservationService(); - - givenAnObservation(support.registration.getId(), new LwM2mPath("/1/2/3")); - - Response coapResponse = new Response(CoAP.ResponseCode.CONTENT); - coapResponse.setToken(coapRequest.getToken()); - - CatchResponseObservationListener listener = new CatchResponseObservationListener(); - - observationService.addListener(listener); - - // when - observationService.onNotification(coapRequest, coapResponse); - - // then - assertNotNull(listener.observeResponse); - assertNotNull(listener.observation); - assertTrue(listener.observeResponse instanceof ObserveResponse); - assertTrue(listener.observation instanceof SingleObservation); - } - - @Test - public void on_notification_composite_observe_response() { - // given - createDummyDecoderObservationService(); - - givenAnCompositeObservation(support.registration.getId(), new LwM2mPath("/1/2/3")); - - Response coapResponse = new Response(CoAP.ResponseCode.CONTENT); - coapResponse.setToken(coapRequest.getToken()); - - CatchResponseObservationListener listener = new CatchResponseObservationListener(); - - observationService.addListener(listener); - - // when - observationService.onNotification(coapRequest, coapResponse); - - // then - assertNotNull(listener.observeResponse); - assertNotNull(listener.observation); - assertTrue(listener.observeResponse instanceof ObserveCompositeResponse); - assertTrue(listener.observation instanceof CompositeObservation); - } - - private void createDummyDecoderObservationService() { - observationService = new ObservationServiceImpl(store, new StandardModelProvider(), new DummyDecoder()); - } - private void createDefaultObservationService() { - observationService = new ObservationServiceImpl(store, new StandardModelProvider(), new DefaultLwM2mDecoder()); + observationService = new ObservationServiceImpl(store, new DummyEndpointsProvider()); } private Observation givenAnObservation(String registrationId, LwM2mPath target) { @@ -210,46 +171,11 @@ private Observation givenAnObservation(String registrationId, LwM2mPath target) store.addRegistration(registration); } - coapRequest = Request.newGet(); - coapRequest.setToken(CaliforniumTestSupport.createToken()); - coapRequest.getOptions().addUriPath(String.valueOf(target.getObjectId())); - coapRequest.getOptions().addUriPath(String.valueOf(target.getObjectInstanceId())); - coapRequest.getOptions().addUriPath(String.valueOf(target.getResourceId())); - coapRequest.setObserve(); - coapRequest - .setDestinationContext(EndpointContextUtil.extractContext(support.registration.getIdentity(), false)); - Map context = ObserveUtil.createCoapObserveRequestContext(registration.getEndpoint(), - registrationId, new ObserveRequest(target.toString())); - coapRequest.setUserContext(context); - - store.put(coapRequest.getToken(), new org.eclipse.californium.core.observe.Observation(coapRequest, null)); - - SingleObservation observation = ObserveUtil.createLwM2mObservation(coapRequest); - observationService.addObservation(registration, observation); - - return observation; - } - - private Observation givenAnCompositeObservation(String registrationId, LwM2mPath target) { - Registration registration = store.getRegistration(registrationId); - if (registration == null) { - registration = givenASimpleClient(registrationId); - store.addRegistration(registration); - } - - coapRequest = Request.newFetch(); - coapRequest.setToken(CaliforniumTestSupport.createToken()); - coapRequest.setObserve(); - coapRequest - .setDestinationContext(EndpointContextUtil.extractContext(support.registration.getIdentity(), false)); - Map context = ObserveUtil.createCoapObserveCompositeRequestContext(registration.getEndpoint(), - registrationId, new ObserveCompositeRequest(null, null, target.toString())); - coapRequest.setUserContext(context); - - store.put(coapRequest.getToken(), new org.eclipse.californium.core.observe.Observation(coapRequest, null)); - - CompositeObservation observation = ObserveUtil.createLwM2mCompositeObservation(coapRequest); - + byte[] token = new byte[8]; + r.nextBytes(token); + SingleObservation observation = new SingleObservation(new ObservationIdentifier(token), registrationId, target, + ContentFormat.DEFAULT, null, null); + store.addObservation(registrationId, observation, false); return observation; } @@ -264,37 +190,65 @@ private Registration givenASimpleClient(String registrationId) { } } - private static class CatchResponseObservationListener implements ObservationListener { + private class DummyEndpointsProvider implements LwM2mServerEndpointsProvider { + private final LwM2mServerEndpoint dummyEndpoint = new LwM2mServerEndpoint() { + @Override + public void send(ClientProfile destination, DownlinkRequest request, + ResponseCallback responseCallback, ErrorCallback errorCallback, + LowerLayerConfig lowerLayerConfig, long timeoutInMs) { + } - AbstractLwM2mResponse observeResponse; - Observation observation; + @Override + public T send(ClientProfile destination, DownlinkRequest request, + LowerLayerConfig lowerLayerConfig, long timeoutInMs) throws InterruptedException { + return null; + } - @Override - public void newObservation(Observation observation, Registration registration) { + @Override + public URI getURI() { + return null; + } - } + @Override + public Protocol getProtocol() { + return null; + } + + @Override + public void cancelRequests(String sessionID) { + + } + + @Override + public void cancelObservation(Observation observation) { + } + }; @Override - public void cancelled(Observation observation) { + public List getEndpoints() { + return Arrays.asList(dummyEndpoint); + } + @Override + public LwM2mServerEndpoint getEndpoint(URI uri) { + return dummyEndpoint; } @Override - public void onResponse(SingleObservation observation, Registration registration, ObserveResponse response) { - this.observeResponse = response; - this.observation = observation; + public void createEndpoints(UplinkRequestReceiver requestReceiver, LwM2mNotificationReceiver observationService, + ServerEndpointToolbox toolbox, ServerSecurityInfo serverSecurityInfo, LeshanServer server) { } @Override - public void onResponse(CompositeObservation observation, Registration registration, - ObserveCompositeResponse response) { - this.observeResponse = response; - this.observation = observation; + public void start() { } @Override - public void onError(Observation observation, Registration registration, Exception error) { + public void stop() { + } + @Override + public void destroy() { } } } diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/ObserveUtilTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/ObserveUtilTest.java index ea08b8881d..c4cb26ef13 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/ObserveUtilTest.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/observation/ObserveUtilTest.java @@ -16,6 +16,7 @@ *******************************************************************************/ package org.eclipse.leshan.server.californium.observation; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -63,7 +64,7 @@ public void should_create_observation_from_context() { // then assertEquals(examplePath, observation.getPath().toString()); assertEquals(exampleRegistrationId, observation.getRegistrationId()); - assertEquals(exampleToken.getBytes(), observation.getId()); + assertArrayEquals(exampleToken.getBytes(), observation.getId().getBytes()); assertTrue(observation.getContext().containsKey("extraKey")); assertEquals("extraValue", observation.getContext().get("extraKey")); assertEquals(ContentFormat.DEFAULT, observation.getContentFormat()); @@ -94,7 +95,7 @@ public void should_create_composite_observation_from_context() { // then assertEquals(examplePaths, observation.getPaths()); assertEquals(exampleRegistrationId, observation.getRegistrationId()); - assertEquals(exampleToken.getBytes(), observation.getId()); + assertArrayEquals(exampleToken.getBytes(), observation.getId().getBytes()); assertTrue(observation.getContext().containsKey("extraKey")); assertEquals("extraValue", observation.getContext().get("extraKey")); assertEquals(ContentFormat.CBOR, observation.getRequestContentFormat()); diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java index c84b57b653..60c3ee2300 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java +++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java @@ -26,6 +26,8 @@ import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.Request; +import org.eclipse.leshan.core.californium.identity.DefaultCoapIdentityHandler; +import org.eclipse.leshan.core.californium.identity.IdentityHandler; import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.link.attributes.ResourceTypeAttribute; import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; @@ -64,11 +66,13 @@ public class CoapRequestBuilderTest { private static LwM2mModel model; private static LwM2mEncoder encoder; + private static IdentityHandler identityHandler; @BeforeClass public static void loadModel() { model = new StaticModel(ObjectLoader.loadDefault()); encoder = new DefaultLwM2mEncoder(); + identityHandler = new DefaultCoapIdentityHandler(); } private Registration newRegistration() throws UnknownHostException { @@ -91,7 +95,7 @@ public void build_read_request() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); ReadRequest request = new ReadRequest(3, 0); builder.visit(request); @@ -109,7 +113,7 @@ public void build_read_request_with_non_default_object_path() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); ReadRequest request = new ReadRequest(3, 0, 1); builder.visit(request); @@ -124,7 +128,7 @@ public void build_read_request_with_root_path() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); ReadRequest request = new ReadRequest(3); builder.visit(request); @@ -139,7 +143,7 @@ public void build_discover_request() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); DiscoverRequest request = new DiscoverRequest(3, 0); builder.visit(request); @@ -158,7 +162,7 @@ public void build_write_request() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); WriteRequest request = new WriteRequest(Mode.UPDATE, 3, 0, LwM2mSingleResource.newStringResource(15, "value")); builder.visit(request); @@ -182,7 +186,7 @@ public void build_write_request_replace() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); WriteRequest request = new WriteRequest(3, 0, 14, "value"); builder.visit(request); @@ -197,7 +201,7 @@ public void build_write_attribute_request() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); LwM2mAttributeSet attributes = new LwM2mAttributeSet( new LwM2mAttribute(LwM2mAttributes.MINIMUM_PERIOD, 10L), new LwM2mAttribute(LwM2mAttributes.MAXIMUM_PERIOD, 100L)); @@ -218,7 +222,7 @@ public void build_unset_write_attribute_request() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); LwM2mAttributeSet attributes = new LwM2mAttributeSet(new LwM2mAttribute(LwM2mAttributes.MINIMUM_PERIOD), new LwM2mAttribute(LwM2mAttributes.MAXIMUM_PERIOD)); WriteAttributesRequest request = new WriteAttributesRequest(3, 0, 14, attributes); @@ -238,7 +242,7 @@ public void build_execute_request() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); ExecuteRequest request = new ExecuteRequest(3, 0, 12, "0='params'"); builder.visit(request); @@ -257,7 +261,7 @@ public void build_create_request__without_instance_id() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); CreateRequest request = new CreateRequest(12, LwM2mSingleResource.newStringResource(0, "value")); builder.visit(request); @@ -280,7 +284,7 @@ public void build_create_request__with_instance_id() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); CreateRequest request = new CreateRequest(12, new LwM2mObjectInstance(26, LwM2mSingleResource.newStringResource(0, "value"))); builder.visit(request); @@ -305,7 +309,7 @@ public void build_delete_request() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); DeleteRequest request = new DeleteRequest(12, 0); builder.visit(request); @@ -323,7 +327,7 @@ public void build_observe_request() throws Exception { // test CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(), - reg.getEndpoint(), model, encoder, false, null); + reg.getEndpoint(), model, encoder, false, null, identityHandler); ObserveRequest request = new ObserveRequest(12, 0); builder.visit(request); diff --git a/leshan-server-core/pom.xml b/leshan-server-core/pom.xml index ec533bdb00..8c70763364 100644 --- a/leshan-server-core/pom.xml +++ b/leshan-server-core/pom.xml @@ -35,11 +35,6 @@ Contributors: org.eclipse.leshan leshan-core - - org.eclipse.californium - cf-oscore - ${californium.version} - diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServer.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/LeshanServer.java similarity index 55% rename from leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServer.java rename to leshan-server-core/src/main/java/org/eclipse/leshan/server/LeshanServer.java index d0dbc06937..4690f93310 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServer.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/LeshanServer.java @@ -15,24 +15,15 @@ * RISE SICS AB - added Queue Mode operation * Michał Wadowski (Orange) - Improved compliance with rfc6690 *******************************************************************************/ -package org.eclipse.leshan.server.californium; +package org.eclipse.leshan.server; -import java.net.InetSocketAddress; import java.util.Collection; +import java.util.List; -import org.eclipse.californium.core.CoapResource; -import org.eclipse.californium.core.CoapServer; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.eclipse.californium.core.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.californium.scandium.DTLSConnector; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; -import org.eclipse.leshan.core.californium.CoapResponseCallback; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.link.lwm2m.LwM2mLinkParser; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.LwM2mDecoder; @@ -50,20 +41,19 @@ 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.californium.observation.ObservationServiceImpl; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; -import org.eclipse.leshan.server.californium.registration.RegisterResource; -import org.eclipse.leshan.server.californium.request.CaliforniumLwM2mRequestSender; -import org.eclipse.leshan.server.californium.request.CaliforniumQueueModeRequestSender; -import org.eclipse.leshan.server.californium.request.CoapRequestSender; -import org.eclipse.leshan.server.californium.send.SendResource; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpointsProvider; +import org.eclipse.leshan.server.endpoint.ServerEndpointToolbox; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.observation.ObservationService; +import org.eclipse.leshan.server.observation.ObservationServiceImpl; +import org.eclipse.leshan.server.profile.DefaultClientProfileProvider; import org.eclipse.leshan.server.queue.ClientAwakeTimeProvider; import org.eclipse.leshan.server.queue.PresenceListener; import org.eclipse.leshan.server.queue.PresenceService; import org.eclipse.leshan.server.queue.PresenceServiceImpl; import org.eclipse.leshan.server.queue.PresenceStateListener; +import org.eclipse.leshan.server.queue.QueueModeLwM2mRequestSender; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationHandler; import org.eclipse.leshan.server.registration.RegistrationIdProvider; @@ -72,13 +62,14 @@ import org.eclipse.leshan.server.registration.RegistrationServiceImpl; import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; +import org.eclipse.leshan.server.request.DefaultDownlinkRequestSender; +import org.eclipse.leshan.server.request.DefaultUplinkRequestReceiver; +import org.eclipse.leshan.server.request.DownlinkRequestSender; import org.eclipse.leshan.server.request.LowerLayerConfig; -import org.eclipse.leshan.server.request.LwM2mRequestSender; import org.eclipse.leshan.server.security.Authorizer; -import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityStore; -import org.eclipse.leshan.server.security.SecurityStoreListener; +import org.eclipse.leshan.server.security.ServerSecurityInfo; import org.eclipse.leshan.server.send.SendHandler; import org.eclipse.leshan.server.send.SendService; import org.slf4j.Logger; @@ -87,9 +78,6 @@ /** * A Lightweight M2M server. *

- * This implementation starts a Californium {@link CoapServer} with a unsecured (for coap://) and secured endpoint (for - * coaps://). This CoAP server defines a /rd resource as described in the LWM2M specification. - *

* This class is the entry point to send synchronous and asynchronous requests to registered clients. *

* The {@link LeshanServerBuilder} should be the preferred way to build an instance of {@link LeshanServer}. @@ -102,176 +90,98 @@ public class LeshanServer { // send a Confirmable message to the time when an acknowledgement is no longer expected. private static final long DEFAULT_TIMEOUT = 2 * 60 * 1000l; // 2min in ms - // CoAP/Californium attributes - private final CoapAPI coapApi; - private final CoapServer coapServer; - private final CoapEndpoint unsecuredEndpoint; - private final CoapEndpoint securedEndpoint; - // LWM2M attributes private final RegistrationServiceImpl registrationService; - private final CaliforniumRegistrationStore registrationStore; + private final RegistrationStore registrationStore; private final SendHandler sendService; + private final LwM2mServerEndpointsProvider endpointsProvider; - /** @since 1.1 */ - protected final ObservationServiceImpl observationService; + private final ObservationServiceImpl observationService; private final SecurityStore securityStore; private final LwM2mModelProvider modelProvider; - private final PresenceServiceImpl presenceService; - private final LwM2mRequestSender requestSender; - - // Configuration - /** since 1.1 */ - protected final boolean updateRegistrationOnNotification; - - protected final LwM2mLinkParser linkParser; + private PresenceServiceImpl presenceService; + private final DownlinkRequestSender requestSender; /** * Initialize a server which will bind to the specified address and port. *

* {@link LeshanServerBuilder} is the priviledged way to create a {@link LeshanServer}. * - * @param unsecuredEndpoint CoAP endpoint used for coap:// communication. - * @param securedEndpoint CoAP endpoint used for coaps:// communication. + * @param endpointsProvider which will create all available {@link LwM2mServerEndpoint} * @param registrationStore the {@link Registration} store. * @param securityStore the {@link SecurityInfo} store. * @param authorizer define which devices is allow to register on this server. * @param modelProvider provides the objects description for each client. * @param encoder encode used to encode request payload. * @param decoder decoder used to decode response payload. - * @param coapConfig the CoAP {@link Configuration}. - * @param noQueueMode true to disable presenceService. - * @param awakeTimeProvider to set the client awake time if queue mode is used. - * @param registrationIdProvider to provide registrationId using for location-path option values on response of - * @param linkParser a parser {@link LwM2mLinkParser} used to parse a CoRE Link. - */ - public LeshanServer(CoapEndpoint unsecuredEndpoint, CoapEndpoint securedEndpoint, - CaliforniumRegistrationStore registrationStore, SecurityStore securityStore, Authorizer authorizer, - LwM2mModelProvider modelProvider, LwM2mEncoder encoder, LwM2mDecoder decoder, Configuration coapConfig, - boolean noQueueMode, ClientAwakeTimeProvider awakeTimeProvider, - RegistrationIdProvider registrationIdProvider, LwM2mLinkParser linkParser) { - this(unsecuredEndpoint, securedEndpoint, registrationStore, securityStore, authorizer, modelProvider, encoder, - decoder, coapConfig, noQueueMode, awakeTimeProvider, registrationIdProvider, false, linkParser); - } - - /** - * Initialize a server which will bind to the specified address and port. - *

- * {@link LeshanServerBuilder} is the priviledged way to create a {@link LeshanServer}. - * - * @param unsecuredEndpoint CoAP endpoint used for coap:// communication. - * @param securedEndpoint CoAP endpoint used for coaps:// communication. - * @param registrationStore the {@link Registration} store. - * @param securityStore the {@link SecurityInfo} store. - * @param authorizer define which devices is allow to register on this server. - * @param modelProvider provides the objects description for each client. - * @param encoder encode used to encode request payload. - * @param decoder decoder used to decode response payload. - * @param coapConfig the CoAP {@link Configuration}. * @param noQueueMode true to disable presenceService. * @param awakeTimeProvider to set the client awake time if queue mode is used. * @param registrationIdProvider to provide registrationId using for location-path option values on response of * Register operation. * @param updateRegistrationOnNotification will activate registration update on observe notification. * @param linkParser a parser {@link LwM2mLinkParser} used to parse a CoRE Link. + * @param serverSecurityInfo credentials of the Server * @since 1.1 */ - public LeshanServer(CoapEndpoint unsecuredEndpoint, CoapEndpoint securedEndpoint, - CaliforniumRegistrationStore registrationStore, SecurityStore securityStore, Authorizer authorizer, - LwM2mModelProvider modelProvider, LwM2mEncoder encoder, LwM2mDecoder decoder, Configuration coapConfig, - boolean noQueueMode, ClientAwakeTimeProvider awakeTimeProvider, + public LeshanServer(LwM2mServerEndpointsProvider endpointsProvider, RegistrationStore registrationStore, + SecurityStore securityStore, Authorizer authorizer, LwM2mModelProvider modelProvider, LwM2mEncoder encoder, + LwM2mDecoder decoder, boolean noQueueMode, ClientAwakeTimeProvider awakeTimeProvider, RegistrationIdProvider registrationIdProvider, boolean updateRegistrationOnNotification, - LwM2mLinkParser linkParser) { - this.linkParser = linkParser; + LwM2mLinkParser linkParser, ServerSecurityInfo serverSecurityInfo) { + Validate.notNull(endpointsProvider, "endpointsProvider cannot be null"); Validate.notNull(registrationStore, "registration store cannot be null"); Validate.notNull(authorizer, "authorizer cannot be null"); Validate.notNull(modelProvider, "modelProvider cannot be null"); Validate.notNull(encoder, "encoder cannot be null"); Validate.notNull(decoder, "decoder cannot be null"); - Validate.notNull(coapConfig, "coapConfig cannot be null"); Validate.notNull(registrationIdProvider, "registrationIdProvider cannot be null"); - // Create CoAP server - coapServer = createCoapServer(coapConfig); - - // unsecured endpoint - this.unsecuredEndpoint = unsecuredEndpoint; - if (unsecuredEndpoint != null) { - coapServer.addEndpoint(unsecuredEndpoint); - } - - // secure endpoint - this.securedEndpoint = securedEndpoint; - if (securedEndpoint != null) { - coapServer.addEndpoint(securedEndpoint); - } - // init services and stores + this.endpointsProvider = endpointsProvider; this.registrationStore = registrationStore; registrationService = createRegistrationService(registrationStore); this.securityStore = securityStore; this.modelProvider = modelProvider; - this.updateRegistrationOnNotification = updateRegistrationOnNotification; - observationService = createObservationService(registrationStore, modelProvider, decoder, unsecuredEndpoint, - securedEndpoint); + this.observationService = createObservationService(registrationStore, updateRegistrationOnNotification, + endpointsProvider); if (noQueueMode) { presenceService = null; } else { - presenceService = createPresenceService(registrationService, awakeTimeProvider); + presenceService = createPresenceService(registrationService, awakeTimeProvider, + updateRegistrationOnNotification); } - - // define /rd resource - coapServer.add(createRegisterResource(registrationService, authorizer, registrationIdProvider)); - - // define /dp resource this.sendService = createSendHandler(); - coapServer.add(createSendResource(sendService, modelProvider, decoder, registrationStore)); - - // create request sender - requestSender = createRequestSender(securedEndpoint, unsecuredEndpoint, registrationService, observationService, - this.modelProvider, encoder, decoder, presenceService); - // connection cleaner - createConnectionCleaner(securityStore, securedEndpoint); + // create endpoints + ServerEndpointToolbox toolbox = new ServerEndpointToolbox(decoder, encoder, linkParser, + new DefaultClientProfileProvider(registrationStore, modelProvider)); + RegistrationHandler registrationHandler = new RegistrationHandler(registrationService, authorizer, + registrationIdProvider); + DefaultUplinkRequestReceiver requestReceiver = new DefaultUplinkRequestReceiver(registrationHandler, + sendService); + endpointsProvider.createEndpoints(requestReceiver, observationService, toolbox, serverSecurityInfo, this); - coapApi = new CoapAPI(); - } + // create request sender + requestSender = createRequestSender(endpointsProvider, registrationService, this.modelProvider, + presenceService); - protected CoapServer createCoapServer(Configuration coapConfig) { - return new CoapServer(coapConfig) { - @Override - protected Resource createRoot() { - return new RootResource(); - } - }; } protected RegistrationServiceImpl createRegistrationService(RegistrationStore registrationStore) { return new RegistrationServiceImpl(registrationStore); } - protected ObservationServiceImpl createObservationService(CaliforniumRegistrationStore registrationStore, - LwM2mModelProvider modelProvider, LwM2mDecoder decoder, CoapEndpoint unsecuredEndpoint, - CoapEndpoint securedEndpoint) { - - ObservationServiceImpl observationService = new ObservationServiceImpl(registrationStore, modelProvider, - decoder, updateRegistrationOnNotification); - - if (unsecuredEndpoint != null) { - unsecuredEndpoint.addNotificationListener(observationService); - observationService.setNonSecureEndpoint(unsecuredEndpoint); - } + protected ObservationServiceImpl createObservationService(RegistrationStore registrationStore, + boolean updateRegistrationOnNotification, LwM2mServerEndpointsProvider endpointsProvider) { - if (securedEndpoint != null) { - securedEndpoint.addNotificationListener(observationService); - observationService.setSecureEndpoint(securedEndpoint); - } + ObservationServiceImpl observationService = new ObservationServiceImpl(registrationStore, endpointsProvider, + updateRegistrationOnNotification); return observationService; } protected PresenceServiceImpl createPresenceService(RegistrationService registrationService, - ClientAwakeTimeProvider awakeTimeProvider) { + ClientAwakeTimeProvider awakeTimeProvider, boolean updateRegistrationOnNotification) { PresenceServiceImpl presenceService = new PresenceServiceImpl(awakeTimeProvider); PresenceStateListener presenceStateListener = new PresenceStateListener(presenceService); registrationService.addListener(new PresenceStateListener(presenceService)); @@ -281,35 +191,21 @@ protected PresenceServiceImpl createPresenceService(RegistrationService registra return presenceService; } - protected CoapResource createRegisterResource(RegistrationServiceImpl registrationService, Authorizer authorizer, - RegistrationIdProvider registrationIdProvider) { - return new RegisterResource(new RegistrationHandler(registrationService, authorizer, registrationIdProvider), - linkParser); - } - protected SendHandler createSendHandler() { return new SendHandler(); } - protected CoapResource createSendResource(SendHandler sendHandler, LwM2mModelProvider modelProvider, - LwM2mDecoder decoder, CaliforniumRegistrationStore registrationStore) { - return new SendResource(sendHandler, modelProvider, decoder, registrationStore); - } - - protected LwM2mRequestSender createRequestSender(Endpoint securedEndpoint, Endpoint unsecuredEndpoint, - RegistrationServiceImpl registrationService, ObservationServiceImpl observationService, - LwM2mModelProvider modelProvider, LwM2mEncoder encoder, LwM2mDecoder decoder, + protected DownlinkRequestSender createRequestSender(LwM2mServerEndpointsProvider endpointsProvider, + RegistrationServiceImpl registrationService, LwM2mModelProvider modelProvider, PresenceServiceImpl presenceService) { // if no queue mode, create a "simple" sender - final LwM2mRequestSender requestSender; + final DownlinkRequestSender requestSender; if (presenceService == null) - requestSender = new CaliforniumLwM2mRequestSender(securedEndpoint, unsecuredEndpoint, observationService, - modelProvider, encoder, decoder, linkParser); + requestSender = new DefaultDownlinkRequestSender(endpointsProvider, modelProvider); else - requestSender = new CaliforniumQueueModeRequestSender(presenceService, - new CaliforniumLwM2mRequestSender(securedEndpoint, unsecuredEndpoint, observationService, - modelProvider, encoder, decoder, linkParser)); + requestSender = new QueueModeLwM2mRequestSender(presenceService, + new DefaultDownlinkRequestSender(endpointsProvider, modelProvider)); // Cancel observations on client unregistering registrationService.addListener(new RegistrationListener() { @@ -330,31 +226,13 @@ public void unregistered(Registration registration, Collection obse @Override public void registered(Registration registration, Registration previousReg, - Collection previousObservations) { + Collection previousObsersations) { } }); return requestSender; } - protected void createConnectionCleaner(SecurityStore securityStore, CoapEndpoint securedEndpoint) { - if (securedEndpoint != null && securedEndpoint.getConnector() instanceof DTLSConnector - && securityStore instanceof EditableSecurityStore) { - - final ConnectionCleaner connectionCleaner = new ConnectionCleaner( - (DTLSConnector) securedEndpoint.getConnector()); - - ((EditableSecurityStore) securityStore).addListener(new SecurityStoreListener() { - @Override - public void securityInfoRemoved(boolean infosAreCompromised, SecurityInfo... infos) { - if (infosAreCompromised) { - connectionCleaner.cleanConnectionFor(infos); - } - } - }); - } - } - /** * Starts the server and binds it to the specified port. */ @@ -372,12 +250,13 @@ public void start() { } // Start server - coapServer.start(); + endpointsProvider.start(); if (LOG.isInfoEnabled()) { - LOG.info("LWM2M server started at {} {}", - getUnsecuredAddress() == null ? "" : "coap://" + getUnsecuredAddress(), - getSecuredAddress() == null ? "" : "coaps://" + getSecuredAddress()); + LOG.info("LWM2M server started."); + for (LwM2mServerEndpoint endpoint : endpointsProvider.getEndpoints()) { + LOG.info("{} endpoint available at {}.", endpoint.getProtocol().getName(), endpoint.getURI()); + } } } @@ -386,7 +265,7 @@ public void start() { */ public void stop() { // Stop server - coapServer.stop(); + endpointsProvider.stop(); // Stop stores if (registrationStore instanceof Stoppable) { @@ -409,7 +288,7 @@ public void stop() { */ public void destroy() { // Destroy server - coapServer.destroy(); + endpointsProvider.destroy(); // Destroy stores if (registrationStore instanceof Destroyable) { @@ -444,6 +323,10 @@ public RegistrationService getRegistrationService() { return this.registrationService; } + public RegistrationStore getRegistrationStore() { + return registrationService.getStore(); + } + /** * Get the {@link ObservationService} to access current observations. *

@@ -485,6 +368,19 @@ public LwM2mModelProvider getModelProvider() { return this.modelProvider; } + public List getEndpoints() { + return endpointsProvider.getEndpoints(); + } + + public LwM2mServerEndpoint getEndpoint(Protocol protocol) { + for (LwM2mServerEndpoint endpoint : endpointsProvider.getEndpoints()) { + if (endpoint.getProtocol().equals(protocol)) { + return endpoint; + } + } + return null; + } + /** * Send a Lightweight M2M request synchronously using a default 2min timeout. Will block until a response is * received from the remote server. @@ -666,189 +562,4 @@ public void send(Registration destination, DownlinkReq ErrorCallback errorCallback) { requestSender.send(destination, request, lowerLayerConfig, timeoutInMs, responseCallback, errorCallback); } - - /** - * @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; - } - } - - /** - * A CoAP API, generally needed when you want to mix LWM2M and CoAP protocol. - */ - 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; - } - - /** - * Send a CoAP {@link Request} synchronously to a LWM2M client using a default 2min timeout. Will block until a - * response is received from the remote client. - *

- * The synchronous way could block a thread during a long time so it is more recommended to use the asynchronous - * way. - *

- * We choose a default timeout a bit higher to the MAX_TRANSMIT_WAIT(62-93s) which is the time from starting to - * send a Confirmable message to the time when an acknowledgement is no longer expected. - * - * @param destination The registration linked to the LWM2M client to which the request must be sent. - * @param request The CoAP request to send to the client. - * @return the response or null if the timeout expires (see - * https://github.com/eclipse/leshan/wiki/Request-Timeout). - * - * @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 ClientSleepingException if client is currently sleeping. - */ - public Response send(Registration destination, Request request) throws InterruptedException { - // Ensure that delegated sender is able to send CoAP request - if (!(requestSender instanceof CoapRequestSender)) { - throw new UnsupportedOperationException("This sender does not support to send CoAP request"); - } - CoapRequestSender sender = (CoapRequestSender) requestSender; - - return sender.sendCoapRequest(destination, request, DEFAULT_TIMEOUT); - } - - /** - * Send a CoAP {@link Request} synchronously to a LWM2M client. Will block until a response is received from the - * remote client. - *

- * The synchronous way could block a thread during a long time so it is more recommended to use the asynchronous - * way. - *

- * We choose a default timeout a bit higher to the MAX_TRANSMIT_WAIT(62-93s) which is the time from starting to - * send a Confirmable message to the time when an acknowledgement is no longer expected. - * - * @param destination The registration linked to the LWM2M client to which the request must be sent. - * @param request The CoAP request to send to the client. - * @param timeoutInMs The response timeout to wait in milliseconds (see - * https://github.com/eclipse/leshan/wiki/Request-Timeout) - * @return the response or null if the timeout expires (see - * https://github.com/eclipse/leshan/wiki/Request-Timeout). - * - * @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 ClientSleepingException if client is currently sleeping. - */ - public Response send(Registration destination, Request request, long timeoutInMs) throws InterruptedException { - // Ensure that delegated sender is able to send CoAP request - if (!(requestSender instanceof CoapRequestSender)) { - throw new UnsupportedOperationException("This sender does not support to send CoAP request"); - } - CoapRequestSender sender = (CoapRequestSender) requestSender; - - return sender.sendCoapRequest(destination, request, timeoutInMs); - } - - /** - * Sends a CoAP {@link Request} asynchronously to a LWM2M client using a default 2min timeout. - *

- * {@link ResponseCallback} and {@link ErrorCallback} are exclusively called. - * - * @param destination The registration linked to the LWM2M client to which the request must be sent. - * @param request The CoAP request to send to the client. - * @param responseCallback a callback called when a response is received (successful or error response). This - * callback MUST NOT be null. - * @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 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. - */ - public void send(Registration destination, Request request, CoapResponseCallback responseCallback, - ErrorCallback errorCallback) { - // Ensure that delegated sender is able to send CoAP request - if (!(requestSender instanceof CoapRequestSender)) { - throw new UnsupportedOperationException("This sender does not support to send CoAP request"); - } - CoapRequestSender sender = (CoapRequestSender) requestSender; - - sender.sendCoapRequest(destination, request, DEFAULT_TIMEOUT, responseCallback, errorCallback); - } - - /** - * Sends a CoAP {@link Request} asynchronously to a LWM2M client. - *

- * {@link ResponseCallback} and {@link ErrorCallback} are exclusively called. - * - * @param destination The registration linked to the LWM2M client to which the request must be sent. - * @param request The CoAP request to send to the client. - * @param responseCallback a callback called when a response is received (successful or error response). This - * callback MUST NOT be null. - * @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 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. - */ - public void send(Registration destination, Request request, long timeout, CoapResponseCallback responseCallback, - ErrorCallback errorCallback) { - // Ensure that delegated sender is able to send CoAP request - if (!(requestSender instanceof CoapRequestSender)) { - throw new UnsupportedOperationException("This sender does not support to send CoAP request"); - } - CoapRequestSender sender = (CoapRequestSender) requestSender; - - sender.sendCoapRequest(destination, request, timeout, responseCallback, errorCallback); - } - } } diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/LeshanServerBuilder.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/LeshanServerBuilder.java new file mode 100644 index 0000000000..9b4aebc12a --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/LeshanServerBuilder.java @@ -0,0 +1,324 @@ +/******************************************************************************* + * 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 Lwm2mEndpointContextMatcher + * for secure endpoint. + * Achim Kraus (Bosch Software Innovations GmbH) - use CoapEndpointBuilder + * Michał Wadowski (Orange) - Improved compliance with rfc6690. + *******************************************************************************/ +package org.eclipse.leshan.server; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import javax.security.auth.login.Configuration; + +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.core.observation.Observation; +import org.eclipse.leshan.core.request.exception.ClientSleepingException; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpointsProvider; +import org.eclipse.leshan.server.model.LwM2mModelProvider; +import org.eclipse.leshan.server.model.StandardModelProvider; +import org.eclipse.leshan.server.queue.ClientAwakeTimeProvider; +import org.eclipse.leshan.server.queue.StaticClientAwakeTimeProvider; +import org.eclipse.leshan.server.registration.InMemoryRegistrationStore; +import org.eclipse.leshan.server.registration.RandomStringRegistrationIdProvider; +import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationIdProvider; +import org.eclipse.leshan.server.registration.RegistrationStore; +import org.eclipse.leshan.server.security.Authorizer; +import org.eclipse.leshan.server.security.DefaultAuthorizer; +import org.eclipse.leshan.server.security.InMemorySecurityStore; +import org.eclipse.leshan.server.security.SecurityInfo; +import org.eclipse.leshan.server.security.SecurityStore; +import org.eclipse.leshan.server.security.ServerSecurityInfo; + +/** + * Class helping you to build and configure a Californium based Leshan 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 LeshanServer} ready to operate. + */ +public class LeshanServerBuilder { + + private RegistrationStore registrationStore; + private SecurityStore securityStore; + private LwM2mModelProvider modelProvider; + private Authorizer authorizer; + private ClientAwakeTimeProvider awakeTimeProvider; + private RegistrationIdProvider registrationIdProvider; + + private LwM2mEncoder encoder; + private LwM2mDecoder decoder; + private LwM2mLinkParser linkParser; + + private PublicKey publicKey; + private PrivateKey privateKey; + private X509Certificate[] certificateChain; + private Certificate[] trustedCertificates; + + private boolean noQueueMode = false; + private boolean updateRegistrationOnNotification; + + private LwM2mServerEndpointsProvider endpointProvider; + + /** + *

+ * Set your {@link RegistrationStore} implementation which stores {@link Registration} and {@link Observation}. + *

+ * By default the {@link InMemoryRegistrationStore} implementation is used. + * + */ + public LeshanServerBuilder setRegistrationStore(RegistrationStore registrationStore) { + this.registrationStore = registrationStore; + return this; + } + + /** + *

+ * Set your {@link SecurityStore} implementation which stores {@link SecurityInfo}. + *

+ * By default no security store is set. It is needed for secured connection if you are using the defaultAuthorizer + * or if you want PSK feature activated. An {@link InMemorySecurityStore} is provided to start using secured + * connection. + * + */ + public LeshanServerBuilder setSecurityStore(SecurityStore securityStore) { + this.securityStore = securityStore; + return this; + } + + /** + *

+ * Set your {@link Authorizer} implementation to define if a device if authorize to register to this server. + *

+ * By default the {@link DefaultAuthorizer} implementation is used, it needs a security store to accept secured + * connection. + */ + public LeshanServerBuilder setAuthorizer(Authorizer authorizer) { + this.authorizer = authorizer; + return this; + } + + /** + *

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

+ * By default the {@link StandardModelProvider}. + */ + public LeshanServerBuilder setObjectModelProvider(LwM2mModelProvider objectModelProvider) { + this.modelProvider = objectModelProvider; + return this; + } + + /** + *

+ * Set the {@link PublicKey} of the server which will be used for RawPublicKey DTLS authentication. + *

+ * This should be used for RPK support only. If you support RPK and X509, + * {@link LeshanServerBuilder#setCertificateChain(X509Certificate[])} should be used. + */ + public LeshanServerBuilder setPublicKey(PublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + /** + * Set the {@link PrivateKey} of the server which will be used for RawPublicKey(RPK) and X509 DTLS authentication. + */ + public LeshanServerBuilder setPrivateKey(PrivateKey privateKey) { + this.privateKey = privateKey; + return this; + } + + /** + *

+ * Set the CertificateChain of the server which will be used for X509 DTLS authentication. + *

+ * For RPK the public key will be extract from the first X509 certificate of the certificate chain. If you only need + * RPK support, use {@link LeshanServerBuilder#setPublicKey(PublicKey)} instead. + */ + public LeshanServerBuilder setCertificateChain(T[] certificateChain) { + this.certificateChain = certificateChain; + return this; + } + + /** + * The list of trusted certificates used to authenticate devices. + */ + public LeshanServerBuilder setTrustedCertificates(T[] trustedCertificates) { + this.trustedCertificates = trustedCertificates; + 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 LeshanServerBuilder 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 LeshanServerBuilder setDecoder(LwM2mDecoder decoder) { + this.decoder = decoder; + 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 PresenceService which tracks presence of devices using LWM2M Queue Mode. When Queue Mode is + * deactivated request is always sent immediately and {@link ClientSleepingException} will never be raised. + * Deactivate QueueMode can make sense if you want to handle it on your own or if you don't plan to support devices + * with queue mode. + */ + public LeshanServerBuilder disableQueueModeSupport() { + this.noQueueMode = true; + return this; + } + + /** + * Sets a new {@link ClientAwakeTimeProvider} object different from the default one. + *

+ * By default a {@link StaticClientAwakeTimeProvider} will be used initialized with the + * MAX_TRANSMIT_WAIT value available in CoAP {@link Configuration} which should be by default 93s as + * defined in RFC7252. + * + * @param awakeTimeProvider the {@link ClientAwakeTimeProvider} to set. + */ + public LeshanServerBuilder setClientAwakeTimeProvider(ClientAwakeTimeProvider awakeTimeProvider) { + this.awakeTimeProvider = awakeTimeProvider; + return this; + } + + /** + * Sets a new {@link RegistrationIdProvider} object different from the default one (Random string). + * + * @param registrationIdProvider the {@link RegistrationIdProvider} to set. + */ + public void setRegistrationIdProvider(RegistrationIdProvider registrationIdProvider) { + this.registrationIdProvider = registrationIdProvider; + } + + /** + * Update Registration on notification. + *

+ * There is some use cases where device can have a dynamic IP (E.g. NAT environment), the specification says to use + * an UPDATE request to notify server about IP address/ port changes. But it seems there is some rare use case where + * this update REQUEST can not be done. + *

+ * With this option you can allow Leshan to update Registration on observe notification. This is clearly OUT OF + * SPECIFICATION and so this is not recommended and should be used only if there is no other way. + * + * For {@code coap://} you probably need to use a the Relaxed response matching mode. + * + *

+     * coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED");
+     * 
+ * + * @since 1.1 + * + * @see Dynamic + * IP environnement documentaiton + */ + public LeshanServerBuilder setUpdateRegistrationOnNotification(boolean updateRegistrationOnNotification) { + this.updateRegistrationOnNotification = updateRegistrationOnNotification; + return this; + } + + public LeshanServerBuilder setEndpointsProvider(LwM2mServerEndpointsProvider endpointProvider) { + this.endpointProvider = endpointProvider; + return this; + } + + /** + * Create the {@link LeshanServer}. + *

+ * Next step will be to start it : {@link LeshanServer#start()}. + * + * @return the LWM2M server. + * @throws IllegalStateException if builder configuration is not consistent. + */ + public LeshanServer build() { + if (registrationStore == null) + registrationStore = new InMemoryRegistrationStore(); + if (authorizer == null) + authorizer = new DefaultAuthorizer(securityStore); + if (modelProvider == null) + modelProvider = new StandardModelProvider(); + if (encoder == null) + encoder = new DefaultLwM2mEncoder(); + if (decoder == null) + decoder = new DefaultLwM2mDecoder(); + if (linkParser == null) + linkParser = new DefaultLwM2mLinkParser(); + if (awakeTimeProvider == null) { + awakeTimeProvider = new StaticClientAwakeTimeProvider(); + } + if (registrationIdProvider == null) + registrationIdProvider = new RandomStringRegistrationIdProvider(); + + ServerSecurityInfo serverSecurityInfo = new ServerSecurityInfo(privateKey, publicKey, certificateChain, + trustedCertificates); + + return createServer(endpointProvider, registrationStore, securityStore, authorizer, modelProvider, encoder, + decoder, noQueueMode, awakeTimeProvider, registrationIdProvider, linkParser, serverSecurityInfo, + updateRegistrationOnNotification); + } + + /** + * Create the LeshanServer. + *

+ * You can extend LeshanServerBuilder and override this method to create a new builder which will be + * able to build an extended LeshanServer. + * + * @see LeshanServer#LeshanServer(LwM2mServerEndpointsProvider, RegistrationStore, SecurityStore, Authorizer, + * LwM2mModelProvider, LwM2mEncoder, LwM2mDecoder, boolean, ClientAwakeTimeProvider, RegistrationIdProvider, + * boolean, LwM2mLinkParser, ServerSecurityInfo) + */ + protected LeshanServer createServer(LwM2mServerEndpointsProvider endpointsProvider, + RegistrationStore registrationStore, SecurityStore securityStore, Authorizer authorizer, + LwM2mModelProvider modelProvider, LwM2mEncoder encoder, LwM2mDecoder decoder, boolean noQueueMode, + ClientAwakeTimeProvider awakeTimeProvider, RegistrationIdProvider registrationIdProvider, + LwM2mLinkParser linkParser, ServerSecurityInfo serverSecurityInfo, + boolean updateRegistrationOnNotification) { + return new LeshanServer(endpointsProvider, registrationStore, securityStore, authorizer, modelProvider, encoder, + decoder, noQueueMode, awakeTimeProvider, registrationIdProvider, updateRegistrationOnNotification, + linkParser, serverSecurityInfo); + } +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/LwM2mServerEndpoint.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/LwM2mServerEndpoint.java new file mode 100644 index 0000000000..2710792287 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/LwM2mServerEndpoint.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.endpoint; + +import java.net.URI; + +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.DownlinkRequest; +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.profile.ClientProfile; +import org.eclipse.leshan.server.request.LowerLayerConfig; + +public interface LwM2mServerEndpoint { + + Protocol getProtocol(); + + URI getURI(); + + T send(ClientProfile destination, DownlinkRequest request, + LowerLayerConfig lowerLayerConfig, long timeoutInMs) throws InterruptedException; + + void send(ClientProfile destination, DownlinkRequest request, + ResponseCallback responseCallback, ErrorCallback errorCallback, LowerLayerConfig lowerLayerConfig, + long timeoutInMs); + + void cancelRequests(String sessionID); + + void cancelObservation(Observation observation); +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/LwM2mServerEndpointsProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/LwM2mServerEndpointsProvider.java new file mode 100644 index 0000000000..ae0e500736 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/LwM2mServerEndpointsProvider.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.endpoint; + +import java.net.URI; +import java.util.List; + +import org.eclipse.leshan.server.LeshanServer; +import org.eclipse.leshan.server.observation.LwM2mNotificationReceiver; +import org.eclipse.leshan.server.request.UplinkRequestReceiver; +import org.eclipse.leshan.server.security.ServerSecurityInfo; + +public interface LwM2mServerEndpointsProvider { + + List getEndpoints(); + + LwM2mServerEndpoint getEndpoint(URI uri); + + void createEndpoints(UplinkRequestReceiver requestReceiver, LwM2mNotificationReceiver observationService, + ServerEndpointToolbox toolbox, ServerSecurityInfo serverSecurityInfo, LeshanServer server); + + void start(); + + void stop(); + + void destroy(); + +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/ServerEndpointToolbox.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/ServerEndpointToolbox.java new file mode 100644 index 0000000000..6fb3f45838 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/endpoint/ServerEndpointToolbox.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * 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.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; +import org.eclipse.leshan.server.profile.ClientProfileProvider; + +public class ServerEndpointToolbox { + + private final LwM2mDecoder decoder; + private final LwM2mEncoder encoder; + private final LwM2mLinkParser linkParser; + private final ClientProfileProvider profileProvider; + + public ServerEndpointToolbox(LwM2mDecoder decoder, LwM2mEncoder encoder, LwM2mLinkParser linkParser, + ClientProfileProvider profileProvider) { + this.decoder = decoder; + this.encoder = encoder; + this.linkParser = linkParser; + this.profileProvider = profileProvider; + } + + public LwM2mDecoder getDecoder() { + return decoder; + } + + public LwM2mEncoder getEncoder() { + return encoder; + } + + public LwM2mLinkParser getLinkParser() { + return linkParser; + } + + public ClientProfileProvider getProfileProvider() { + return profileProvider; + } +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/LwM2mNotificationReceiver.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/LwM2mNotificationReceiver.java new file mode 100644 index 0000000000..3651cdf0e4 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/LwM2mNotificationReceiver.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * 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.observation; + +import org.eclipse.leshan.core.observation.CompositeObservation; +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.SingleObservation; +import org.eclipse.leshan.core.response.ObserveCompositeResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.server.profile.ClientProfile; +import org.eclipse.leshan.server.registration.Registration; + +public interface LwM2mNotificationReceiver { + + /** + * Called when a new observation is created. + * + * @param observation the new observation. + * @param registration the related registration + */ + void newObservation(Observation observation, Registration registration); + + /** + * Called when an observation is cancelled. + * + * @param observation the cancelled observation. + */ + void cancelled(Observation observation); + + /** + * Called on new notification. + * + * @param observation the observation for which new data are received + * @param profile the client profile concerned by this observation + * @param response the lwm2m response received (successful or error response) + * + */ + void onNotification(SingleObservation observation, ClientProfile profile, ObserveResponse response); + + /** + * Called on new notification. + * + * @param observation the composite-observation for which new data are received + * @param profile the client profile concerned by this observation + * @param response the lwm2m observe-composite response received (successful or error response) + * + */ + void onNotification(CompositeObservation observation, ClientProfile profile, ObserveCompositeResponse response); + + /** + * Called when an error occurs on new notification. + * + * @param observation the observation for which new data are received + * @param profile the client profile concerned by this observation + * @param error the exception raised when we handle the notification. It can be : + *

    + *
  • InvalidResponseException if the response received is malformed.
  • + *
  • or any other RuntimeException for unexpected issue. + *
+ */ + void onError(Observation observation, ClientProfile profile, Exception error); +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java new file mode 100644 index 0000000000..b9f4f49fae --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java @@ -0,0 +1,257 @@ +/******************************************************************************* + * Copyright (c) 2016 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. + *******************************************************************************/ +package org.eclipse.leshan.server.observation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.observation.CompositeObservation; +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.SingleObservation; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.response.ObserveCompositeResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpointsProvider; +import org.eclipse.leshan.server.profile.ClientProfile; +import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationStore; +import org.eclipse.leshan.server.registration.RegistrationUpdate; +import org.eclipse.leshan.server.registration.UpdatedRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of the {@link ObservationService} accessing the persisted observation via the provided + * {@link RegistrationStore}. + * + * When a new observation is added or changed or canceled, the registered listeners are notified. + */ +public class ObservationServiceImpl implements ObservationService, LwM2mNotificationReceiver { + + private final Logger LOG = LoggerFactory.getLogger(ObservationServiceImpl.class); + + private final RegistrationStore registrationStore; + private final LwM2mServerEndpointsProvider endpointProvider; + private final boolean updateRegistrationOnNotification; + + private final List listeners = new CopyOnWriteArrayList<>();; + + /** + * Creates an instance of {@link ObservationServiceImpl} + */ + public ObservationServiceImpl(RegistrationStore store, LwM2mServerEndpointsProvider endpointProvider) { + this(store, endpointProvider, false); + } + + /** + * Creates an instance of {@link ObservationServiceImpl} + * + * @param updateRegistrationOnNotification will activate registration update on observe notification. + * + * @since 1.1 + */ + public ObservationServiceImpl(RegistrationStore store, LwM2mServerEndpointsProvider endpointProvider, + boolean updateRegistrationOnNotification) { + this.registrationStore = store; + this.updateRegistrationOnNotification = updateRegistrationOnNotification; + this.endpointProvider = endpointProvider; + } + + @Override + public int cancelObservations(Registration registration) { + // check registration id + String registrationId = registration.getId(); + if (registrationId == null) + return 0; + + Collection observations = registrationStore.removeObservations(registrationId); + if (observations == null) + return 0; + + for (Observation observation : observations) { + cancel(observation); + } + + return observations.size(); + } + + @Override + public int cancelObservations(Registration registration, String nodePath) { + if (registration == null || registration.getId() == null || nodePath == null || nodePath.isEmpty()) + return 0; + + Set observations = getObservations(registration.getId(), nodePath); + for (Observation observation : observations) { + cancelObservation(observation); + } + return observations.size(); + } + + @Override + public int cancelCompositeObservations(Registration registration, String[] nodePaths) { + if (registration == null || registration.getId() == null || nodePaths == null || nodePaths.length == 0) + return 0; + + Set observations = getCompositeObservations(registration.getId(), nodePaths); + for (Observation observation : observations) { + cancelObservation(observation); + } + return observations.size(); + } + + @Override + public void cancelObservation(Observation observation) { + if (observation == null) + return; + + registrationStore.removeObservation(observation.getRegistrationId(), observation.getId()); + cancel(observation); + } + + private void cancel(Observation observation) { + List endpoints = endpointProvider.getEndpoints(); + for (LwM2mServerEndpoint lwM2mEndpoint : endpoints) { + lwM2mEndpoint.cancelObservation(observation); + } + + for (ObservationListener listener : listeners) { + listener.cancelled(observation); + } + } + + @Override + public Set getObservations(Registration registration) { + return getObservations(registration.getId()); + } + + private Set getObservations(String registrationId) { + if (registrationId == null) + return Collections.emptySet(); + + return new HashSet<>(registrationStore.getObservations(registrationId)); + } + + private Set getCompositeObservations(String registrationId, String[] nodePaths) { + if (registrationId == null || nodePaths == null) + return Collections.emptySet(); + + // array of String to array of LWM2M path + List lwPaths = new ArrayList<>(nodePaths.length); + for (int i = 0; i < nodePaths.length; i++) { + lwPaths.add(new LwM2mPath(nodePaths[i])); + } + + // search composite-observation + Set result = new HashSet<>(); + for (Observation obs : getObservations(registrationId)) { + if (obs instanceof CompositeObservation) { + if (lwPaths.equals(((CompositeObservation) obs).getPaths())) { + result.add(obs); + } + } + } + return result; + } + + private Set getObservations(String registrationId, String nodePath) { + if (registrationId == null || nodePath == null) + return Collections.emptySet(); + + Set result = new HashSet<>(); + LwM2mPath lwPath = new LwM2mPath(nodePath); + for (Observation obs : getObservations(registrationId)) { + if (obs instanceof SingleObservation) { + if (lwPath.equals(((SingleObservation) obs).getPath())) { + result.add(obs); + } + } + } + return result; + } + + @Override + public void addListener(ObservationListener listener) { + listeners.add(listener); + } + + @Override + public void removeListener(ObservationListener listener) { + listeners.remove(listener); + } + + private void updateRegistrationOnRegistration(Observation observation, ClientProfile profile) { + if (updateRegistrationOnNotification) { + Identity obsIdentity = profile.getIdentity(); + RegistrationUpdate regUpdate = new RegistrationUpdate(observation.getRegistrationId(), obsIdentity, null, + null, null, null, null, null); + UpdatedRegistration updatedRegistration = registrationStore.updateRegistration(regUpdate); + if (updatedRegistration == null || updatedRegistration.getUpdatedRegistration() == null) { + LOG.error("Unexpected error: There is no registration with id {} for this observation {}", + observation.getRegistrationId(), observation); + return; + } + updatedRegistration.getUpdatedRegistration(); + } + } + + // ********** NotificationListener interface **********// + @Override + public void onNotification(SingleObservation observation, ClientProfile profile, ObserveResponse response) { + updateRegistrationOnRegistration(observation, profile); + for (ObservationListener listener : listeners) { + listener.onResponse(observation, profile.getRegistration(), response); + } + } + + @Override + public void onNotification(CompositeObservation observation, ClientProfile profile, + ObserveCompositeResponse response) { + updateRegistrationOnRegistration(observation, profile); + for (ObservationListener listener : listeners) { + listener.onResponse(observation, profile.getRegistration(), response); + } + } + + @Override + public void onError(Observation observation, ClientProfile profile, Exception error) { + for (ObservationListener listener : listeners) { + listener.onError(observation, profile.getRegistration(), error); + } + } + + @Override + public void newObservation(Observation observation, Registration registration) { + for (ObservationListener listener : listeners) { + listener.newObservation(observation, registration); + } + } + + @Override + public void cancelled(Observation observation) { + for (ObservationListener listener : listeners) { + listener.cancelled(observation); + } + + } +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/profile/ClientProfile.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/profile/ClientProfile.java new file mode 100644 index 0000000000..aa635b6ca2 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/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.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/profile/ClientProfileProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/profile/ClientProfileProvider.java new file mode 100644 index 0000000000..97106a1432 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/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.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/profile/DefaultClientProfileProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/profile/DefaultClientProfileProvider.java new file mode 100644 index 0000000000..2278717383 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/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.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/queue/QueueModeLwM2mRequestSender.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/QueueModeLwM2mRequestSender.java index 5b8c15260c..63394533c1 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/QueueModeLwM2mRequestSender.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/QueueModeLwM2mRequestSender.java @@ -24,23 +24,23 @@ import org.eclipse.leshan.core.response.ResponseCallback; import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.request.DownlinkRequestSender; import org.eclipse.leshan.server.request.LowerLayerConfig; -import org.eclipse.leshan.server.request.LwM2mRequestSender; /** - * A {@link LwM2mRequestSender} which supports LWM2M Queue Mode. + * A {@link DownlinkRequestSender} which supports LWM2M Queue Mode. */ -public class QueueModeLwM2mRequestSender implements LwM2mRequestSender { +public class QueueModeLwM2mRequestSender implements DownlinkRequestSender { protected PresenceServiceImpl presenceService; - protected LwM2mRequestSender delegatedSender; + protected DownlinkRequestSender delegatedSender; /** * @param presenceService the presence service object for setting the client into sleepint state when request * Timeout expires and into awake state when a response arrives. * @param delegatedSender internal sender that it is used for sending the requests, using delegation. */ - public QueueModeLwM2mRequestSender(PresenceServiceImpl presenceService, LwM2mRequestSender delegatedSender) { + public QueueModeLwM2mRequestSender(PresenceServiceImpl presenceService, DownlinkRequestSender delegatedSender) { Validate.notNull(presenceService); Validate.notNull(delegatedSender); diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/InMemoryRegistrationStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/InMemoryRegistrationStore.java similarity index 64% rename from leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/InMemoryRegistrationStore.java rename to leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/InMemoryRegistrationStore.java index 5a7e85d733..caad461541 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/InMemoryRegistrationStore.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/InMemoryRegistrationStore.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016 Sierra Wireless and others. + * 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 @@ -12,22 +12,11 @@ * * Contributors: * Sierra Wireless - initial API and implementation - * Achim Kraus (Bosch Software Innovations GmbH) - replace serialize/parse in - * unsafeGetObservation() with - * ObservationUtil.shallowClone. - * Reuse already created Key in - * setContext(). - * Achim Kraus (Bosch Software Innovations GmbH) - rename CorrelationContext to - * EndpointContext - * Achim Kraus (Bosch Software Innovations GmbH) - update to modified - * ObservationStore API - * Michał Wadowski (Orange) - Add Observe-Composite feature. *******************************************************************************/ -package org.eclipse.leshan.server.californium.registration; +package org.eclipse.leshan.server.registration; import java.net.InetSocketAddress; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -43,32 +32,22 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.Token; -import org.eclipse.californium.core.observe.ObservationStoreException; -import org.eclipse.californium.core.observe.ObservationUtil; -import org.eclipse.californium.elements.EndpointContext; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; -import org.eclipse.leshan.core.californium.ObserveUtil; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.util.NamedThreadFactory; -import org.eclipse.leshan.server.registration.Deregistration; -import org.eclipse.leshan.server.registration.ExpirationListener; -import org.eclipse.leshan.server.registration.Registration; -import org.eclipse.leshan.server.registration.RegistrationUpdate; -import org.eclipse.leshan.server.registration.UpdatedRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An in memory store for registration and observation. */ -public class InMemoryRegistrationStore implements CaliforniumRegistrationStore, Startable, Stoppable, Destroyable { +public class InMemoryRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { private final Logger LOG = LoggerFactory.getLogger(InMemoryRegistrationStore.class); // Data structure @@ -76,8 +55,8 @@ public class InMemoryRegistrationStore implements CaliforniumRegistrationStore, private final Map regsByAddr = new HashMap<>(); private final Map regsByRegId = new HashMap<>(); private final Map regsByIdentity = new HashMap<>(); - private Map obsByToken = new HashMap<>(); - private Map> tokensByRegId = new HashMap<>(); + private final Map obsByToken = new HashMap<>(); + private final Map> tokensByRegId = new HashMap<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -239,21 +218,44 @@ public Deregistration removeRegistration(String registrationId) { /* *************** Leshan Observation API **************** */ - /* - * The observation is not persisted here, it is done by the Californium layer (in the implementation of the - * org.eclipse.californium.core.observe.ObservationStore#add method) - */ @Override - public Collection addObservation(String registrationId, Observation observation) { - + public Collection addObservation(String registrationId, Observation observation, boolean addIfAbsent) { List removed = new ArrayList<>(); - try { lock.writeLock().lock(); + + if (!regsByRegId.containsKey(registrationId)) { + throw new IllegalStateException(String.format( + "can not add observation %s there is no registration with id %s", observation, registrationId)); + } + + Observation previousObservation; + ObservationIdentifier id = observation.getId(); + + if (addIfAbsent) { + if (!obsByToken.containsKey(id)) + previousObservation = obsByToken.put(id, observation); + else + previousObservation = obsByToken.get(id); + } else { + previousObservation = obsByToken.put(id, observation); + } + if (!tokensByRegId.containsKey(registrationId)) { + tokensByRegId.put(registrationId, new HashSet()); + } + tokensByRegId.get(registrationId).add(id); + + // log any collisions + if (previousObservation != null) { + removed.add(previousObservation); + LOG.warn("Token collision ? observation [{}] will be replaced by observation [{}] ", + previousObservation, observation); + } + // cancel existing observations for the same path and registration id. for (Observation obs : unsafeGetObservations(registrationId)) { - if (areTheSamePaths(observation, obs) && !Arrays.equals(observation.getId(), obs.getId())) { - unsafeRemoveObservation(new Token(obs.getId())); + if (areTheSamePaths(observation, obs) && !observation.getId().equals(obs.getId())) { + unsafeRemoveObservation(obs.getId()); removed.add(obs); } } @@ -275,13 +277,13 @@ private boolean areTheSamePaths(Observation observation, Observation obs) { } @Override - public Observation removeObservation(String registrationId, byte[] observationId) { + public Observation removeObservation(String registrationId, ObservationIdentifier observationId) { try { lock.writeLock().lock(); - Token token = new Token(observationId); - Observation observation = build(unsafeGetObservation(token)); - if (observation != null && registrationId.equals(observation.getRegistrationId())) { - unsafeRemoveObservation(token); + Observation observation = unsafeGetObservation(observationId); + if (observation != null + && (registrationId == null || registrationId.equals(observation.getRegistrationId()))) { + unsafeRemoveObservation(observationId); return observation; } return null; @@ -291,11 +293,12 @@ public Observation removeObservation(String registrationId, byte[] observationId } @Override - public Observation getObservation(String registrationId, byte[] observationId) { + public Observation getObservation(String registrationId, ObservationIdentifier observationId) { try { lock.readLock().lock(); - Observation observation = build(unsafeGetObservation(new Token(observationId))); - if (observation != null && registrationId.equals(observation.getRegistrationId())) { + Observation observation = unsafeGetObservation(observationId); + if (observation != null + && (registrationId == null || registrationId.equals(observation.getRegistrationId()))) { return observation; } return null; @@ -324,102 +327,19 @@ public Collection removeObservations(String registrationId) { } } - /* *************** Californium ObservationStore API **************** */ - - @Override - public org.eclipse.californium.core.observe.Observation putIfAbsent(Token token, - org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException { - return add(token, obs, true); - } - - @Override - public org.eclipse.californium.core.observe.Observation put(Token token, - org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException { - return add(token, obs, false); - } - - private org.eclipse.californium.core.observe.Observation add(Token token, - org.eclipse.californium.core.observe.Observation obs, boolean ifAbsent) throws ObservationStoreException { - org.eclipse.californium.core.observe.Observation previousObservation = null; - if (obs != null) { - try { - lock.writeLock().lock(); - - validateObservation(obs); - - String registrationId = ObserveUtil.extractRegistrationId(obs); - if (ifAbsent) { - if (!obsByToken.containsKey(token)) - previousObservation = obsByToken.put(token, obs); - else - return obsByToken.get(token); - } else { - previousObservation = obsByToken.put(token, obs); - } - if (!tokensByRegId.containsKey(registrationId)) { - tokensByRegId.put(registrationId, new HashSet()); - } - tokensByRegId.get(registrationId).add(token); - - // log any collisions - if (previousObservation != null) { - LOG.warn( - "Token collision ? observation from request [{}] will be replaced by observation from request [{}] ", - previousObservation.getRequest(), obs.getRequest()); - } - } finally { - lock.writeLock().unlock(); - } - } - return previousObservation; - } - - @Override - public org.eclipse.californium.core.observe.Observation get(Token token) { - try { - lock.readLock().lock(); - return unsafeGetObservation(token); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public void setContext(Token token, EndpointContext ctx) { - try { - lock.writeLock().lock(); - org.eclipse.californium.core.observe.Observation obs = obsByToken.get(token); - if (obs != null) { - obsByToken.put(token, new org.eclipse.californium.core.observe.Observation(obs.getRequest(), ctx)); - } - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public void remove(Token token) { - try { - lock.writeLock().lock(); - unsafeRemoveObservation(token); - } finally { - lock.writeLock().unlock(); - } - } - /* *************** Observation utility functions **************** */ - private org.eclipse.californium.core.observe.Observation unsafeGetObservation(Token token) { - org.eclipse.californium.core.observe.Observation obs = obsByToken.get(token); - return ObservationUtil.shallowClone(obs); + private Observation unsafeGetObservation(ObservationIdentifier token) { + Observation obs = obsByToken.get(token); + return obs; } - private void unsafeRemoveObservation(Token observationId) { - org.eclipse.californium.core.observe.Observation removed = obsByToken.remove(observationId); + private void unsafeRemoveObservation(ObservationIdentifier observationId) { + Observation removed = obsByToken.remove(observationId); if (removed != null) { - String registrationId = ObserveUtil.extractRegistrationId(removed); - Set tokens = tokensByRegId.get(registrationId); + String registrationId = removed.getRegistrationId(); + Set tokens = tokensByRegId.get(registrationId); tokens.remove(observationId); if (tokens.isEmpty()) { tokensByRegId.remove(registrationId); @@ -429,10 +349,10 @@ private void unsafeRemoveObservation(Token observationId) { private Collection unsafeRemoveAllObservations(String registrationId) { Collection removed = new ArrayList<>(); - Set tokens = tokensByRegId.get(registrationId); - if (tokens != null) { - for (Token token : tokens) { - Observation observationRemoved = build(obsByToken.remove(token)); + Set ids = tokensByRegId.get(registrationId); + if (ids != null) { + for (ObservationIdentifier id : ids) { + Observation observationRemoved = obsByToken.remove(id); if (observationRemoved != null) { removed.add(observationRemoved); } @@ -444,10 +364,10 @@ private Collection unsafeRemoveAllObservations(String registrationI private Collection unsafeGetObservations(String registrationId) { Collection result = new ArrayList<>(); - Set tokens = tokensByRegId.get(registrationId); - if (tokens != null) { - for (Token token : tokens) { - Observation obs = build(unsafeGetObservation(token)); + Set ids = tokensByRegId.get(registrationId); + if (ids != null) { + for (ObservationIdentifier id : ids) { + Observation obs = unsafeGetObservation(id); if (obs != null) { result.add(obs); } @@ -455,30 +375,6 @@ private Collection unsafeGetObservations(String registrationId) { } return result; } - - private Observation build(org.eclipse.californium.core.observe.Observation cfObs) { - if (cfObs == null) - return null; - - if (cfObs.getRequest().getCode() == CoAP.Code.GET) { - return ObserveUtil.createLwM2mObservation(cfObs.getRequest()); - } else if (cfObs.getRequest().getCode() == CoAP.Code.FETCH) { - return ObserveUtil.createLwM2mCompositeObservation(cfObs.getRequest()); - } else { - throw new IllegalStateException("Observation request can be GET or FETCH only"); - } - } - - private String validateObservation(org.eclipse.californium.core.observe.Observation observation) - throws ObservationStoreException { - String endpoint = ObserveUtil.validateCoapObservation(observation); - if (getRegistration(ObserveUtil.extractRegistrationId(observation)) == null) { - throw new ObservationStoreException("no registration for this Id"); - } - - return endpoint; - } - /* *************** Expiration handling **************** */ @Override @@ -561,9 +457,4 @@ protected boolean removeFromMap(Map map, K key, V value) { } else return false; } - - @Override - public void setExecutor(ScheduledExecutorService executor) { - // TODO sould we reuse californium executor ? - } } diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java index fd06d68d9b..a360051fa3 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java @@ -19,6 +19,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -94,6 +95,8 @@ public class Registration { private final Map applicationData; + private final URI lastEndpointUsed; + protected Registration(Builder builder) { Validate.notNull(builder.registrationId); @@ -123,6 +126,8 @@ protected Registration(Builder builder) { additionalRegistrationAttributes = builder.additionalRegistrationAttributes; applicationData = builder.applicationData; + + lastEndpointUsed = builder.lastEndpointUsed; } public String getId() { @@ -346,6 +351,10 @@ public Map getApplicationData() { return applicationData; } + public URI getLastEndpointUsed() { + return lastEndpointUsed; + } + @Override public String toString() { return String.format( @@ -490,6 +499,7 @@ public static class Builder { private Set availableInstances; private Map additionalRegistrationAttributes; private Map applicationData; + private URI lastEndpointUsed; // builder setting private boolean extractData; // if true extract data from objectLinks @@ -519,6 +529,7 @@ public Builder(Registration registration) { additionalRegistrationAttributes = registration.additionalRegistrationAttributes; applicationData = registration.applicationData; + lastEndpointUsed = registration.lastEndpointUsed; } public Builder(String registrationId, String endpoint, Identity identity) { @@ -614,6 +625,11 @@ public Builder applicationData(Map applicationData) { return this; } + public Builder lastEndpointUsed(URI lastEndpointUsed) { + this.lastEndpointUsed = lastEndpointUsed; + return this; + } + private void extractDataFromObjectLinks() { if (objectLinks != null) { diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java index 7b2f67155f..b52d5f3c62 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java @@ -17,6 +17,7 @@ *******************************************************************************/ package org.eclipse.leshan.server.registration; +import java.net.URI; import java.util.Date; import org.eclipse.leshan.core.LwM2m.LwM2mVersion; @@ -52,7 +53,8 @@ public RegistrationHandler(RegistrationServiceImpl registrationService, Authoriz this.registrationIdProvider = registrationIdProvider; } - public SendableResponse register(Identity sender, RegisterRequest registerRequest) { + public SendableResponse register(Identity sender, RegisterRequest registerRequest, + URI endpointUsed) { // Create Registration from RegisterRequest Registration.Builder builder = new Registration.Builder( @@ -63,7 +65,9 @@ public SendableResponse register(Identity sender, RegisterRequ .lifeTimeInSec(registerRequest.getLifetime()).bindingMode(registerRequest.getBindingMode()) .queueMode(registerRequest.getQueueMode()).objectLinks(registerRequest.getObjectLinks()) .smsNumber(registerRequest.getSmsNumber()).registrationDate(new Date()).lastUpdate(new Date()) - .additionalRegistrationAttributes(registerRequest.getAdditionalAttributes()); + .additionalRegistrationAttributes(registerRequest.getAdditionalAttributes()) + .lastEndpointUsed(endpointUsed); + Registration registrationToApproved = builder.build(); // We check if the client get authorization. diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationStore.java index ed6551a44b..1abe694a88 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationStore.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationStore.java @@ -20,6 +20,7 @@ import java.util.Iterator; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.request.Identity; /** @@ -105,17 +106,17 @@ public interface RegistrationStore { * * @return the list of removed observations or an empty list if none were removed. */ - Collection addObservation(String registrationId, Observation observation); + Collection addObservation(String registrationId, Observation observation, boolean addIfAbsent); /** * Get the observation for the given registration with the given observationId */ - Observation getObservation(String registrationId, byte[] observationId); + Observation getObservation(String registrationId, ObservationIdentifier observationId); /** * Remove the observation for the given registration with the given observationId */ - Observation removeObservation(String registrationId, byte[] observationId); + Observation removeObservation(String registrationId, ObservationIdentifier observationId); /** * Get all observations for the given registrationId @@ -131,4 +132,5 @@ public interface RegistrationStore { * set a listener for registration expiration. */ void setExpirationListener(ExpirationListener listener); + } diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java index 069d8ef90b..f56eacb716 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java @@ -101,10 +101,10 @@ public Registration update(Registration registration) { .additionalRegistrationAttributes(additionalAttributes).rootPath(registration.getRootPath()) .supportedContentFormats(registration.getSupportedContentFormats()) .supportedObjects(registration.getSupportedObject()) - .availableInstances(registration.getAvailableInstances()).applicationData(applicationData); + .availableInstances(registration.getAvailableInstances()).applicationData(applicationData) + .lastEndpointUsed(registration.getLastEndpointUsed()); return builder.build(); - } public String getRegistrationId() { diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DefaultDownlinkRequestSender.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DefaultDownlinkRequestSender.java new file mode 100644 index 0000000000..9c8b47ffda --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DefaultDownlinkRequestSender.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * 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.request; + +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.node.codec.CodecException; +import org.eclipse.leshan.core.request.DownlinkRequest; +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.core.util.Validate; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpointsProvider; +import org.eclipse.leshan.server.model.LwM2mModelProvider; +import org.eclipse.leshan.server.profile.ClientProfile; +import org.eclipse.leshan.server.registration.Registration; + +/** + * The default implementation of {@link DownlinkRequestSender}. + */ +public class DefaultDownlinkRequestSender implements DownlinkRequestSender { + + private final LwM2mModelProvider modelProvider; + private final LwM2mServerEndpointsProvider endpointsProvider; + + /** + * @param endpointsProvider which provides available {@link LwM2mServerEndpoint} + * @param modelProvider the {@link LwM2mModelProvider} used retrieve the {@link LwM2mModel} used to encode/decode + * {@link LwM2mNode}. + */ + public DefaultDownlinkRequestSender(LwM2mServerEndpointsProvider endpointsProvider, + LwM2mModelProvider modelProvider) { + Validate.notNull(modelProvider); + this.modelProvider = modelProvider; + this.endpointsProvider = endpointsProvider; + } + + /** + * 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 Registration} associate to the device we want to sent the request. + * @param request The request to send to the client. + * @param lowerLayerConfig to tweak lower layer request (e.g. coap request) + * @param timeoutInMs The global timeout to wait in milliseconds (see + * https://github.com/eclipse/leshan/wiki/Request-Timeout) + * @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). + */ + @Override + public T send(Registration destination, DownlinkRequest request, + LowerLayerConfig lowerLayerConfig, long timeoutInMs) throws InterruptedException { + + // find endpoint to use + LwM2mServerEndpoint endpoint = endpointsProvider.getEndpoint(destination.getLastEndpointUsed()); + + // Retrieve the objects definition + final LwM2mModel model = modelProvider.getObjectModel(destination); + + // Send requests synchronously + T response = endpoint.send(new ClientProfile(destination, model), request, lowerLayerConfig, timeoutInMs); + return response; + } + + /** + * Send a Lightweight M2M {@link DownlinkRequest} asynchronously to a LWM2M client. + * + * The Californium API does not ensure that message callback are exclusive. E.g. In some race condition, you can get + * a onReponse call and a onCancel one. This method ensures that you will receive only one event. Meaning, you get + * either 1 response or 1 error. + * + * @param destination The {@link Registration} associate to the device we want to sent the request. + * @param request The request to send to the client. + * @param lowerLayerConfig to tweak lower layer request (e.g. coap request) + * @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 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 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. + */ + @Override + public void send(final Registration destination, DownlinkRequest request, + LowerLayerConfig lowerLayerConfig, long timeoutInMs, final ResponseCallback responseCallback, + ErrorCallback errorCallback) { + + // find endpoint to use + LwM2mServerEndpoint endpoint = endpointsProvider.getEndpoint(destination.getLastEndpointUsed()); + + // Retrieve the objects definition + final LwM2mModel model = modelProvider.getObjectModel(destination); + + // Send requests asynchronously + endpoint.send(new ClientProfile(destination, model), request, new ResponseCallback() { + @Override + public void onResponse(T response) { + responseCallback.onResponse(response); + } + }, errorCallback, lowerLayerConfig, timeoutInMs); + } + + @Override + public void cancelOngoingRequests(Registration registration) { + for (LwM2mServerEndpoint endpoint : endpointsProvider.getEndpoints()) { + endpoint.cancelRequests(registration.getId()); + } + } +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DefaultUplinkRequestReceiver.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DefaultUplinkRequestReceiver.java new file mode 100644 index 0000000000..23e02c01d6 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DefaultUplinkRequestReceiver.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * 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.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.profile.ClientProfile; +import org.eclipse.leshan.server.registration.RegistrationHandler; +import org.eclipse.leshan.server.send.SendHandler; + +public class DefaultUplinkRequestReceiver implements UplinkRequestReceiver { + + private final RegistrationHandler registrationHandler; + private final SendHandler sendHandler; + + public DefaultUplinkRequestReceiver(RegistrationHandler registrationHandler, SendHandler sendHandler) { + this.registrationHandler = registrationHandler; + this.sendHandler = sendHandler; + } + + @Override + public void onError(Identity senderIdentity, ClientProfile senderProfile, Exception exception, + Class> requestType, URI serverEndpointUri) { + if (requestType.equals(SendRequest.class)) { + sendHandler.onError(senderProfile.getRegistration(), exception); + } + } + + @Override + public SendableResponse requestReceived(Identity senderIdentity, + ClientProfile senderProfile, UplinkRequest request, URI serverEndpointUri) { + + RequestHandler requestHandler = new RequestHandler(senderIdentity, senderProfile, serverEndpointUri); + request.accept(requestHandler); + return requestHandler.getResponse(); + } + + public class RequestHandler implements UplinkRequestVisitor { + + private final Identity senderIdentity; + private final ClientProfile senderProfile; + private final URI endpoint; + private SendableResponse response; + + public RequestHandler(Identity senderIdentity, ClientProfile clientProfile, URI serverEndpointUri) { + this.senderIdentity = senderIdentity; + this.senderProfile = clientProfile; + this.endpoint = serverEndpointUri; + } + + @Override + public void visit(RegisterRequest request) { + response = registrationHandler.register(senderIdentity, request, endpoint); + } + + @Override + public void visit(UpdateRequest request) { + response = registrationHandler.update(senderIdentity, request); + + } + + @Override + public void visit(DeregisterRequest request) { + response = registrationHandler.deregister(senderIdentity, request); + } + + @Override + public void visit(BootstrapRequest request) { + // Not implemented. + } + + @Override + public void visit(SendRequest request) { + response = sendHandler.handleSend(senderProfile.getRegistration(), request); + } + + @SuppressWarnings("unchecked") + public SendableResponse getResponse() { + return (SendableResponse) response; + } + } + +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/LwM2mRequestSender.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DownlinkRequestSender.java similarity index 97% rename from leshan-server-core/src/main/java/org/eclipse/leshan/server/request/LwM2mRequestSender.java rename to leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DownlinkRequestSender.java index b0b96fc25e..a3479fe335 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/LwM2mRequestSender.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/DownlinkRequestSender.java @@ -31,9 +31,10 @@ import org.eclipse.leshan.server.registration.Registration; /** - * A {@link LwM2mRequestSender} is responsible to send LWM2M {@link DownlinkRequest} for a given {@link Registration}. + * A {@link DownlinkRequestSender} is responsible to send LWM2M {@link DownlinkRequest} for a given + * {@link Registration}. */ -public interface LwM2mRequestSender { +public interface DownlinkRequestSender { /** * Send a Lightweight M2M request synchronously. Will block until a response is received from the remote server. diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/UplinkRequestReceiver.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/UplinkRequestReceiver.java new file mode 100644 index 0000000000..b756839b8b --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/UplinkRequestReceiver.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.server.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; +import org.eclipse.leshan.server.profile.ClientProfile; + +public interface UplinkRequestReceiver { + + SendableResponse requestReceived(Identity senderIdentity, ClientProfile senderProfile, + UplinkRequest request, URI serverEndpointUri); + + void onError(Identity senderIdentity, ClientProfile senderProfile, Exception exception, + Class> requestType, URI serverEndpointUri); +} diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/ServerSecurityInfo.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/ServerSecurityInfo.java new file mode 100644 index 0000000000..28ca3cab87 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/ServerSecurityInfo.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * 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.security; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +public class ServerSecurityInfo { + + private PrivateKey privateKey; + private PublicKey publicKey; + private X509Certificate[] certificateChain; + private Certificate[] trustedCertificates; + + public ServerSecurityInfo(PrivateKey privateKey, PublicKey publicKey, X509Certificate[] certificateChain, + Certificate[] trustedCertificates) { + this.privateKey = privateKey; + this.publicKey = publicKey; + this.certificateChain = certificateChain; + this.trustedCertificates = trustedCertificates; + } + + public PrivateKey getPrivateKey() { + return privateKey; + } + + public PublicKey getPublicKey() { + return publicKey; + } + + public X509Certificate[] getCertificateChain() { + return certificateChain; + } + + public Certificate[] getTrustedCertificates() { + return trustedCertificates; + } +} diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/InMemoryRegistrationStoreTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/InMemoryRegistrationStoreTest.java new file mode 100644 index 0000000000..bed33986a5 --- /dev/null +++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/InMemoryRegistrationStoreTest.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * 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) - Improved compliance with rfc6690. + *******************************************************************************/ +package org.eclipse.leshan.server.registration; + +import static org.junit.Assert.assertEquals; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.EnumSet; + +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.request.BindingMode; +import org.eclipse.leshan.core.request.Identity; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class InMemoryRegistrationStoreTest { + + private final String ep = "urn:endpoint"; + private final int port = 23452; + private final Long lifetime = 10000L; + private final String sms = "0171-32423545"; + private final EnumSet binding = EnumSet.of(BindingMode.U, BindingMode.Q, BindingMode.S); + private final Link[] objectLinks = new Link[] { new Link("/3") }; + private final String registrationId = "4711"; + + RegistrationStore store; + InetAddress address; + Registration registration; + + @Before + public void setUp() throws UnknownHostException { + address = InetAddress.getLocalHost(); + store = new InMemoryRegistrationStore(); + } + + @Test + public void update_registration_keeps_properties_unchanged() { + givenASimpleRegistration(lifetime); + store.addRegistration(registration); + + RegistrationUpdate update = new RegistrationUpdate(registrationId, Identity.unsecure(address, port), null, null, + null, null, null, null); + UpdatedRegistration updatedRegistration = store.updateRegistration(update); + assertEquals(lifetime, updatedRegistration.getUpdatedRegistration().getLifeTimeInSec()); + Assert.assertSame(binding, updatedRegistration.getUpdatedRegistration().getBindingMode()); + assertEquals(sms, updatedRegistration.getUpdatedRegistration().getSmsNumber()); + + assertEquals(registration, updatedRegistration.getPreviousRegistration()); + + Registration reg = store.getRegistrationByEndpoint(ep); + assertEquals(lifetime, reg.getLifeTimeInSec()); + Assert.assertSame(binding, reg.getBindingMode()); + assertEquals(sms, reg.getSmsNumber()); + } + + @Test + public void client_registration_sets_time_to_live() { + givenASimpleRegistration(lifetime); + store.addRegistration(registration); + Assert.assertTrue(registration.isAlive()); + } + + @Test + public void update_registration_to_extend_time_to_live() { + givenASimpleRegistration(0L); + store.addRegistration(registration); + Assert.assertFalse(registration.isAlive()); + + RegistrationUpdate update = new RegistrationUpdate(registrationId, Identity.unsecure(address, port), lifetime, + null, null, null, null, null); + UpdatedRegistration updatedRegistration = store.updateRegistration(update); + Assert.assertTrue(updatedRegistration.getUpdatedRegistration().isAlive()); + + Registration reg = store.getRegistrationByEndpoint(ep); + Assert.assertTrue(reg.isAlive()); + } + + private void givenASimpleRegistration(Long lifetime) { + + Registration.Builder builder = new Registration.Builder(registrationId, ep, Identity.unsecure(address, port)); + + registration = builder.lifeTimeInSec(lifetime).smsNumber(sms).bindingMode(binding).objectLinks(objectLinks) + .build(); + } +} diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/registration/RegistrationHandlerTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java similarity index 91% rename from leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/registration/RegistrationHandlerTest.java rename to leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java index 3f692cd29a..9f1f01a14a 100644 --- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/registration/RegistrationHandlerTest.java +++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java @@ -13,16 +13,18 @@ * Contributors: * Sierra Wireless - initial API and implementation *******************************************************************************/ -package org.eclipse.leshan.server.californium.registration; +package org.eclipse.leshan.server.registration; import static org.junit.Assert.assertEquals; import java.net.InetSocketAddress; +import java.net.URI; import java.net.UnknownHostException; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import org.eclipse.leshan.core.endpoint.EndpointUriUtil; import org.eclipse.leshan.core.link.DefaultLinkParser; import org.eclipse.leshan.core.link.LinkParseException; import org.eclipse.leshan.core.request.BindingMode; @@ -30,11 +32,6 @@ import org.eclipse.leshan.core.request.RegisterRequest; import org.eclipse.leshan.core.request.UpdateRequest; import org.eclipse.leshan.core.request.UplinkRequest; -import org.eclipse.leshan.server.registration.RandomStringRegistrationIdProvider; -import org.eclipse.leshan.server.registration.Registration; -import org.eclipse.leshan.server.registration.RegistrationHandler; -import org.eclipse.leshan.server.registration.RegistrationServiceImpl; -import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.security.Authorization; import org.eclipse.leshan.server.security.Authorizer; import org.junit.Before; @@ -65,7 +62,8 @@ public void test_application_data_from_authorizer() { authorizer.willReturn(Authorization.approved(appData)); // handle REGISTER request - registrationHandler.register(givenIdenity(), givenRegisterRequestWithEndpoint("myEndpoint")); + registrationHandler.register(givenIdenity(), givenRegisterRequestWithEndpoint("myEndpoint"), + givenServerEndpointUri()); // check result Registration registration = registrationStore.getRegistrationByEndpoint("myEndpoint"); @@ -95,7 +93,8 @@ public void test_update_without_application_data_from_authorizer() { authorizer.willReturn(Authorization.approved(appData)); // handle REGISTER request - registrationHandler.register(givenIdenity(), givenRegisterRequestWithEndpoint("myEndpoint")); + registrationHandler.register(givenIdenity(), givenRegisterRequestWithEndpoint("myEndpoint"), + givenServerEndpointUri()); // check result Registration registration = registrationStore.getRegistrationByEndpoint("myEndpoint"); @@ -116,6 +115,10 @@ private Identity givenIdenity() { return Identity.unsecure(new InetSocketAddress(0)); } + private URI givenServerEndpointUri() { + return EndpointUriUtil.createUri("coap", "localhost", 5683); + } + private RegisterRequest givenRegisterRequestWithEndpoint(String endpoint) { try { return new RegisterRequest(endpoint, 3600l, "1.1", EnumSet.of(BindingMode.U), false, null, diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java index fc526e0114..94f4234c32 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java @@ -21,6 +21,7 @@ import java.io.PrintWriter; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.URI; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.List; @@ -33,17 +34,26 @@ import org.eclipse.californium.elements.util.CertPathUtil; 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.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.LwM2mDemoConstant; 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.californium.LeshanServer; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; +import org.eclipse.leshan.server.LeshanServer; +import org.eclipse.leshan.server.LeshanServerBuilder; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointFactory; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider; +import org.eclipse.leshan.server.californium.endpoint.ServerProtocolProvider; +import org.eclipse.leshan.server.californium.endpoint.coap.CoapOscoreServerEndpointFactory; +import org.eclipse.leshan.server.californium.endpoint.coap.CoapServerProtocolProvider; +import org.eclipse.leshan.server.californium.endpoint.coaps.CoapsServerEndpointFactory; +import org.eclipse.leshan.server.californium.endpoint.coaps.CoapsServerProtocolProvider; import org.eclipse.leshan.server.core.demo.json.servlet.SecurityServlet; import org.eclipse.leshan.server.demo.cli.LeshanServerDemoCLI; import org.eclipse.leshan.server.demo.servlet.ClientServlet; @@ -136,49 +146,95 @@ public static LeshanServer createLeshanServer(LeshanServerDemoCLI cli) throws Ex // Prepare LWM2M server LeshanServerBuilder builder = new LeshanServerBuilder(); - // Create CoAP Config - File configFile = new File(CF_CONFIGURATION_FILENAME); - Configuration coapConfig = LeshanServerBuilder.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); + // Define model provider + List models = ObjectLoader.loadAllDefault(); + models.addAll(ObjectLoader.loadDdfResources("/models/", LwM2mDemoConstant.modelPaths)); + if (cli.main.modelsFolder != null) { + models.addAll(ObjectLoader.loadObjectsFromDir(cli.main.modelsFolder, true)); } - 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); + LwM2mModelProvider modelProvider = new VersionedModelProvider(models); + builder.setObjectModelProvider(modelProvider); + + // Set securityStore & registrationStore + EditableSecurityStore securityStore; + if (cli.main.redis == null) { + // use file persistence + securityStore = new FileSecurityStore(); + } else { + // use Redis Store + securityStore = new RedisSecurityStore(cli.main.redis); + builder.setRegistrationStore(new RedisRegistrationStore(cli.main.redis)); } - // Add MDC for connection logs - if (cli.helpsOptions.getVerboseLevel() > 0) - dtlsConfig.setConnectionListener(new PrincipalMdcConnectionListener()); + builder.setSecurityStore(securityStore); if (cli.identity.isx509()) { // use X.509 mode (+ RPK) builder.setPrivateKey(cli.identity.getPrivateKey()); builder.setCertificateChain(cli.identity.getCertChain()); + // Define trust store + List trustStore = cli.identity.getTrustStore(); + builder.setTrustedCertificates(trustStore.toArray(new Certificate[trustStore.size()])); + } else if (cli.identity.isRPK()) { + // use RPK only + builder.setPublicKey(cli.identity.getPublicKey()); + builder.setPrivateKey(cli.identity.getPrivateKey()); + } + + // Create Californium Endpoints Provider: + // ------------------ + // Create Custom CoAPS protocol provider to add MDC logger : + ServerProtocolProvider coapsProtocolProvider = new CoapsServerProtocolProvider() { + @Override + public CaliforniumServerEndpointFactory createDefaultEndpointFactory(URI uri) { + return new CoapsServerEndpointFactory(uri) { + + @Override + protected Builder createDtlsConnectorConfigBuilder(Configuration endpointConfiguration) { + Builder dtlsConfigBuilder = super.createDtlsConnectorConfigBuilder(endpointConfiguration); + + // Add MDC for connection logs + if (cli.helpsOptions.getVerboseLevel() > 0) + dtlsConfigBuilder.setConnectionListener(new PrincipalMdcConnectionListener()); + + return dtlsConfigBuilder; + } + }; + } + }; + + // Create Server Endpoints Provider + CaliforniumServerEndpointsProvider.Builder endpointsBuilder = new CaliforniumServerEndpointsProvider.Builder( + new CoapServerProtocolProvider(), 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.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !cli.dtls.supportDeprecatedCiphers); + serverCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); + if (cli.dtls.cid != null) { + serverCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cli.dtls.cid); + } + + // Persist configuration + File configFile = new File(CF_CONFIGURATION_FILENAME); + if (configFile.isFile()) { + serverCoapConfig.load(configFile); + } else { + serverCoapConfig.store(configFile, CF_CONFIGURATION_HEADER); + } + + // Enforce DTLS role to ServerOnly if needed + if (cli.identity.isx509()) { X509Certificate serverCertificate = cli.identity.getCertChain()[0]; - // autodetect serverOnly - if (dtlsConfig.getIncompleteConfig().get(DtlsConfig.DTLS_ROLE) == DtlsRole.BOTH) { + if (serverCoapConfig.get(DtlsConfig.DTLS_ROLE) == DtlsRole.BOTH) { if (serverCertificate != null) { if (CertPathUtil.canBeUsedForAuthentication(serverCertificate, false)) { if (!CertPathUtil.canBeUsedForAuthentication(serverCertificate, true)) { - dtlsConfig.set(DtlsConfig.DTLS_ROLE, DtlsRole.SERVER_ONLY); + serverCoapConfig.set(DtlsConfig.DTLS_ROLE, DtlsRole.SERVER_ONLY); LOG.warn("Server certificate does not allow Client Authentication usage." + "\nThis will prevent this LWM2M server to initiate DTLS connection." + "\nSee : https://github.com/eclipse/leshan/wiki/Server-Failover#about-connections"); @@ -186,46 +242,31 @@ public static LeshanServer createLeshanServer(LeshanServerDemoCLI cli) throws Ex } } } - - // Define trust store - List trustStore = cli.identity.getTrustStore(); - builder.setTrustedCertificates(trustStore.toArray(new Certificate[trustStore.size()])); - } else if (cli.identity.isRPK()) { - // use RPK only - builder.setPublicKey(cli.identity.getPublicKey()); - builder.setPrivateKey(cli.identity.getPrivateKey()); } - // Set DTLS Config - builder.setDtlsConfig(dtlsConfig); + // Set Californium Configuration + endpointsBuilder.setConfiguration(serverCoapConfig); - // Define model provider - List models = ObjectLoader.loadAllDefault(); - models.addAll(ObjectLoader.loadDdfResources("/models/", LwM2mDemoConstant.modelPaths)); - if (cli.main.modelsFolder != null) { - models.addAll(ObjectLoader.loadObjectsFromDir(cli.main.modelsFolder, true)); - } - LwM2mModelProvider modelProvider = new VersionedModelProvider(models); - builder.setObjectModelProvider(modelProvider); - - // Set securityStore & registrationStore - EditableSecurityStore securityStore; - if (cli.main.redis == null) { - // use file persistence - securityStore = new FileSecurityStore(); + // 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 { - // use Redis Store - securityStore = new RedisSecurityStore(cli.main.redis); - builder.setRegistrationStore(new RedisRegistrationStore(cli.main.redis)); + endpointsBuilder.addEndpoint(new CoapOscoreServerEndpointFactory( + EndpointUriUtil.createUri(Protocol.COAP.getUriScheme(), coapAddr))); } - builder.setSecurityStore(securityStore); - // TODO OSCORE Temporary cli option to deactivate OSCORE - if (!cli.main.disableOscore) { - builder.setEnableOscore(true); - } + // 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(); } @@ -245,7 +286,8 @@ private static Server createJettyServer(LeshanServerDemoCLI cli, LeshanServer lw server.setHandler(root); // Create Servlet - EventServlet eventServlet = new EventServlet(lwServer, lwServer.getSecuredAddress().getPort()); + // EventServlet eventServlet = new EventServlet(lwServer, lwServer.getSecuredAddress().getPort()); + EventServlet eventServlet = new EventServlet(lwServer, 5684); ServletHolder eventServletHolder = new ServletHolder(eventServlet); root.addServlet(eventServletHolder, "/api/event/*"); diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ClientServlet.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ClientServlet.java index 1b0c927e15..3ef4369576 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ClientServlet.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ClientServlet.java @@ -73,7 +73,7 @@ import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteCompositeResponse; import org.eclipse.leshan.core.response.WriteResponse; -import org.eclipse.leshan.server.californium.LeshanServer; +import org.eclipse.leshan.server.LeshanServer; import org.eclipse.leshan.server.demo.servlet.json.JacksonLinkSerializer; import org.eclipse.leshan.server.demo.servlet.json.JacksonLwM2mNodeDeserializer; import org.eclipse.leshan.server.demo.servlet.json.JacksonLwM2mNodeSerializer; diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java index d4df0de952..341b0ab5c0 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/EventServlet.java @@ -27,7 +27,6 @@ import javax.servlet.http.HttpServletRequest; -import org.eclipse.californium.core.network.Endpoint; import org.eclipse.jetty.servlets.EventSource; import org.eclipse.jetty.servlets.EventSourceServlet; import org.eclipse.leshan.core.link.Link; @@ -40,13 +39,15 @@ import org.eclipse.leshan.core.request.SendRequest; import org.eclipse.leshan.core.response.ObserveCompositeResponse; import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.server.californium.LeshanServer; +import org.eclipse.leshan.server.LeshanServer; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpoint; import org.eclipse.leshan.server.demo.servlet.json.JacksonLinkSerializer; import org.eclipse.leshan.server.demo.servlet.json.JacksonLwM2mNodeSerializer; import org.eclipse.leshan.server.demo.servlet.json.JacksonRegistrationSerializer; import org.eclipse.leshan.server.demo.servlet.log.CoapMessage; import org.eclipse.leshan.server.demo.servlet.log.CoapMessageListener; import org.eclipse.leshan.server.demo.servlet.log.CoapMessageTracer; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint; import org.eclipse.leshan.server.observation.ObservationListener; import org.eclipse.leshan.server.queue.PresenceListener; import org.eclipse.leshan.server.registration.Registration; @@ -92,7 +93,7 @@ public class EventServlet extends EventSourceServlet { private final CoapMessageTracer coapMessageTracer; - private Set eventSources = Collections + private final Set eventSources = Collections .newSetFromMap(new ConcurrentHashMap()); private final RegistrationListener registrationListener = new RegistrationListener() { @@ -288,8 +289,9 @@ public EventServlet(LeshanServer server, int securePort) { // add an interceptor to each endpoint to trace all CoAP messages coapMessageTracer = new CoapMessageTracer(server.getRegistrationService()); - for (Endpoint endpoint : server.coap().getServer().getEndpoints()) { - endpoint.addInterceptor(coapMessageTracer); + for (LwM2mServerEndpoint endpoint : server.getEndpoints()) { + if (endpoint instanceof CaliforniumServerEndpoint) + ((CaliforniumServerEndpoint) endpoint).getCoapEndpoint().addInterceptor(coapMessageTracer); } ObjectMapper mapper = new ObjectMapper(); @@ -354,7 +356,7 @@ protected EventSource newEventSource(HttpServletRequest req) { private class LeshanEventSource implements EventSource { - private String endpoint; + private final String endpoint; private Emitter emitter; public LeshanEventSource(String endpoint) { @@ -365,9 +367,11 @@ public LeshanEventSource(String endpoint) { public void onOpen(Emitter emitter) throws IOException { this.emitter = emitter; eventSources.add(this); + if (endpoint != null) { coapMessageTracer.addListener(endpoint, new ClientCoapListener(endpoint)); } + } @Override diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ServerServlet.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ServerServlet.java index 18e60a87de..4331142077 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ServerServlet.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/ServerServlet.java @@ -27,9 +27,11 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; -import org.eclipse.leshan.server.californium.LeshanServer; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.server.LeshanServer; import org.eclipse.leshan.server.core.demo.json.PublicKeySerDes; import org.eclipse.leshan.server.core.demo.json.X509CertificateSerDes; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -84,14 +86,23 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se return; } + // search coap and coaps port + Integer coapPort = null; + Integer coapsPort = null; + for (LwM2mServerEndpoint 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-server-redis/pom.xml b/leshan-server-redis/pom.xml index 2690812579..9210b4a937 100644 --- a/leshan-server-redis/pom.xml +++ b/leshan-server-redis/pom.xml @@ -33,7 +33,7 @@ Contributors: org.eclipse.leshan - leshan-server-cf + leshan-server-core redis.clients diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisRegistrationStore.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisRegistrationStore.java index b89ccfb29b..a44ceca919 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisRegistrationStore.java +++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisRegistrationStore.java @@ -35,27 +35,23 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.Token; -import org.eclipse.californium.core.observe.ObservationStoreException; -import org.eclipse.californium.elements.EndpointContext; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; -import org.eclipse.leshan.core.californium.ObserveUtil; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.Validate; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.redis.serialization.IdentitySerDes; import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.registration.Deregistration; import org.eclipse.leshan.server.registration.ExpirationListener; import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.UpdatedRegistration; import org.slf4j.Logger; @@ -70,7 +66,7 @@ /** * A RegistrationStore which stores registrations and observations in Redis. */ -public class RedisRegistrationStore implements CaliforniumRegistrationStore, Startable, Stoppable, Destroyable { +public class RedisRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { /** Default time in seconds between 2 cleaning tasks (used to remove expired registration). */ public static final long DEFAULT_CLEAN_PERIOD = 60; @@ -322,8 +318,8 @@ public Iterator getAllRegistrations() { protected class RedisIterator implements Iterator { - private Pool pool; - private ScanParams scanParams; + private final Pool pool; + private final ScanParams scanParams; private String cursor; private List scanResult; @@ -491,12 +487,8 @@ private Registration deserializeReg(byte[] data) { /* *************** Leshan Observation API **************** */ - /* - * The observation is not persisted here, it is done by the Californium layer (in the implementation of the - * org.eclipse.californium.core.observe.ObservationStore#add method) - */ @Override - public Collection addObservation(String registrationId, Observation observation) { + public Collection addObservation(String registrationId, Observation observation, boolean addIfAbsent) { List removed = new ArrayList<>(); try (Jedis j = pool.getResource()) { @@ -504,18 +496,41 @@ public Collection addObservation(String registrationId, Observation // fetch the client ep by registration ID index byte[] ep = j.get(toRegIdKey(registrationId)); if (ep == null) { - return null; + throw new IllegalStateException(String.format( + "can not add observation %s there is no registration with id %s", observation, registrationId)); } byte[] lockValue = null; byte[] lockKey = toLockKey(ep); - try { lockValue = lock.acquire(j, lockKey); + // Add and Get previous observation + byte[] previousValue; + byte[] key = toKey(OBS_TKN, observation.getId().getBytes()); + byte[] serializeObs = serializeObs(observation); + if (addIfAbsent) { + previousValue = j.get(key); + if (previousValue == null || previousValue.length == 0) { + j.set(key, serializeObs); + } + } else { + previousValue = j.getSet(key, serializeObs); + } + + // secondary index to get the list by registrationId + j.lpush(toKey(OBS_TKNS_REGID_IDX, registrationId), observation.getId().getBytes()); + + // log any collisions + Observation previousObservation; + if (previousValue != null && previousValue.length != 0) { + previousObservation = deserializeObs(previousValue); + LOG.warn("Token collision ? observation [{}] will be replaced by observation [{}] ", + previousObservation, observation); + } // cancel existing observations for the same path and registration id. - for (Observation obs : getObservations(j, registrationId)) { - if (areTheSamePaths(observation, obs) && !Arrays.equals(observation.getId(), obs.getId())) { + for (Observation obs : unsafeGetObservations(j, registrationId)) { + if (areTheSamePaths(observation, obs) && !observation.getId().equals(obs.getId())) { removed.add(obs); unsafeRemoveObservation(j, registrationId, obs.getId()); } @@ -539,7 +554,7 @@ private boolean areTheSamePaths(Observation observation, Observation obs) { } @Override - public Observation removeObservation(String registrationId, byte[] observationId) { + public Observation removeObservation(String registrationId, ObservationIdentifier observationId) { try (Jedis j = pool.getResource()) { // fetch the client ep by registration ID index @@ -554,8 +569,9 @@ public Observation removeObservation(String registrationId, byte[] observationId try { lockValue = lock.acquire(j, lockKey); - Observation observation = build(get(new Token(observationId))); - if (observation != null && registrationId.equals(observation.getRegistrationId())) { + Observation observation = unsafeGetObservation(j, observationId); + if (observation != null + && (registrationId == null || registrationId.equals(observation.getRegistrationId()))) { unsafeRemoveObservation(j, registrationId, observationId); return observation; } @@ -568,28 +584,24 @@ public Observation removeObservation(String registrationId, byte[] observationId } @Override - public Observation getObservation(String registrationId, byte[] observationId) { - return build(get(new Token(observationId))); + public Observation getObservation(String registrationId, ObservationIdentifier observationId) { + try (Jedis j = pool.getResource()) { + Observation observation = unsafeGetObservation(j, observationId); + if (observation != null + && (registrationId == null || registrationId.equals(observation.getRegistrationId()))) { + return observation; + } + return null; + } } @Override public Collection getObservations(String registrationId) { try (Jedis j = pool.getResource()) { - return getObservations(j, registrationId); + return unsafeGetObservations(j, registrationId); } } - private Collection getObservations(Jedis j, String registrationId) { - Collection result = new ArrayList<>(); - for (byte[] token : j.lrange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) { - byte[] obs = j.get(toKey(OBS_TKN, token)); - if (obs != null) { - result.add(build(deserializeObs(obs))); - } - } - return result; - } - @Override public Collection removeObservations(String registrationId) { try (Jedis j = pool.getResource()) { @@ -612,110 +624,6 @@ public Collection removeObservations(String registrationId) { } } - /* *************** Californium ObservationStore API **************** */ - - @Override - public org.eclipse.californium.core.observe.Observation putIfAbsent(Token token, - org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException { - return add(token, obs, true); - } - - @Override - public org.eclipse.californium.core.observe.Observation put(Token token, - org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException { - return add(token, obs, false); - } - - private org.eclipse.californium.core.observe.Observation add(Token token, - org.eclipse.californium.core.observe.Observation obs, boolean ifAbsent) throws ObservationStoreException { - String endpoint = ObserveUtil.validateCoapObservation(obs); - org.eclipse.californium.core.observe.Observation previousObservation = null; - - try (Jedis j = pool.getResource()) { - byte[] lockValue = null; - byte[] lockKey = toKey(LOCK_EP, endpoint); - try { - lockValue = lock.acquire(j, lockKey); - - String registrationId = ObserveUtil.extractRegistrationId(obs); - if (!j.exists(toRegIdKey(registrationId))) - throw new ObservationStoreException("no registration for this Id"); - byte[] key = toKey(OBS_TKN, obs.getRequest().getToken().getBytes()); - byte[] serializeObs = serializeObs(obs); - byte[] previousValue = null; - if (ifAbsent) { - previousValue = j.get(key); - if (previousValue == null || previousValue.length == 0) { - j.set(key, serializeObs); - } else { - return deserializeObs(previousValue); - } - } else { - previousValue = j.getSet(key, serializeObs); - } - - // secondary index to get the list by registrationId - j.lpush(toKey(OBS_TKNS_REGID_IDX, registrationId), obs.getRequest().getToken().getBytes()); - - // log any collisions - if (previousValue != null && previousValue.length != 0) { - previousObservation = deserializeObs(previousValue); - LOG.warn( - "Token collision ? observation from request [{}] will be replaced by observation from request [{}] ", - previousObservation.getRequest(), obs.getRequest()); - } - } finally { - lock.release(j, lockKey, lockValue); - } - } - return previousObservation; - } - - @Override - public void remove(Token token) { - try (Jedis j = pool.getResource()) { - byte[] tokenKey = toKey(OBS_TKN, token.getBytes()); - - // fetch the observation by token - byte[] serializedObs = j.get(tokenKey); - if (serializedObs == null) - return; - - org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs); - String registrationId = ObserveUtil.extractRegistrationId(obs); - Registration registration = getRegistration(j, registrationId); - if (registration == null) { - LOG.warn("Unable to remove observation {}, registration {} does not exist anymore", obs.getRequest(), - registrationId); - return; - } - - String endpoint = registration.getEndpoint(); - byte[] lockValue = null; - byte[] lockKey = toKey(LOCK_EP, endpoint); - try { - lockValue = lock.acquire(j, lockKey); - - unsafeRemoveObservation(j, registrationId, token.getBytes()); - } finally { - lock.release(j, lockKey, lockValue); - } - } - - } - - @Override - public org.eclipse.californium.core.observe.Observation get(Token token) { - try (Jedis j = pool.getResource()) { - byte[] obs = j.get(toKey(OBS_TKN, token.getBytes())); - if (obs == null) { - return null; - } else { - return deserializeObs(obs); - } - } - } - /* *************** Observation utility functions **************** */ private Registration getRegistration(Jedis j, String registrationId) { @@ -731,9 +639,29 @@ private Registration getRegistration(Jedis j, String registrationId) { return deserializeReg(data); } - private void unsafeRemoveObservation(Jedis j, String registrationId, byte[] observationId) { - if (j.del(toKey(OBS_TKN, observationId)) > 0L) { - j.lrem(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, observationId); + private Collection unsafeGetObservations(Jedis j, String registrationId) { + Collection result = new ArrayList<>(); + for (byte[] token : j.lrange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) { + byte[] obs = j.get(toKey(OBS_TKN, token)); + if (obs != null) { + result.add(deserializeObs(obs)); + } + } + return result; + } + + private Observation unsafeGetObservation(Jedis j, ObservationIdentifier observationId) { + byte[] obs = j.get(toKey(OBS_TKN, observationId.getBytes())); + if (obs == null) { + return null; + } else { + return deserializeObs(obs); + } + } + + private void unsafeRemoveObservation(Jedis j, String registrationId, ObservationIdentifier observationId) { + if (j.del(toKey(OBS_TKN, observationId.getBytes())) > 0L) { + j.lrem(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, observationId.getBytes()); } } @@ -745,7 +673,7 @@ private Collection unsafeRemoveAllObservations(Jedis j, String regi for (byte[] token : j.lrange(regIdKey, 0, -1)) { byte[] obs = j.get(toKey(OBS_TKN, token)); if (obs != null) { - removed.add(build(deserializeObs(obs))); + removed.add(deserializeObs(obs)); } j.del(toKey(OBS_TKN, token)); } @@ -754,32 +682,14 @@ private Collection unsafeRemoveAllObservations(Jedis j, String regi return removed; } - @Override - public void setContext(Token token, EndpointContext correlationContext) { - // In Leshan we always set context when we send the request, so this should not be needed to implement this. - } - - private byte[] serializeObs(org.eclipse.californium.core.observe.Observation obs) { + private byte[] serializeObs(Observation obs) { return ObservationSerDes.serialize(obs); } - private org.eclipse.californium.core.observe.Observation deserializeObs(byte[] data) { + private Observation deserializeObs(byte[] data) { return ObservationSerDes.deserialize(data); } - private Observation build(org.eclipse.californium.core.observe.Observation cfObs) { - if (cfObs == null) - return null; - - if (cfObs.getRequest().getCode() == CoAP.Code.GET) { - return ObserveUtil.createLwM2mObservation(cfObs.getRequest()); - } else if (cfObs.getRequest().getCode() == CoAP.Code.FETCH) { - return ObserveUtil.createLwM2mCompositeObservation(cfObs.getRequest()); - } else { - throw new IllegalStateException("Observation request can be GET or FETCH only"); - } - } - /* *************** Expiration handling **************** */ /** @@ -852,9 +762,4 @@ public void run() { public void setExpirationListener(ExpirationListener listener) { expirationListener = listener; } - - @Override - public void setExecutor(ScheduledExecutorService executor) { - // TODO should we reuse californium executor ? - } } 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 21a722329e..fe5600a8af 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,7 +23,8 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; -import org.eclipse.californium.elements.EndpointContext; +import javax.xml.ws.EndpointContext; + import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.util.Hex; diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/ObservationSerDes.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/ObservationSerDes.java index 6c63ad572f..51d6cc4725 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/ObservationSerDes.java +++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/ObservationSerDes.java @@ -18,81 +18,158 @@ *******************************************************************************/ package org.eclipse.leshan.server.redis.serialization; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; -import java.util.Map.Entry; - -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.network.serialization.DataParser; -import org.eclipse.californium.core.network.serialization.DataSerializer; -import org.eclipse.californium.core.network.serialization.UdpDataParser; -import org.eclipse.californium.core.network.serialization.UdpDataSerializer; -import org.eclipse.californium.core.observe.Observation; -import org.eclipse.californium.elements.EndpointContext; + +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.observation.CompositeObservation; +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; +import org.eclipse.leshan.core.observation.SingleObservation; +import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.util.Hex; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; /** - * Functions for serializing and deserializing a Californium {@link Observation} in JSON. - * - * The embedded CoAP request is serialized using the Californium network serialization (see {@link UdpDataParser} and - * {@link UdpDataSerializer}). + * Functions for serializing and deserializing a {@link Observation} in JSON. */ public class ObservationSerDes { - private static final DataSerializer serializer = new UdpDataSerializer(); - private static final DataParser parser = new UdpDataParser(); + private static final String OBS_ID = "id"; + private static final String OBS_REGID = "regid"; + private static final String OBS_USER_CONTEXT = "userContext"; + private static final String OBS_PROTOCOL_DATA = "protocolData"; + private static final String OBS_KIND = "kind"; + + private static final String SOBS_CONTENT_FORMAT = "ct"; + private static final String SOBS_PATH = "path"; + + private static final String COBS_RESP_CONTENT_FORMAT = "resCt"; + private static final String COBS_REQ_CONTENT_FORMAT = "reqCt"; + private static final String COBS_PATHS = "paths"; + + private static final String KIND_SINGLE = "single"; + private static final String KIND_COMPOSITE = "composite"; public static byte[] serialize(Observation obs) { - ObjectNode o = JsonNodeFactory.instance.objectNode(); - - o.put("request", Hex.encodeHexString(serializer.serializeRequest(obs.getRequest()).bytes)); - if (obs.getContext() != null) - o.set("peer", EndpointContextSerDes.serialize(obs.getContext())); - else - o.set("peer", EndpointContextSerDes.serialize(obs.getRequest().getDestinationContext())); - - if (obs.getRequest().getUserContext() != null) { - ObjectNode ctxObject = JsonNodeFactory.instance.objectNode(); - for (Entry e : obs.getRequest().getUserContext().entrySet()) { - ctxObject.put(e.getKey(), e.getValue()); + ObjectNode n = JsonNodeFactory.instance.objectNode(); + n.put(OBS_ID, obs.getId().getAsHexString()); + n.put(OBS_REGID, obs.getRegistrationId()); + + ObjectNode userContext = JsonNodeFactory.instance.objectNode(); + for (Map.Entry e : obs.getContext().entrySet()) { + userContext.put(e.getKey(), e.getValue()); + } + n.set(OBS_USER_CONTEXT, userContext); + + ObjectNode protocolData = JsonNodeFactory.instance.objectNode(); + for (Map.Entry e : obs.getProtocolData().entrySet()) { + protocolData.put(e.getKey(), e.getValue()); + } + n.set(OBS_PROTOCOL_DATA, protocolData); + + if (obs instanceof SingleObservation) { + SingleObservation sobs = (SingleObservation) obs; + n.put(OBS_KIND, KIND_SINGLE); + if (sobs.getContentFormat() != null) { + n.put(SOBS_CONTENT_FORMAT, sobs.getContentFormat().getCode()); + } + n.put(SOBS_PATH, sobs.getPath().toString()); + } else if (obs instanceof CompositeObservation) { + CompositeObservation cobs = (CompositeObservation) obs; + n.put(OBS_KIND, KIND_COMPOSITE); + if (cobs.getRequestContentFormat() != null) { + n.put(COBS_REQ_CONTENT_FORMAT, cobs.getRequestContentFormat().getCode()); + } + if (cobs.getResponseContentFormat() != null) { + n.put(COBS_RESP_CONTENT_FORMAT, cobs.getResponseContentFormat().getCode()); + } + + ArrayNode paths = JsonNodeFactory.instance.arrayNode(); + for (LwM2mPath path : cobs.getPaths()) { + paths.add(path.toString()); } - o.set("context", ctxObject); + n.set(COBS_PATHS, paths); + } else { + throw new IllegalArgumentException(String.format("Unsupported kind of Observation : %s", obs)); } - return o.toString().getBytes(); + + return n.toString().getBytes(); } public static Observation deserialize(byte[] data) { String json = new String(data); try { - JsonNode v = new ObjectMapper().readTree(json); - - EndpointContext endpointContext = EndpointContextSerDes.deserialize(v.get("peer")); - byte[] req = Hex.decodeHex(v.get("request").asText().toCharArray()); + JsonNode n = new ObjectMapper().readTree(json); + String id = n.get(OBS_ID).asText(); + ObservationIdentifier obsId = new ObservationIdentifier(Hex.decodeHex(id.toCharArray())); + String regid = n.get(OBS_REGID).asText(); + String kind = n.get(OBS_KIND).asText(); - Request request = (Request) parser.parseMessage(req); - request.setDestinationContext(endpointContext); + Map context = null; + ObjectNode jUserContext = (ObjectNode) n.get(OBS_USER_CONTEXT); + if (jUserContext != null) { + context = new HashMap<>(); + for (Iterator it = jUserContext.fieldNames(); it.hasNext();) { + String k = it.next(); + context.put(k, jUserContext.get(k).asText()); + } + } - JsonNode ctxValue = v.get("context"); - if (ctxValue != null) { - Map context = new HashMap<>(); - for (Iterator it = ctxValue.fieldNames(); it.hasNext();) { - String name = it.next(); - context.put(name, ctxValue.get(name).asText()); + Map protocolData = null; + ObjectNode jProtocolData = (ObjectNode) n.get(OBS_PROTOCOL_DATA); + if (jProtocolData != null) { + protocolData = new HashMap<>(); + for (Iterator it = jProtocolData.fieldNames(); it.hasNext();) { + String k = it.next(); + protocolData.put(k, jProtocolData.get(k).asText()); } - request.setUserContext(context); } - return new Observation(request, endpointContext); + if (KIND_SINGLE.equals(kind)) { + ContentFormat contentFormat = null; + if (n.has(SOBS_CONTENT_FORMAT)) { + contentFormat = ContentFormat.fromCode(n.get(SOBS_CONTENT_FORMAT).asInt()); + } + LwM2mPath path = new LwM2mPath(n.get(SOBS_PATH).asText()); + + return new SingleObservation(obsId, regid, path, contentFormat, context, protocolData); + } else if (KIND_COMPOSITE.equals(kind)) { + ContentFormat reqContentFormat = null; + if (n.has(COBS_REQ_CONTENT_FORMAT)) { + reqContentFormat = ContentFormat.fromCode(n.get(COBS_REQ_CONTENT_FORMAT).asInt()); + } + ContentFormat respcontentFormat = null; + if (n.has(COBS_RESP_CONTENT_FORMAT)) { + respcontentFormat = ContentFormat.fromCode(n.get(COBS_RESP_CONTENT_FORMAT).asInt()); + } + + List paths = null; + ArrayNode jPaths = (ArrayNode) n.get(COBS_PATHS); + if (jPaths != null) { + paths = new ArrayList<>(); + for (JsonNode jPath : jPaths) { + paths.add(new LwM2mPath(jPath.asText())); + } + } + return new CompositeObservation(obsId, regid, paths, reqContentFormat, respcontentFormat, context, + protocolData); + + } else { + throw new IllegalArgumentException( + String.format("Unsupported kind of Observation : %s in %s", kind, json)); + } } catch (JsonProcessingException e) { throw new IllegalArgumentException(String.format("Unable to deserialize Observation %s", json), e); } } - } diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java index 58d7c264a8..a294c55abf 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java +++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java @@ -16,6 +16,8 @@ *******************************************************************************/ package org.eclipse.leshan.server.redis.serialization; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -56,7 +58,7 @@ */ public class RegistrationSerDes { - private AttributeParser attributeParser; + private final AttributeParser attributeParser; public RegistrationSerDes() { // Define all supported Attributes @@ -86,6 +88,7 @@ public JsonNode jSerialize(Registration r) { o.put("qm", r.getQueueMode()); o.put("ep", r.getEndpoint()); o.put("regId", r.getId()); + o.put("epUri", r.getLastEndpointUsed().toString()); ArrayNode links = JsonNodeFactory.instance.arrayNode(); for (Link l : r.getObjectLinks()) { @@ -154,6 +157,15 @@ public byte[] bSerialize(Registration r) { public Registration deserialize(JsonNode jObj) { Registration.Builder b = new Registration.Builder(jObj.get("regId").asText(), jObj.get("ep").asText(), IdentitySerDes.deserialize(jObj.get("identity"))); + + try { + b.lastEndpointUsed(new URI(jObj.get("epUri").asText())); + } catch (URISyntaxException e1) { + throw new IllegalStateException( + String.format("Unable to deserialize last endpoint used URI %s of registration %s/%s", + jObj.get("epUri").asText(), jObj.get("regId").asText(), jObj.get("ep").asText())); + } + b.bindingMode(BindingMode.parse(jObj.get("bnd").asText())); if (jObj.get("qm") != null) b.queueMode(jObj.get("qm").asBoolean()); diff --git a/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/EndpointContextSerDesTest.java b/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/EndpointContextSerDesTest.java deleted file mode 100644 index 5e0d5d169e..0000000000 --- a/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/EndpointContextSerDesTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2021 Bosch.IO GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v2.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v20.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.html. - * - * Contributors: - * Bosch IO.GmbH - initial API and implementation - *******************************************************************************/ -package org.eclipse.leshan.server.redis.serialization; - -import static org.junit.Assert.assertEquals; - -import java.net.InetSocketAddress; -import java.security.Principal; - -import org.eclipse.californium.elements.Definition; -import org.eclipse.californium.elements.EndpointContext; -import org.eclipse.californium.elements.MapBasedEndpointContext; -import org.eclipse.californium.elements.MapBasedEndpointContext.Attributes; -import org.eclipse.californium.elements.auth.PreSharedKeyIdentity; -import org.junit.Test; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -public class EndpointContextSerDesTest { - - @Test - public void endpoint_context_ser_des_then_equal() { - Definition source = new Definition<>("source", InetSocketAddress.class, - MapBasedEndpointContext.ATTRIBUTE_DEFINITIONS); - Definition enable = new Definition<>("enable", Boolean.class, - MapBasedEndpointContext.ATTRIBUTE_DEFINITIONS); - InetSocketAddress address4 = new InetSocketAddress("127.0.0.1", 5683); - InetSocketAddress address6 = new InetSocketAddress("::1", 5684); - Attributes attributes = new Attributes().add(source, address4).add(enable, true); - Principal principal = new PreSharedKeyIdentity("me"); - EndpointContext endpoint = new MapBasedEndpointContext(address6, principal, attributes); - - ObjectNode data = EndpointContextSerDes.serialize(endpoint); - - EndpointContext endpoint2 = EndpointContextSerDes.deserialize(data); - assertEquals(endpoint.getPeerAddress(), endpoint2.getPeerAddress()); - assertEquals(endpoint.getPeerIdentity(), endpoint2.getPeerIdentity()); - assertEquals(endpoint.entries(), endpoint2.entries()); - } -} diff --git a/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java b/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java index 503f988e9a..1683ff3bd4 100644 --- a/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java +++ b/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java @@ -19,6 +19,8 @@ import static org.junit.Assert.assertEquals; import java.net.Inet4Address; +import java.net.URI; +import java.net.URISyntaxException; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -37,10 +39,10 @@ public class RegistrationSerDesTest { - private RegistrationSerDes registrationSerDes = new RegistrationSerDes(); + private final RegistrationSerDes registrationSerDes = new RegistrationSerDes(); @Test - public void ser_and_des_are_equals() { + public void ser_and_des_are_equals() throws URISyntaxException { Link[] objs = new Link[2]; AttributeSet attrs = new AttributeSet( // new UnquotedStringAttribute("us", "12"), // @@ -54,7 +56,7 @@ public void ser_and_des_are_equals() { Registration.Builder builder = new Registration.Builder("registrationId", "endpoint", Identity.unsecure(Inet4Address.getLoopbackAddress(), 1)).objectLinks(objs).rootPath("/") .supportedContentFormats(ContentFormat.TLV, ContentFormat.TEXT); - + builder.lastEndpointUsed(new URI("coap://localhost:5683")); builder.registrationDate(new Date(100L)); builder.extractDataFromObjectLink(true); builder.lastUpdate(new Date(101L)); @@ -67,7 +69,7 @@ public void ser_and_des_are_equals() { } @Test - public void ser_and_des_are_equals_with_app_data() { + public void ser_and_des_are_equals_with_app_data() throws URISyntaxException { Link[] objs = new Link[2]; AttributeSet attrs = new AttributeSet( // new UnquotedStringAttribute("us", "12"), // @@ -86,6 +88,7 @@ public void ser_and_des_are_equals_with_app_data() { Identity.unsecure(Inet4Address.getLoopbackAddress(), 1)).objectLinks(objs).rootPath("/") .supportedContentFormats(ContentFormat.TLV, ContentFormat.TEXT).applicationData(appData); + builder.lastEndpointUsed(new URI("coap://localhost:5683")); builder.registrationDate(new Date(100L)); builder.lastUpdate(new Date(101L)); builder.extractDataFromObjectLink(true);