Repository: eclipse/leshan
Pull request #440 (2017-11-27 17:29:52)

Use new EndpointContext from Californium
This is largely based on what @boaks done in #382.

Main changes use Identity instead of just address/port to send request.
Thx to EndpointContext we can now ensure we send request to the peer we expect.

This should fix  #187.

~(should not be merged before californium release a new version, but can be reviewed ;))~

sbernard31: I update the PR to use the 2.0.0-M6 version of californium.

Patch:
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/CoapRequestBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/CoapRequestBuilder.java
index c95fc145..20b4d339 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/CoapRequestBuilder.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/CoapRequestBuilder.java
@@ -20,6 +20,7 @@ import java.util.HashMap;
 import java.util.Map.Entry;
 
 import org.eclipse.californium.core.coap.Request;
+import org.eclipse.californium.elements.AddressEndpointContext;
 import org.eclipse.leshan.Link;
 import org.eclipse.leshan.core.request.BindingMode;
 import org.eclipse.leshan.core.request.BootstrapRequest;
@@ -121,7 +122,6 @@ public class CoapRequestBuilder implements UplinkRequestVisitor {
     }
 
     private void buildRequestSettings() {
-        coapRequest.setDestination(serverAddress.getAddress());
-        coapRequest.setDestinationPort(serverAddress.getPort());
+        coapRequest.setDestinationContext(new AddressEndpointContext(serverAddress));
     }
 }
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/ResourceUtil.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/ResourceUtil.java
index d7f93405..d78942de 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/ResourceUtil.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/impl/ResourceUtil.java
@@ -19,7 +19,7 @@ package org.eclipse.leshan.client.californium.impl;
 import org.eclipse.californium.core.server.resources.CoapExchange;
 import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.servers.BootstrapHandler;
-import org.eclipse.leshan.core.californium.ExchangeUtil;
+import org.eclipse.leshan.core.californium.EndpointContextUtil;
 import org.eclipse.leshan.core.request.Identity;
 
 public class ResourceUtil {
@@ -28,7 +28,7 @@ public class ResourceUtil {
     // using BootstrapHandler may be not the right choice, because it
     // handles the current bootstrap server.
     public static ServerIdentity extractServerIdentity(CoapExchange exchange, BootstrapHandler bootstrapHandler) {
-        Identity identity = ExchangeUtil.extractIdentity(exchange);
+        Identity identity = EndpointContextUtil.extractIdentity(exchange.advanced().getRequest().getSourceContext());
 
         if (bootstrapHandler.isBootstrapServer(identity)) {
             return ServerIdentity.createLwm2mBootstrapServerIdentity(identity);
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 7564f59a..4ecb0325 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
@@ -12,6 +12,9 @@
  * 
  * Contributors:
  *     Sierra Wireless - initial API and implementation
+ *     Achim Kraus (Bosch Software Innovations GmbH) - redirect onSendError
+ *                                                     to error callback.
+ *     Simon Bernard                                 - use specific exception for onSendError                                                 
  *******************************************************************************/
 package org.eclipse.leshan.core.californium;
 
@@ -25,6 +28,7 @@ import org.eclipse.californium.core.coap.Request;
 import org.eclipse.californium.core.coap.Response;
 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.response.ErrorCallback;
 import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.response.ResponseCallback;
@@ -105,7 +109,7 @@ public abstract class AsyncRequestObserver<T extends LwM2mResponse> extends Abst
     @Override
     public void onSendError(Throwable error) {
         cleaningTask.cancel(false);
-        errorCallback.onError(new Exception(String.format("Unable to send request %s", coapRequest.getURI()), error));
+        errorCallback.onError(new SendFailedException(error, "Unable to send request %s", coapRequest.getURI()));
     }
 
     private ScheduledExecutorService getExecutor() {
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
new file mode 100644
index 00000000..91bb455b
--- /dev/null
+++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointContextUtil.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.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
+ *******************************************************************************/
+package org.eclipse.leshan.core.californium;
+
+import java.net.InetSocketAddress;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.eclipse.californium.elements.AddressEndpointContext;
+import org.eclipse.californium.elements.EndpointContext;
+import org.eclipse.californium.scandium.auth.PreSharedKeyIdentity;
+import org.eclipse.californium.scandium.auth.RawPublicKeyIdentity;
+import org.eclipse.californium.scandium.auth.X509CertPath;
+import org.eclipse.leshan.core.request.Identity;
+
+public class EndpointContextUtil {
+
+    public static Identity extractIdentity(EndpointContext context) {
+        InetSocketAddress peerAddress = context.getPeerAddress();
+        Principal senderIdentity = context.getPeerIdentity();
+        if (senderIdentity != null) {
+            if (senderIdentity instanceof PreSharedKeyIdentity) {
+                return Identity.psk(peerAddress, senderIdentity.getName());
+            } 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 = extractCN(senderIdentity.getName());
+                return Identity.x509(peerAddress, x509CommonName);
+            }
+            throw new IllegalStateException("Unable to extract sender identity : unexpected type of Principal");
+        }
+        return Identity.unsecure(peerAddress);
+    }
+
+    /**
+     * Create californium endpoint context from leshan identity.
+     * 
+     * @param identity leshan identity received on last registration.
+     * @return californium endpoint context for leshan identity
+     */
+    public static EndpointContext extractContext(Identity identity) {
+        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());
+            }
+        }
+        return new AddressEndpointContext(identity.getPeerAddress(), peerIdentity);
+    }
+
+    /**
+     * Extract "common name" from "distinguished name".
+     * 
+     * @param dn distinguished name
+     * @return common name
+     * @throws IllegalStateException if no CN is contained in DN.
+     */
+    public static String extractCN(String dn) {
+        // Extract common name
+        Matcher endpointMatcher = Pattern.compile("CN=(.*?)(,|$)").matcher(dn);
+        if (endpointMatcher.find()) {
+            return endpointMatcher.group(1);
+        } else {
+            throw new IllegalStateException(
+                    "Unable to extract sender identity : can not get common name in certificate");
+        }
+    }
+}
\ No newline at end of file
diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ExchangeUtil.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ExchangeUtil.java
deleted file mode 100644
index a5ad47fe..00000000
--- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/ExchangeUtil.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Sierra Wireless and others.
- * 
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * and Eclipse Distribution License v1.0 which accompany this distribution.
- * 
- * The Eclipse Public License is available at
- *    http://www.eclipse.org/legal/epl-v10.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 java.net.InetSocketAddress;
-import java.security.Principal;
-import java.security.PublicKey;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.eclipse.californium.core.server.resources.CoapExchange;
-import org.eclipse.californium.scandium.auth.PreSharedKeyIdentity;
-import org.eclipse.californium.scandium.auth.RawPublicKeyIdentity;
-import org.eclipse.californium.scandium.auth.X509CertPath;
-import org.eclipse.leshan.core.request.Identity;
-
-public class ExchangeUtil {
-
-    public static Identity extractIdentity(CoapExchange exchange) {
-        InetSocketAddress peerAddress = new InetSocketAddress(exchange.getSourceAddress(), exchange.getSourcePort());
-
-        Principal senderIdentity = exchange.advanced().getRequest().getSenderIdentity();
-        if (senderIdentity != null) {
-            if (senderIdentity instanceof PreSharedKeyIdentity) {
-                return Identity.psk(peerAddress, senderIdentity.getName());
-            } 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
-                Matcher endpointMatcher = Pattern.compile("CN=(.*?)(,|$)").matcher(senderIdentity.getName());
-                if (endpointMatcher.find()) {
-                    String x509CommonName = endpointMatcher.group(1);
-                    return Identity.x509(peerAddress, x509CommonName);
-                } else {
-                    throw new IllegalStateException(
-                            "Unable to extract sender identity : can not get common name in certificate");
-                }
-            }
-            throw new IllegalStateException("Unable to extract sender identity : unexpected type of Principal");
-        }
-        return Identity.unsecure(peerAddress);
-    }
-}
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
new file mode 100644
index 00000000..0b3eb210
--- /dev/null
+++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/Lwm2mEndpointContextMatcher.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Bosch Software Innovations GmbH and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *    Bosch Software Innovations GmbH - initial implementation
+ ******************************************************************************/
+package org.eclipse.leshan.core.californium;
+
+import java.security.Principal;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.eclipse.californium.elements.PrincipalEndpointContextMatcher;
+
+/**
+ * LWM2M principal based endpoint context matcher.
+ * 
+ * Matches DTLS based on the used principal. Requires unique credentials.
+ * 
+ * For x.509, only the CN is checked, because the other parts of the
+ * distinguished names are removed when converting it into a {@Link Identity}.
+ */
+public class Lwm2mEndpointContextMatcher extends PrincipalEndpointContextMatcher {
+
+    public Lwm2mEndpointContextMatcher() {
+    }
+
+    @Override
+    public String getName() {
+        return "lwm2m correlation";
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * For LWM2M x.509 principals consider only the common name for matching.
+     */
+    @Override
+    protected boolean matchPrincipals(Principal requestedPrincipal, Principal availablePrincipal) {
+        if (requestedPrincipal instanceof X500Principal || availablePrincipal instanceof X500Principal) {
+            try {
+                String requestedCommonName = EndpointContextUtil.extractCN(requestedPrincipal.getName());
+                String availableCommonName = EndpointContextUtil.extractCN(availablePrincipal.getName());
+                return requestedCommonName.equals(availableCommonName);
+            } catch (IllegalStateException e) {
+                return false;
+            }
+        } else {
+            return super.matchPrincipals(requestedPrincipal, availablePrincipal);
+        }
+    }
+}
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 9a174fdb..867a440b 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
@@ -12,6 +12,8 @@
  * 
  * Contributors:
  *     Sierra Wireless - initial API and implementation
+ *     Achim Kraus (Bosch Software Innovations GmbH) - set exception in onSendError
+ *     Simon Bernard                                 - use specific exception for onSendError  
  *******************************************************************************/
 package org.eclipse.leshan.core.californium;
 
@@ -23,6 +25,7 @@ import java.util.concurrent.atomic.AtomicReference;
 import org.eclipse.californium.core.coap.Request;
 import org.eclipse.californium.core.coap.Response;
 import org.eclipse.leshan.core.request.exception.RequestRejectedException;
+import org.eclipse.leshan.core.request.exception.SendFailedException;
 import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -75,6 +78,12 @@ public abstract class SyncRequestObserver<T extends LwM2mResponse> extends Abstr
         latch.countDown();
     }
 
+    @Override
+    public void onSendError(Throwable error) {
+        exception.set(new SendFailedException(error, "Request %s cannot be sent", coapRequest, error.getMessage()));
+        latch.countDown();
+    }
+
     public T waitForResponse() throws InterruptedException {
         try {
             boolean timeElapsed = false;
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java
index 2cb46487..e0d75053 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java
@@ -16,6 +16,7 @@
  *******************************************************************************/
 package org.eclipse.leshan.core.request;
 
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.security.PublicKey;
 
@@ -82,18 +83,34 @@ public class Identity {
         return new Identity(peerAddress, null, null, null);
     }
 
+    public static Identity unsecure(InetAddress address, int port) {
+        return new Identity(new InetSocketAddress(address, port), null, null, null);
+    }
+
     public static Identity psk(InetSocketAddress peerAddress, String identity) {
         return new Identity(peerAddress, identity, null, null);
     }
 
+    public static Identity psk(InetAddress address, int port, String identity) {
+        return new Identity(new InetSocketAddress(address, port), identity, null, null);
+    }
+
     public static Identity rpk(InetSocketAddress peerAddress, PublicKey publicKey) {
         return new Identity(peerAddress, null, publicKey, null);
     }
 
+    public static Identity rpk(InetAddress address, int port, PublicKey publicKey) {
+        return new Identity(new InetSocketAddress(address, port), null, publicKey, null);
+    }
+
     public static Identity x509(InetSocketAddress peerAddress, String commonName) {
         return new Identity(peerAddress, null, null, commonName);
     }
 
+    public static Identity x509(InetAddress address, int port, String commonName) {
+        return new Identity(new InetSocketAddress(address, port), null, null, commonName);
+    }
+
     @Override
     public String toString() {
         if (pskIdentity != null)
@@ -105,4 +122,47 @@ public class Identity {
         else
             return String.format("Identity %s[unsecure]", peerAddress);
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((peerAddress == null) ? 0 : peerAddress.hashCode());
+        result = prime * result + ((pskIdentity == null) ? 0 : pskIdentity.hashCode());
+        result = prime * result + ((rawPublicKey == null) ? 0 : rawPublicKey.hashCode());
+        result = prime * result + ((x509CommonName == null) ? 0 : x509CommonName.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;
+        Identity other = (Identity) obj;
+        if (peerAddress == null) {
+            if (other.peerAddress != null)
+                return false;
+        } else if (!peerAddress.equals(other.peerAddress))
+            return false;
+        if (pskIdentity == null) {
+            if (other.pskIdentity != null)
+                return false;
+        } else if (!pskIdentity.equals(other.pskIdentity))
+            return false;
+        if (rawPublicKey == null) {
+            if (other.rawPublicKey != null)
+                return false;
+        } else if (!rawPublicKey.equals(other.rawPublicKey))
+            return false;
+        if (x509CommonName == null) {
+            if (other.x509CommonName != null)
+                return false;
+        } else if (!x509CommonName.equals(other.x509CommonName))
+            return false;
+        return true;
+    }
 }
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/exception/SendFailedException.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/exception/SendFailedException.java
new file mode 100644
index 00000000..40a0188a
--- /dev/null
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/exception/SendFailedException.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.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.request.exception;
+
+/**
+ * Thrown to indicate that the request has failed to be sent.
+ */
+public class SendFailedException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public SendFailedException() {
+    }
+
+    public SendFailedException(String m) {
+        super(m);
+    }
+
+    public SendFailedException(String m, Object... args) {
+        super(String.format(m, args));
+    }
+
+    public SendFailedException(Throwable e) {
+        super(e);
+    }
+
+    public SendFailedException(String m, Throwable e) {
+        super(m, e);
+    }
+
+    public SendFailedException(Throwable e, String m, Object... args) {
+        super(String.format(m, args), e);
+    }
+}
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java
index a204a40b..b6f2e339 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java
@@ -25,6 +25,7 @@ import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
 import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder;
 import org.eclipse.leshan.LwM2m;
 import org.eclipse.leshan.core.californium.EndpointFactory;
+import org.eclipse.leshan.core.californium.Lwm2mEndpointContextMatcher;
 import org.eclipse.leshan.core.model.LwM2mModel;
 import org.eclipse.leshan.core.model.ObjectLoader;
 import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
@@ -260,7 +261,8 @@ public class LeshanBootstrapServerBuilder {
             if (endpointFactory != null) {
                 securedEndpoint = endpointFactory.createSecuredEndpoint(dtlsConfig, coapConfig, null);
             } else {
-                securedEndpoint = new CoapEndpoint(new DTLSConnector(dtlsConfig), coapConfig);
+                securedEndpoint = new CoapEndpoint(new DTLSConnector(dtlsConfig), coapConfig, null, null,
+                        new Lwm2mEndpointContextMatcher());
             }
         }
 
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
index d91df0bb..589b424d 100644
--- 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
@@ -12,6 +12,8 @@
  * 
  * Contributors:
  *     Sierra Wireless - initial API and implementation
+ *     Achim Kraus (Bosch Software Innovations GmbH) - use Lwm2mEndpointContextMatcher
+ *                                                     for secure endpoint.
  *******************************************************************************/
 package org.eclipse.leshan.server.californium;
 
@@ -29,6 +31,7 @@ import org.eclipse.californium.scandium.DTLSConnector;
 import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
 import org.eclipse.leshan.LwM2m;
 import org.eclipse.leshan.core.californium.EndpointFactory;
+import org.eclipse.leshan.core.californium.Lwm2mEndpointContextMatcher;
 import org.eclipse.leshan.core.node.LwM2mNode;
 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
@@ -428,7 +431,7 @@ public class LeshanServerBuilder {
             if (endpointFactory != null) {
                 securedEndpoint = endpointFactory.createSecuredEndpoint(dtlsConfig, coapConfig, registrationStore);
             } else {
-                securedEndpoint = new CoapEndpoint(new DTLSConnector(dtlsConfig), coapConfig, registrationStore, null);
+                securedEndpoint = new CoapEndpoint(new DTLSConnector(dtlsConfig), coapConfig, registrationStore, null, new Lwm2mEndpointContextMatcher());
             }
         }
 
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/BootstrapResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/BootstrapResource.java
index 4258ed40..2cb987b6 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/BootstrapResource.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/BootstrapResource.java
@@ -24,7 +24,7 @@ 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.leshan.core.californium.ExchangeUtil;
+import org.eclipse.leshan.core.californium.EndpointContextUtil;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.response.BootstrapResponse;
@@ -76,7 +76,7 @@ public class BootstrapResource extends CoapResource {
         }
 
         // Extract client identity
-        Identity clientIdentity = ExchangeUtil.extractIdentity(exchange);
+        Identity clientIdentity = EndpointContextUtil.extractIdentity(request.getSourceContext());
 
         // handle bootstrap request
         BootstrapResponse response = bootstrapHandler.bootstrap(clientIdentity, new BootstrapRequest(endpoint));
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumLwM2mBootstrapRequestSender.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumLwM2mBootstrapRequestSender.java
index 30a6cc2d..4d05a578 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumLwM2mBootstrapRequestSender.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumLwM2mBootstrapRequestSender.java
@@ -15,8 +15,6 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.californium.impl;
 
-import java.net.InetSocketAddress;
-
 import org.eclipse.californium.core.coap.MessageObserver;
 import org.eclipse.californium.core.coap.Request;
 import org.eclipse.californium.core.coap.Response;
@@ -29,6 +27,7 @@ import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder;
 import org.eclipse.leshan.core.request.DownlinkRequest;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.response.ResponseCallback;
@@ -54,10 +53,10 @@ public class CaliforniumLwM2mBootstrapRequestSender implements LwM2mBootstrapReq
     }
 
     @Override
-    public <T extends LwM2mResponse> T send(final String endpointName, final InetSocketAddress clientAddress,
-            final boolean secure, final DownlinkRequest<T> request, long timeout) throws InterruptedException {
-        // Create the CoAP request from LwM2m request
-        CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(clientAddress, model, encoder);
+    public <T extends LwM2mResponse> T send(final String endpointName, final Identity destination,
+            final DownlinkRequest<T> request, long timeout) throws InterruptedException { // Create the CoAP request
+                                                                                          // from LwM2m request
+        CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(destination, model, encoder);
         request.accept(coapClientRequestBuilder);
 
         final Request coapRequest = coapClientRequestBuilder.getRequest();
@@ -68,9 +67,8 @@ public class CaliforniumLwM2mBootstrapRequestSender implements LwM2mBootstrapReq
             public T buildResponse(Response coapResponse) {
                 // TODO we need to fix that by removing the Client dependency from LwM2MResponseBuilder or by creating a
                 // LwM2mBootstrapResponseBuilder
-                Registration registration = new Registration.Builder("fakeregistrationid", endpointName,
-                        clientAddress.getAddress(), clientAddress.getPort(),
-                        secure ? secureEndpoint.getAddress() : nonSecureEndpoint.getAddress()).build();
+                Registration registration = new Registration.Builder("fakeregistrationid", endpointName, destination,
+                        destination.isSecure() ? secureEndpoint.getAddress() : nonSecureEndpoint.getAddress()).build();
                 // Build LwM2m response
                 LwM2mResponseBuilder<T> lwm2mResponseBuilder = new LwM2mResponseBuilder<>(coapRequest, coapResponse,
                         registration, model, null, decoder);
@@ -81,7 +79,7 @@ public class CaliforniumLwM2mBootstrapRequestSender implements LwM2mBootstrapReq
         coapRequest.addMessageObserver(syncMessageObserver);
 
         // Send CoAP request asynchronously
-        if (secure)
+        if (destination.isSecure())
             secureEndpoint.sendRequest(coapRequest);
         else
             nonSecureEndpoint.sendRequest(coapRequest);
@@ -91,11 +89,11 @@ public class CaliforniumLwM2mBootstrapRequestSender implements LwM2mBootstrapReq
     }
 
     @Override
-    public <T extends LwM2mResponse> void send(final String endpointName, final InetSocketAddress clientAddress,
-            final boolean secure, final DownlinkRequest<T> request, final long timeout,
-            ResponseCallback<T> responseCallback, ErrorCallback errorCallback) {
+    public <T extends LwM2mResponse> void send(final String endpointName, final Identity destination,
+            final DownlinkRequest<T> request, final long timeout, ResponseCallback<T> responseCallback,
+            ErrorCallback errorCallback) {
         // Create the CoAP request from LwM2m request
-        CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(clientAddress, model, encoder);
+        CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(destination, model, encoder);
         request.accept(coapClientRequestBuilder);
         final Request coapRequest = coapClientRequestBuilder.getRequest();
 
@@ -105,9 +103,8 @@ public class CaliforniumLwM2mBootstrapRequestSender implements LwM2mBootstrapReq
             public T buildResponse(Response coapResponse) {
                 // TODO we need to fix that by removing the Client dependency from LwM2MResponseBuilder or by creating a
                 // LwM2mBootstrapResponseBuilder
-                Registration registration = new Registration.Builder("fakeregistrationid", endpointName,
-                        clientAddress.getAddress(), clientAddress.getPort(),
-                        secure ? secureEndpoint.getAddress() : nonSecureEndpoint.getAddress()).build();
+                Registration registration = new Registration.Builder("fakeregistrationid", endpointName, destination,
+                        destination.isSecure() ? secureEndpoint.getAddress() : nonSecureEndpoint.getAddress()).build();
 
                 // Build LwM2m response
                 LwM2mResponseBuilder<T> lwm2mResponseBuilder = new LwM2mResponseBuilder<>(coapRequest, coapResponse,
@@ -119,7 +116,7 @@ public class CaliforniumLwM2mBootstrapRequestSender implements LwM2mBootstrapReq
         coapRequest.addMessageObserver(obs);
 
         // Send CoAP request asynchronously
-        if (secure)
+        if (destination.isSecure())
             secureEndpoint.sendRequest(coapRequest);
         else
             nonSecureEndpoint.sendRequest(coapRequest);
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumLwM2mRequestSender.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumLwM2mRequestSender.java
index 0d53c3b3..e6330258 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumLwM2mRequestSender.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CaliforniumLwM2mRequestSender.java
@@ -12,6 +12,7 @@
  * 
  * Contributors:
  *     Sierra Wireless - initial API and implementation
+ *     Achim Kraus (Bosch Software Innovations GmbH) - use Identity as destination
  *******************************************************************************/
 package org.eclipse.leshan.server.californium.impl;
 
@@ -76,8 +77,7 @@ public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender {
         final LwM2mModel model = modelProvider.getObjectModel(destination);
 
         // Create the CoAP request from LwM2m request
-        CoapRequestBuilder coapRequestBuilder = new CoapRequestBuilder(
-                new InetSocketAddress(destination.getAddress(), destination.getPort()), destination.getRootPath(),
+        CoapRequestBuilder coapRequestBuilder = new CoapRequestBuilder(destination.getIdentity(), destination.getRootPath(),
                 destination.getId(), destination.getEndpoint(), model, encoder);
         request.accept(coapRequestBuilder);
         final Request coapRequest = coapRequestBuilder.getRequest();
@@ -114,7 +114,7 @@ public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender {
 
         // Create the CoAP request from LwM2m request
         CoapRequestBuilder coapRequestBuilder = new CoapRequestBuilder(
-                new InetSocketAddress(destination.getAddress(), destination.getPort()), destination.getRootPath(),
+                destination.getIdentity(), destination.getRootPath(),
                 destination.getId(), destination.getEndpoint(), model, encoder);
         request.accept(coapRequestBuilder);
         final Request coapRequest = coapRequestBuilder.getRequest();
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java
index 2bc8ab2a..c4b49d86 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java
@@ -12,13 +12,17 @@
  * 
  * Contributors:
  *     Sierra Wireless - initial API and implementation
+ *     Achim Kraus (Bosch Software Innovations GmbH) - use Identity as destination
+ *                                                     and transform them to 
+ *                                                     EndpointContext for requests
  *******************************************************************************/
 package org.eclipse.leshan.server.californium.impl;
 
-import java.net.InetSocketAddress;
 
 import org.eclipse.californium.core.coap.MediaTypeRegistry;
 import org.eclipse.californium.core.coap.Request;
+import org.eclipse.californium.elements.EndpointContext;
+import org.eclipse.leshan.core.californium.EndpointContextUtil;
 import org.eclipse.leshan.core.model.LwM2mModel;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
 import org.eclipse.leshan.core.node.LwM2mPath;
@@ -32,6 +36,7 @@ import org.eclipse.leshan.core.request.DeleteRequest;
 import org.eclipse.leshan.core.request.DiscoverRequest;
 import org.eclipse.leshan.core.request.DownlinkRequestVisitor;
 import org.eclipse.leshan.core.request.ExecuteRequest;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.request.ObserveRequest;
 import org.eclipse.leshan.core.request.ReadRequest;
 import org.eclipse.leshan.core.request.WriteAttributesRequest;
@@ -44,7 +49,7 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
     private Request coapRequest;
 
     // client information
-    private final InetSocketAddress destination;
+    private final Identity destination;
     private final String rootPath;
     private final String registrationId;
     private final String endpoint;
@@ -52,7 +57,7 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
     private final LwM2mModel model;
     private final LwM2mNodeEncoder encoder;
 
-    public CoapRequestBuilder(InetSocketAddress destination, LwM2mModel model, LwM2mNodeEncoder encoder) {
+    public CoapRequestBuilder(Identity destination, LwM2mModel model, LwM2mNodeEncoder encoder) {
         this.destination = destination;
         this.rootPath = null;
         this.registrationId = null;
@@ -61,9 +66,8 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
         this.encoder = encoder;
     }
 
-    public CoapRequestBuilder(InetSocketAddress destination, String rootPath, String registrationId, String endpoint,
-            LwM2mModel model,
-            LwM2mNodeEncoder encoder) {
+    public CoapRequestBuilder(Identity destination, String rootPath, String registrationId, String endpoint,
+            LwM2mModel model, LwM2mNodeEncoder encoder) {
         this.destination = destination;
         this.rootPath = rootPath;
         this.endpoint = endpoint;
@@ -155,16 +159,16 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
     public void visit(BootstrapDeleteRequest request) {
         coapRequest = Request.newDelete();
         coapRequest.setConfirmable(true);
-        coapRequest.setDestination(destination.getAddress());
-        coapRequest.setDestinationPort(destination.getPort());
+        EndpointContext context = EndpointContextUtil.extractContext(destination);
+        coapRequest.setDestinationContext(context);
     }
 
     @Override
     public void visit(BootstrapFinishRequest request) {
         coapRequest = Request.newPost();
         coapRequest.setConfirmable(true);
-        coapRequest.setDestination(destination.getAddress());
-        coapRequest.setDestinationPort(destination.getPort());
+        EndpointContext context = EndpointContextUtil.extractContext(destination);
+        coapRequest.setDestinationContext(context);
 
         // root path
         if (rootPath != null) {
@@ -179,8 +183,8 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
     }
 
     private final void setTarget(Request coapRequest, LwM2mPath path) {
-        coapRequest.setDestination(destination.getAddress());
-        coapRequest.setDestinationPort(destination.getPort());
+        EndpointContext context = EndpointContextUtil.extractContext(destination);
+        coapRequest.setDestinationContext(context);
 
         // root path
         if (rootPath != null) {
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStore.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStore.java
index 2e2959bf..7cbb7277 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStore.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStore.java
@@ -17,6 +17,8 @@
  *                                                     ObservationUtil.shallowClone.
  *                                                     Reuse already created Key in
  *                                                     setContext().
+ *     Achim Kraus (Bosch Software Innovations GmbH) - rename CorrelationContext to
+ *                                                     EndpointContext
  *******************************************************************************/
 package org.eclipse.leshan.server.californium.impl;
 
@@ -35,7 +37,7 @@ import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import org.eclipse.californium.core.observe.ObservationUtil;
-import org.eclipse.californium.elements.CorrelationContext;
+import org.eclipse.californium.elements.EndpointContext;
 import org.eclipse.leshan.core.observation.Observation;
 import org.eclipse.leshan.server.Startable;
 import org.eclipse.leshan.server.Stoppable;
@@ -312,7 +314,7 @@ public class InMemoryRegistrationStore implements CaliforniumRegistrationStore,
     }
 
     @Override
-    public void setContext(byte[] token, CorrelationContext ctx) {
+    public void setContext(byte[] token, EndpointContext ctx) {
         try {
             lock.writeLock().lock();
             Key key = new Key(token);
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/RegisterResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/RegisterResource.java
index f901a82b..099556b8 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/RegisterResource.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/RegisterResource.java
@@ -15,7 +15,7 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.californium.impl;
 
-import static org.eclipse.leshan.core.californium.ExchangeUtil.extractIdentity;
+import static org.eclipse.leshan.core.californium.EndpointContextUtil.extractIdentity;
 import static org.eclipse.leshan.core.californium.ResponseCodeUtil.toCoapResponseCode;
 
 import java.net.InetSocketAddress;
@@ -142,7 +142,7 @@ public class RegisterResource extends CoapResource {
     private void handleRegister(CoapExchange exchange, Request request) {
         // Get identity
         // --------------------------------
-        Identity sender = extractIdentity(exchange);
+        Identity sender = extractIdentity(request.getSourceContext());
 
         // Create LwM2m request from CoAP request
         // --------------------------------
@@ -202,7 +202,7 @@ public class RegisterResource extends CoapResource {
 
     private void handleUpdate(CoapExchange exchange, Request request, String registrationId) {
         // Get identity
-        Identity sender = extractIdentity(exchange);
+        Identity sender = extractIdentity(request.getSourceContext());
 
         // Create LwM2m request from CoAP request
         Long lifetime = null;
@@ -234,7 +234,7 @@ public class RegisterResource extends CoapResource {
 
     private void handleDeregister(CoapExchange exchange, String registrationId) {
         // Get identity
-        Identity sender = extractIdentity(exchange);
+        Identity sender = extractIdentity(exchange.advanced().getRequest().getSourceContext());
 
         // Create request
         DeregisterRequest deregisterRequest = new DeregisterRequest(registrationId);
diff --git a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/RedisRegistrationStore.java b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/RedisRegistrationStore.java
index 613e83a8..f4a57a25 100644
--- a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/RedisRegistrationStore.java
+++ b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/RedisRegistrationStore.java
@@ -12,6 +12,8 @@
  * 
  * Contributors:
  *     Sierra Wireless - initial API and implementation
+ *     Achim Kraus (Bosch Software Innovations GmbH) - rename CorrelationContext to
+ *                                                     EndpointContext
  *******************************************************************************/
 package org.eclipse.leshan.server.cluster;
 
@@ -29,7 +31,7 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
-import org.eclipse.californium.elements.CorrelationContext;
+import org.eclipse.californium.elements.EndpointContext;
 import org.eclipse.leshan.core.observation.Observation;
 import org.eclipse.leshan.server.Startable;
 import org.eclipse.leshan.server.Stoppable;
@@ -589,7 +591,7 @@ public class RedisRegistrationStore implements CaliforniumRegistrationStore, Sta
     }
 
     @Override
-    public void setContext(byte[] token, CorrelationContext correlationContext) {
+    public void setContext(byte[] token, EndpointContext correlationContext) {
         // TODO should be implemented
     }
 
diff --git a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/EndpointContextSerDes.java b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/EndpointContextSerDes.java
new file mode 100644
index 00000000..0a0558cb
--- /dev/null
+++ b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/EndpointContextSerDes.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Bosch Software Innovations GmbH and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *    Achim Kraus (Bosch Software Innovations GmbH) - initial implementation.
+ ******************************************************************************/
+package org.eclipse.leshan.server.cluster.serialization;
+
+import java.net.InetSocketAddress;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.eclipse.californium.elements.AddressEndpointContext;
+import org.eclipse.californium.elements.EndpointContext;
+import org.eclipse.californium.elements.MapBasedEndpointContext;
+import org.eclipse.californium.scandium.auth.PreSharedKeyIdentity;
+import org.eclipse.californium.scandium.auth.RawPublicKeyIdentity;
+import org.eclipse.californium.scandium.auth.X509CertPath;
+import org.eclipse.leshan.util.Hex;
+
+import com.eclipsesource.json.Json;
+import com.eclipsesource.json.JsonObject;
+import com.eclipsesource.json.JsonObject.Member;
+import com.eclipsesource.json.JsonValue;
+
+/**
+ * Functions for serializing and deserializing a Californium
+ * {@link EndpointContext} in JSON.
+ */
+public class EndpointContextSerDes {
+
+    private static final String KEY_ADDRESS = "address";
+    private static final String KEY_PORT = "port";
+    private static final String KEY_ID = "id";
+    private static final String KEY_DN = "dn";
+    private static final String KEY_RPK = "rpk";
+    private static final String KEY_ATTRIBUTES = "attributes";
+
+    public static JsonObject serialize(EndpointContext context) {
+        JsonObject peer = Json.object();
+        peer.set(KEY_ADDRESS, context.getPeerAddress().getHostString());
+        peer.set(KEY_PORT, context.getPeerAddress().getPort());
+        Principal principal = context.getPeerIdentity();
+        if (principal != null) {
+            if (principal instanceof PreSharedKeyIdentity) {
+                peer.set(KEY_ID, principal.getName());
+            } else if (principal instanceof RawPublicKeyIdentity) {
+                PublicKey publicKey = ((RawPublicKeyIdentity) principal).getKey();
+                peer.set(KEY_RPK, Hex.encodeHexString(publicKey.getEncoded()));
+            } else if (principal instanceof X500Principal || principal instanceof X509CertPath) {
+                peer.set(KEY_DN, principal.getName());
+            }
+        }
+        /** copy the attributes **/
+        Set<Entry<String, String>> attributes = context.entrySet();
+        if (!attributes.isEmpty()) {
+            JsonObject attContext = Json.object();
+            for (Entry<String, String> e : attributes) {
+                attContext.set(e.getKey(), e.getValue());
+            }
+            peer.set(KEY_ATTRIBUTES, attContext);
+        }
+        return peer;
+    }
+
+    public static EndpointContext deserialize(JsonObject peer) {
+
+        String address = peer.get(KEY_ADDRESS).asString();
+        int port = peer.get(KEY_PORT).asInt();
+        InetSocketAddress socketAddress = new InetSocketAddress(address, port);
+
+        Principal principal = null;
+        JsonValue value = peer.get(KEY_ID);
+        if (value != null) {
+            principal = new PreSharedKeyIdentity(value.asString());
+        } else if ((value = peer.get(KEY_RPK)) != null) {
+            try {
+                byte[] rpk = Hex.decodeHex(value.asString().toCharArray());
+                X509EncodedKeySpec spec = new X509EncodedKeySpec(rpk);
+                PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(spec);
+                principal = new RawPublicKeyIdentity(publicKey);
+            } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+                throw new IllegalStateException("Invalid security info content", e);
+            }
+        } else if ((value = peer.get(KEY_DN)) != null) {
+            principal = new X500Principal(value.asString());
+        }
+
+        EndpointContext endpointContext;
+        value = peer.get(KEY_ATTRIBUTES);
+        if (value == null) {
+            endpointContext = new AddressEndpointContext(socketAddress, principal);
+        } else {
+            int index = 0;
+            String attributes[] = new String[value.asObject().size() * 2];
+            for (Member member : value.asObject()) {
+                attributes[index++] = member.getName();
+                attributes[index++] = member.getValue().asString();
+            }
+            endpointContext = new MapBasedEndpointContext(socketAddress, principal, attributes);
+        }
+        return endpointContext;
+    }
+
+}
diff --git a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/IdentitySerDes.java b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/IdentitySerDes.java
new file mode 100644
index 00000000..cdb33d3a
--- /dev/null
+++ b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/IdentitySerDes.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Bosch Software Innovations GmbH and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *    Achim Kraus (Bosch Software Innovations GmbH) - initial implementation.
+ ******************************************************************************/
+package org.eclipse.leshan.server.cluster.serialization;
+
+import java.net.InetSocketAddress;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.eclipse.californium.elements.EndpointContext;
+import org.eclipse.leshan.core.request.Identity;
+import org.eclipse.leshan.util.Hex;
+
+import com.eclipsesource.json.Json;
+import com.eclipsesource.json.JsonObject;
+import com.eclipsesource.json.JsonValue;
+
+/**
+ * Functions for serializing and deserializing a Californium {@link EndpointContext} in JSON.
+ */
+public class IdentitySerDes {
+
+    private static final String KEY_ADDRESS = "address";
+    private static final String KEY_PORT = "port";
+    private static final String KEY_ID = "id";
+    private static final String KEY_CN = "cn";
+    private static final String KEY_RPK = "rpk";
+
+    public static JsonObject serialize(Identity identity) {
+        JsonObject o = Json.object();
+        o.set(KEY_ADDRESS, identity.getPeerAddress().getHostString());
+        o.set(KEY_PORT, identity.getPeerAddress().getPort());
+        if (identity.isPSK()) {
+            o.set(KEY_ID, identity.getPskIdentity());
+        } else if (identity.isRPK()) {
+            PublicKey publicKey = identity.getRawPublicKey();
+            o.set(KEY_RPK, Hex.encodeHexString(publicKey.getEncoded()));
+        } else if (identity.isX509()) {
+            o.set(KEY_CN, identity.getX509CommonName());
+        }
+        return o;
+    }
+
+    public static Identity deserialize(JsonObject peer) {
+        String address = peer.get(KEY_ADDRESS).asString();
+        int port = peer.get(KEY_PORT).asInt();
+
+        JsonValue jpsk = peer.get(KEY_ID);
+        if (jpsk != null) {
+            return Identity.psk(new InetSocketAddress(address, port), jpsk.asString());
+        }
+
+        JsonValue jrpk = peer.get(KEY_RPK);
+        if (jrpk != null) {
+            try {
+                byte[] rpk = Hex.decodeHex(jrpk.asString().toCharArray());
+                X509EncodedKeySpec spec = new X509EncodedKeySpec(rpk);
+                PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(spec);
+                return Identity.rpk(new InetSocketAddress(address, port), publicKey);
+            } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+                throw new IllegalStateException("Invalid security info content", e);
+            }
+        }
+
+        JsonValue jcn = peer.get(KEY_CN);
+        if (jcn != null) {
+            return Identity.x509(new InetSocketAddress(address, port), jcn.asString());
+        }
+
+        return Identity.unsecure(new InetSocketAddress(address, port));
+    }
+}
diff --git a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/ObservationSerDes.java b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/ObservationSerDes.java
index 405b906e..76d31fde 100644
--- a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/ObservationSerDes.java
+++ b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/ObservationSerDes.java
@@ -12,6 +12,8 @@
  * 
  * Contributors:
  *     Sierra Wireless - initial API and implementation
+ *     Achim Kraus (Bosch Software Innovations GmbH) - add support for californium
+ *                                                     endpoint context
  *******************************************************************************/
 package org.eclipse.leshan.server.cluster.serialization;
 
@@ -25,6 +27,7 @@ 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.californium.elements.RawData;
 import org.eclipse.leshan.util.Hex;
 
@@ -47,6 +50,10 @@ public class ObservationSerDes {
         JsonObject o = Json.object();
 
         o.set("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) {
             JsonObject ctxObject = Json.object();
@@ -61,8 +68,12 @@ public class ObservationSerDes {
     public static Observation deserialize(byte[] data) {
         JsonObject v = (JsonObject) Json.parse(new String(data));
 
+        EndpointContext endpointContext = EndpointContextSerDes.deserialize(v.get("peer").asObject());
         byte[] req = Hex.decodeHex(v.getString("request", null).toCharArray());
-        Request request = (Request) parser.parseMessage(new RawData(req, null, 0));
+
+        RawData rawData = RawData.outbound(req, endpointContext, null, false);
+        Request request = (Request) parser.parseMessage(rawData);
+        request.setDestinationContext(endpointContext);
 
         JsonValue ctxValue = v.get("context");
         if (ctxValue != null) {
@@ -74,8 +85,7 @@ public class ObservationSerDes {
             request.setUserContext(context);
         }
 
-        // TODO handle security context
-        return new Observation(request, null);
+        return new Observation(request, endpointContext);
     }
 
 }
diff --git a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/RegistrationSerDes.java b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/RegistrationSerDes.java
index 9d827773..230b3c34 100644
--- a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/RegistrationSerDes.java
+++ b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/RegistrationSerDes.java
@@ -37,8 +37,7 @@ public class RegistrationSerDes {
     public static JsonObject jSerialize(Registration r) {
         JsonObject o = Json.object();
         o.add("regDate", r.getRegistrationDate().getTime());
-        o.add("address", r.getAddress().getHostAddress());
-        o.add("port", r.getPort());
+        o.add("identity", IdentitySerDes.serialize(r.getIdentity()));
         o.add("regAddr", r.getRegistrationEndpointAddress().getHostString());
         o.add("regPort", r.getRegistrationEndpointAddress().getPort());
         o.add("lt", r.getLifeTimeInSec());
@@ -86,8 +85,7 @@ public class RegistrationSerDes {
 
     public static Registration deserialize(JsonObject jObj) {
         Registration.Builder b = new Registration.Builder(jObj.getString("regId", null), jObj.getString("ep", null),
-                new InetSocketAddress(jObj.getString("address", null), jObj.getInt("port", 0)).getAddress(),
-                jObj.getInt("port", 0),
+                IdentitySerDes.deserialize(jObj.get("identity").asObject()),
                 new InetSocketAddress(jObj.getString("regAddr", null), jObj.getInt("regPort", 0)));
         b.bindingMode(BindingMode.valueOf(jObj.getString("bnd", null)));
         b.lastUpdate(new Date(jObj.getLong("lastUp", 0)));
diff --git a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/RegistrationUpdateSerDes.java b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/RegistrationUpdateSerDes.java
index e655f13d..b9e96f45 100644
--- a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/RegistrationUpdateSerDes.java
+++ b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/serialization/RegistrationUpdateSerDes.java
@@ -15,13 +15,13 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.cluster.serialization;
 
-import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.eclipse.leshan.Link;
 import org.eclipse.leshan.core.request.BindingMode;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.server.registration.RegistrationUpdate;
 
 import com.eclipsesource.json.Json;
@@ -39,8 +39,7 @@ public class RegistrationUpdateSerDes {
 
         // mandatory fields
         o.add("regId", u.getRegistrationId());
-        o.add("address", u.getAddress().getHostAddress());
-        o.add("port", u.getPort());
+        o.add("identity", IdentitySerDes.serialize(u.getIdentity()));
 
         // optional fields
         if (u.getLifeTimeInSec() != null)
@@ -84,8 +83,7 @@ public class RegistrationUpdateSerDes {
 
         // mandatory fields
         String regId = v.getString("regId", null);
-        InetAddress addr = InetAddress.getByName(v.getString("address", null));
-        int port = v.getInt("port", -1);
+        Identity identity = IdentitySerDes.deserialize(v.get("identity").asObject());
 
         // optional fields
         BindingMode b = null;
@@ -124,6 +122,6 @@ public class RegistrationUpdateSerDes {
             }
         }
 
-        return new RegistrationUpdate(regId, addr, port, lifetime, sms, b, linkObjs);
+        return new RegistrationUpdate(regId, identity, lifetime, sms, b, linkObjs);
     }
 }
\ No newline at end of file
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java
index 5d96b06f..c7ac7afa 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java
@@ -215,8 +215,7 @@ public class BootstrapHandler {
 
     private <T extends LwM2mResponse> void send(BootstrapSession session, DownlinkRequest<T> request,
             ResponseCallback<T> responseCallback, ErrorCallback errorCallback) {
-        sender.send(session.getEndpoint(), session.getIdentity().getPeerAddress(), session.getIdentity().isSecure(),
-                request, DEFAULT_TIMEOUT, responseCallback, errorCallback);
+        sender.send(session.getEndpoint(), session.getIdentity(), request, DEFAULT_TIMEOUT, responseCallback, errorCallback);
     }
 
     private LwM2mObjectInstance convertToSecurityInstance(int instanceId, ServerSecurity securityConfig) {
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LwM2mBootstrapRequestSender.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LwM2mBootstrapRequestSender.java
index b8d71949..1c5bb25c 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LwM2mBootstrapRequestSender.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LwM2mBootstrapRequestSender.java
@@ -15,9 +15,8 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.bootstrap;
 
-import java.net.InetSocketAddress;
-
 import org.eclipse.leshan.core.request.DownlinkRequest;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.response.ResponseCallback;
@@ -29,13 +28,12 @@ public interface LwM2mBootstrapRequestSender {
      * @return the LWM2M response. The response can be <code>null</code> if the timeout (given parameter or CoAP
      *         timeout) expires.
      */
-    <T extends LwM2mResponse> T send(String clientEndpoint, InetSocketAddress client, boolean secure,
-            DownlinkRequest<T> request, long timeout) throws InterruptedException;
+    <T extends LwM2mResponse> T send(String clientEndpoint, Identity client, DownlinkRequest<T> request, long timeout)
+            throws InterruptedException;
 
     /**
      * Send a Lightweight M2M request asynchronously.
      */
-    <T extends LwM2mResponse> void send(String clientEndpoint, InetSocketAddress client, boolean secure,
-            DownlinkRequest<T> request, long timeout, ResponseCallback<T> responseCallback,
-            ErrorCallback errorCallback);
+    <T extends LwM2mResponse> void send(String clientEndpoint, Identity client, DownlinkRequest<T> request,
+            long timeout, ResponseCallback<T> responseCallback, ErrorCallback errorCallback);
 }
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 dc802c4d..81f8d4d0 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
@@ -12,6 +12,7 @@
  * 
  * Contributors:
  *     Sierra Wireless - initial API and implementation
+ *     Achim Kraus (Bosch Software Innovations GmbH) - use Identity as destination
  *******************************************************************************/
 package org.eclipse.leshan.server.registration;
 
@@ -27,6 +28,7 @@ import java.util.Map;
 
 import org.eclipse.leshan.Link;
 import org.eclipse.leshan.core.request.BindingMode;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.util.Validate;
 
 /**
@@ -42,9 +44,7 @@ public class Registration implements Serializable {
 
     private final Date registrationDate;
 
-    private final InetAddress address;
-
-    private final int port;
+    private final Identity identity;
 
     /*
      * The address of the LWM2M Server's CoAP end point the client used to register.
@@ -75,7 +75,7 @@ public class Registration implements Serializable {
 
     private final Date lastUpdate;
 
-    protected Registration(String id, String endpoint, InetAddress address, int port, String lwM2mVersion,
+    protected Registration(String id, String endpoint, Identity identity, String lwM2mVersion,
             Long lifetimeInSec, String smsNumber, BindingMode bindingMode, Link[] objectLinks,
             InetSocketAddress registrationEndpointAddress,
 
@@ -83,14 +83,12 @@ public class Registration implements Serializable {
 
         Validate.notNull(id);
         Validate.notEmpty(endpoint);
-        Validate.notNull(address);
-        Validate.notNull(port);
+        Validate.notNull(identity);
         Validate.notNull(registrationEndpointAddress);
 
         this.id = id;
+        this.identity = identity;
         this.endpoint = endpoint;
-        this.address = address;
-        this.port = port;
         this.smsNumber = smsNumber;
         this.registrationEndpointAddress = registrationEndpointAddress;
 
@@ -130,13 +128,22 @@ public class Registration implements Serializable {
         return registrationDate;
     }
 
+    /**
+     * Gets the clients identity.
+     * 
+     * @return identity from client's most recent registration or registration update. 
+     */
+    public Identity getIdentity() {
+        return identity;
+    }
+
     /**
      * Gets the client's network address.
      * 
      * @return the source address from the client's most recent CoAP message.
      */
     public InetAddress getAddress() {
-        return address;
+        return identity.getPeerAddress().getAddress();
     }
 
     /**
@@ -145,7 +152,7 @@ public class Registration implements Serializable {
      * @return the source port from the client's most recent CoAP message.
      */
     public int getPort() {
-        return port;
+        return identity.getPeerAddress().getPort();
     }
 
     /**
@@ -288,8 +295,8 @@ public class Registration implements Serializable {
     @Override
     public String toString() {
         return String.format(
-                "Registration [registrationDate=%s, address=%s, port=%s, registrationEndpoint=%s, lifeTimeInSec=%s, smsNumber=%s, lwM2mVersion=%s, bindingMode=%s, endpoint=%s, registrationId=%s, objectLinks=%s, lastUpdate=%s]",
-                registrationDate, address, port, registrationEndpointAddress, lifeTimeInSec, smsNumber, lwM2mVersion,
+                "Registration [registrationDate=%s, identity=%s, registrationEndpoint=%s, lifeTimeInSec=%s, smsNumber=%s, lwM2mVersion=%s, bindingMode=%s, endpoint=%s, registrationId=%s, objectLinks=%s, lastUpdate=%s]",
+                registrationDate, identity, registrationEndpointAddress, lifeTimeInSec, smsNumber, lwM2mVersion,
                 bindingMode, endpoint, id, Arrays.toString(objectLinks), lastUpdate);
     }
 
@@ -322,8 +329,7 @@ public class Registration implements Serializable {
     public static class Builder {
         private final String registrationId;
         private final String endpoint;
-        private final InetAddress address;
-        private final int port;
+        private final Identity identity;
         private final InetSocketAddress registrationEndpointAddress;
 
         private Date registrationDate;
@@ -335,18 +341,16 @@ public class Registration implements Serializable {
         private Link[] objectLinks;
         private Map<String, String> additionalRegistrationAttributes;
 
-        public Builder(String registrationId, String endpoint, InetAddress address, int port,
+        public Builder(String registrationId, String endpoint, Identity identity,
                 InetSocketAddress registrationEndpointAddress) {
 
             Validate.notNull(registrationId);
             Validate.notEmpty(endpoint);
-            Validate.notNull(address);
-            Validate.notNull(port);
+            Validate.notNull(identity);
             Validate.notNull(registrationEndpointAddress);
             this.registrationId = registrationId;
             this.endpoint = endpoint;
-            this.address = address;
-            this.port = port;
+            this.identity = identity;
             this.registrationEndpointAddress = registrationEndpointAddress;
 
         }
@@ -392,8 +396,8 @@ public class Registration implements Serializable {
         }
 
         public Registration build() {
-            return new Registration(Builder.this.registrationId, Builder.this.endpoint, Builder.this.address,
-                    Builder.this.port, Builder.this.lwM2mVersion, Builder.this.lifeTimeInSec, Builder.this.smsNumber,
+            return new Registration(Builder.this.registrationId, Builder.this.endpoint, Builder.this.identity,
+                    Builder.this.lwM2mVersion, Builder.this.lifeTimeInSec, Builder.this.smsNumber,
                     this.bindingMode, this.objectLinks, this.registrationEndpointAddress, this.registrationDate,
                     this.lastUpdate, this.additionalRegistrationAttributes);
         }
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 7e4df144..39543d0b 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
@@ -12,6 +12,7 @@
  * 
  * Contributors:
  *     Sierra Wireless - initial API and implementation
+ *     Achim Kraus (Bosch Software Innovations GmbH) - use Identity as destination
  *******************************************************************************/
 package org.eclipse.leshan.server.registration;
 
@@ -52,8 +53,7 @@ public class RegistrationHandler {
             InetSocketAddress serverEndpoint) {
 
         Registration.Builder builder = new Registration.Builder(RegistrationHandler.createRegistrationId(),
-                registerRequest.getEndpointName(), sender.getPeerAddress().getAddress(),
-                sender.getPeerAddress().getPort(), serverEndpoint);
+                registerRequest.getEndpointName(), sender, serverEndpoint);
 
         builder.lwM2mVersion(registerRequest.getLwVersion()).lifeTimeInSec(registerRequest.getLifetime())
                 .bindingMode(registerRequest.getBindingMode()).objectLinks(registerRequest.getObjectLinks())
@@ -104,8 +104,7 @@ public class RegistrationHandler {
         }
 
         // Create update
-        final RegistrationUpdate update = new RegistrationUpdate(updateRequest.getRegistrationId(),
-                sender.getPeerAddress().getAddress(), sender.getPeerAddress().getPort(),
+        final RegistrationUpdate update = new RegistrationUpdate(updateRequest.getRegistrationId(), sender,
                 updateRequest.getLifeTimeInSec(), updateRequest.getSmsNumber(), updateRequest.getBindingMode(),
                 updateRequest.getObjectLinks());
 
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 63a49948..674cfed9 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
@@ -12,6 +12,7 @@
  * 
  * Contributors:
  *     Sierra Wireless - initial API and implementation
+ *     Achim Kraus (Bosch Software Innovations GmbH) - add Identity as destination
  *******************************************************************************/
 package org.eclipse.leshan.server.registration;
 
@@ -21,6 +22,7 @@ import java.util.Date;
 
 import org.eclipse.leshan.Link;
 import org.eclipse.leshan.core.request.BindingMode;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.util.Validate;
 
 /**
@@ -30,23 +32,18 @@ public class RegistrationUpdate {
 
     private final String registrationId;
 
-    private final InetAddress address;
-    private final Integer port;
-
+    private final Identity identity;
     private final Long lifeTimeInSec;
     private final String smsNumber;
     private final BindingMode bindingMode;
     private final Link[] objectLinks;
 
-    public RegistrationUpdate(String registrationId, InetAddress address, Integer port, Long lifeTimeInSec, String smsNumber,
+    public RegistrationUpdate(String registrationId, Identity identity, Long lifeTimeInSec, String smsNumber,
             BindingMode bindingMode, Link[] objectLinks) {
         Validate.notNull(registrationId);
-        Validate.notNull(address);
-        Validate.notNull(port);
+        Validate.notNull(identity);
         this.registrationId = registrationId;
-        this.address = address;
-        this.port = port;
-
+        this.identity = identity;
         this.lifeTimeInSec = lifeTimeInSec;
         this.smsNumber = smsNumber;
         this.bindingMode = bindingMode;
@@ -60,8 +57,7 @@ public class RegistrationUpdate {
      * @return the updated registration
      */
     public Registration update(Registration registration) {
-        InetAddress address = this.address != null ? this.address : registration.getAddress();
-        int port = this.port != null ? this.port : registration.getPort();
+        Identity identity = this.identity != null ? this.identity : registration.getIdentity();
         Link[] linkObject = this.objectLinks != null ? this.objectLinks : registration.getObjectLinks();
         long lifeTimeInSec = this.lifeTimeInSec != null ? this.lifeTimeInSec : registration.getLifeTimeInSec();
         BindingMode bindingMode = this.bindingMode != null ? this.bindingMode : registration.getBindingMode();
@@ -72,7 +68,7 @@ public class RegistrationUpdate {
         Date lastUpdate = new Date();
 
         Registration.Builder builder = new Registration.Builder(registration.getId(), registration.getEndpoint(),
-                address, port, registration.getRegistrationEndpointAddress());
+                identity, registration.getRegistrationEndpointAddress());
 
         builder.lwM2mVersion(registration.getLwM2mVersion()).lifeTimeInSec(lifeTimeInSec).smsNumber(smsNumber)
                 .bindingMode(bindingMode).objectLinks(linkObject).registrationDate(registration.getRegistrationDate())
@@ -87,12 +83,16 @@ public class RegistrationUpdate {
         return registrationId;
     }
 
+    public Identity getIdentity() {
+        return identity;
+    }
+
     public InetAddress getAddress() {
-        return address;
+        return identity.getPeerAddress().getAddress();
     }
 
     public Integer getPort() {
-        return port;
+        return identity.getPeerAddress().getPort();
     }
 
     public Long getLifeTimeInSec() {
@@ -114,19 +114,18 @@ public class RegistrationUpdate {
     @Override
     public String toString() {
         return String.format(
-                "RegistrationUpdate [registrationId=%s, address=%s, port=%s, lifeTimeInSec=%s, smsNumber=%s, bindingMode=%s, objectLinks=%s]",
-                registrationId, address, port, lifeTimeInSec, smsNumber, bindingMode, Arrays.toString(objectLinks));
+                "RegistrationUpdate [registrationId=%s, identity=%s, lifeTimeInSec=%s, smsNumber=%s, bindingMode=%s, objectLinks=%s]",
+                registrationId, identity, lifeTimeInSec, smsNumber, bindingMode, Arrays.toString(objectLinks));
     }
 
     @Override
     public int hashCode() {
         final int prime = 31;
         int result = 1;
-        result = prime * result + ((address == null) ? 0 : address.hashCode());
         result = prime * result + ((bindingMode == null) ? 0 : bindingMode.hashCode());
+        result = prime * result + ((identity == null) ? 0 : identity.hashCode());
         result = prime * result + ((lifeTimeInSec == null) ? 0 : lifeTimeInSec.hashCode());
         result = prime * result + Arrays.hashCode(objectLinks);
-        result = prime * result + ((port == null) ? 0 : port.hashCode());
         result = prime * result + ((registrationId == null) ? 0 : registrationId.hashCode());
         result = prime * result + ((smsNumber == null) ? 0 : smsNumber.hashCode());
         return result;
@@ -141,13 +140,13 @@ public class RegistrationUpdate {
         if (getClass() != obj.getClass())
             return false;
         RegistrationUpdate other = (RegistrationUpdate) obj;
-        if (address == null) {
-            if (other.address != null)
-                return false;
-        } else if (!address.equals(other.address))
-            return false;
         if (bindingMode != other.bindingMode)
             return false;
+        if (identity == null) {
+            if (other.identity != null)
+                return false;
+        } else if (!identity.equals(other.identity))
+            return false;
         if (lifeTimeInSec == null) {
             if (other.lifeTimeInSec != null)
                 return false;
@@ -155,11 +154,6 @@ public class RegistrationUpdate {
             return false;
         if (!Arrays.equals(objectLinks, other.objectLinks))
             return false;
-        if (port == null) {
-            if (other.port != null)
-                return false;
-        } else if (!port.equals(other.port))
-            return false;
         if (registrationId == null) {
             if (other.registrationId != null)
                 return false;
diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/log/CoapMessageTracer.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/log/CoapMessageTracer.java
index baf5e532..8e1b2274 100644
--- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/log/CoapMessageTracer.java
+++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/log/CoapMessageTracer.java
@@ -15,7 +15,7 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.demo.servlet.log;
 
-import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -35,19 +35,19 @@ public class CoapMessageTracer implements MessageInterceptor {
     public void addListener(String endpoint, CoapMessageListener listener) {
         Registration registration = registry.getByEndpoint(endpoint);
         if (registration != null) {
-            listeners.put(toStringAddress(registration.getAddress(), registration.getPort()), listener);
+            listeners.put(toStringAddress(registration.getIdentity().getPeerAddress()), listener);
         }
     }
 
     public void removeListener(String endpoint) {
         Registration registration = registry.getByEndpoint(endpoint);
         if (registration != null) {
-            listeners.remove(toStringAddress(registration.getAddress(), registration.getPort()));
+            listeners.remove(toStringAddress(registration.getIdentity().getPeerAddress()));
         }
     }
 
-    private String toStringAddress(InetAddress clientAddress, int clientPort) {
-        return clientAddress.toString() + ":" + clientPort;
+    private String toStringAddress(InetSocketAddress clientAddress) {
+        return clientAddress.getAddress() + ":" + clientAddress.getPort();
     }
 
     public CoapMessageTracer(RegistrationService registry) {
@@ -56,8 +56,7 @@ public class CoapMessageTracer implements MessageInterceptor {
 
     @Override
     public void sendRequest(Request request) {
-        CoapMessageListener listener = listeners.get(toStringAddress(request.getDestination(),
-                request.getDestinationPort()));
+        CoapMessageListener listener = listeners.get(toStringAddress(request.getDestinationContext().getPeerAddress()));
         if (listener != null) {
             listener.trace(new CoapMessage(request, false));
         }
@@ -65,8 +64,8 @@ public class CoapMessageTracer implements MessageInterceptor {
 
     @Override
     public void sendResponse(Response response) {
-        CoapMessageListener listener = listeners.get(toStringAddress(response.getDestination(),
-                response.getDestinationPort()));
+        CoapMessageListener listener = listeners
+                .get(toStringAddress(response.getDestinationContext().getPeerAddress()));
         if (listener != null) {
             listener.trace(new CoapMessage(response, false));
         }
@@ -74,8 +73,7 @@ public class CoapMessageTracer implements MessageInterceptor {
 
     @Override
     public void sendEmptyMessage(EmptyMessage message) {
-        CoapMessageListener listener = listeners.get(toStringAddress(message.getDestination(),
-                message.getDestinationPort()));
+        CoapMessageListener listener = listeners.get(toStringAddress(message.getDestinationContext().getPeerAddress()));
         if (listener != null) {
             listener.trace(new CoapMessage(message, false));
         }
@@ -83,7 +81,7 @@ public class CoapMessageTracer implements MessageInterceptor {
 
     @Override
     public void receiveRequest(Request request) {
-        CoapMessageListener listener = listeners.get(toStringAddress(request.getSource(), request.getSourcePort()));
+        CoapMessageListener listener = listeners.get(toStringAddress(request.getSourceContext().getPeerAddress()));
         if (listener != null) {
             listener.trace(new CoapMessage(request, true));
         }
@@ -92,7 +90,7 @@ public class CoapMessageTracer implements MessageInterceptor {
 
     @Override
     public void receiveResponse(Response response) {
-        CoapMessageListener listener = listeners.get(toStringAddress(response.getSource(), response.getSourcePort()));
+        CoapMessageListener listener = listeners.get(toStringAddress(response.getSourceContext().getPeerAddress()));
         if (listener != null) {
             listener.trace(new CoapMessage(response, true));
         }
@@ -101,7 +99,7 @@ public class CoapMessageTracer implements MessageInterceptor {
 
     @Override
     public void receiveEmptyMessage(EmptyMessage message) {
-        CoapMessageListener listener = listeners.get(toStringAddress(message.getSource(), message.getSourcePort()));
+        CoapMessageListener listener = listeners.get(toStringAddress(message.getSourceContext().getPeerAddress()));
         if (listener != null) {
             listener.trace(new CoapMessage(message, true));
         }
diff --git a/pom.xml b/pom.xml
index f9c27e8d..7ea0a888 100644
--- a/pom.xml
+++ b/pom.xml
@@ -105,7 +105,7 @@ Contributors:
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <californium.version>2.0.0-M5</californium.version>
+        <californium.version>2.0.0-M6</californium.version>
         <log4j.version>2.1</log4j.version>
         <slf4j.api.version>1.7.10</slf4j.api.version>
         <jetty.version>9.1.4.v20140401</jetty.version>
@@ -369,6 +369,11 @@ Contributors:
                 <artifactId>californium-core</artifactId>
                 <version>${californium.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.eclipse.californium</groupId>
+                <artifactId>element-connector</artifactId>
+                <version>${californium.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.eclipse.californium</groupId>
                 <artifactId>scandium</artifactId>


Test patch:
diff --git a/leshan-integration-tests/pom.xml b/leshan-integration-tests/pom.xml
index 49949ff8..0e1747ab 100644
--- a/leshan-integration-tests/pom.xml
+++ b/leshan-integration-tests/pom.xml
@@ -47,6 +47,20 @@ Contributors:
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.eclipse.californium</groupId>
+            <artifactId>californium-core</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <version>${californium.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.californium</groupId>
+            <artifactId>element-connector</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <version>${californium.version}</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java
index 8937bc99..593a1a88 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java
@@ -20,6 +20,7 @@ import java.util.Random;
 
 import org.eclipse.californium.core.coap.Request;
 import org.eclipse.californium.core.network.serialization.UdpDataSerializer;
+import org.eclipse.californium.core.test.lockstep.LockstepEndpoint;
 import org.eclipse.californium.elements.RawData;
 import org.eclipse.leshan.client.californium.impl.CoapRequestBuilder;
 import org.eclipse.leshan.core.request.UplinkRequest;
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockstepEndpoint.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockstepEndpoint.java
deleted file mode 100644
index 3357a9c8..00000000
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockstepEndpoint.java
+++ /dev/null
@@ -1,1719 +0,0 @@
-package org.eclipse.leshan.integration.tests;
-
-import static org.hamcrest.CoreMatchers.*;
-import static org.junit.Assert.*;
-
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.californium.core.Utils;
-import org.eclipse.californium.core.coap.BlockOption;
-import org.eclipse.californium.core.coap.CoAP;
-import org.eclipse.californium.core.coap.CoAP.Code;
-import org.eclipse.californium.core.coap.CoAP.ResponseCode;
-import org.eclipse.californium.core.coap.CoAP.Type;
-import org.eclipse.californium.core.coap.EmptyMessage;
-import org.eclipse.californium.core.coap.Message;
-import org.eclipse.californium.core.coap.Option;
-import org.eclipse.californium.core.coap.OptionNumberRegistry;
-import org.eclipse.californium.core.coap.Request;
-import org.eclipse.californium.core.coap.Response;
-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.elements.RawData;
-import org.eclipse.californium.elements.RawDataChannel;
-import org.eclipse.californium.elements.UDPConnector;
-
-public class LockstepEndpoint {
-
-    private static boolean DEFAULT_VERBOSE = false;
-
-    private final UDPConnector connector;
-    private InetSocketAddress destination;
-    private final LinkedBlockingQueue<RawData> incoming;
-    /**
-     * Last incoming message.
-     *
-     * Deduplication is based on that last received message.
-     *
-     * @see #receiveNextExpectedMessage(MidExpectation)
-     */
-    private Message lastIncomingMessage;
-
-    private final HashMap<String, Object> storage;
-
-    private final DataSerializer serializer;
-    private final DataParser parser;
-    private boolean verbose = DEFAULT_VERBOSE;
-    private MultiMessageExpectation multi;
-
-    public LockstepEndpoint() {
-        this(null);
-    }
-
-    public LockstepEndpoint(final InetSocketAddress destination) {
-
-        this.destination = destination;
-        storage = new HashMap<>();
-        incoming = new LinkedBlockingQueue<>();
-        connector = new UDPConnector(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
-        connector.setRawDataReceiver(new RawDataChannel() {
-
-            @Override
-            public void receiveData(final RawData raw) {
-                incoming.offer(raw);
-            }
-        });
-        serializer = new UdpDataSerializer();
-        parser = new UdpDataParser();
-
-        try {
-            connector.start();
-            // Thread.sleep(100);
-        } catch (final Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    public void destroy() {
-        if (connector != null) {
-            connector.destroy();
-        }
-    }
-
-    public void print(final String text) {
-        if (verbose) {
-            System.out.println(text);
-        }
-    }
-
-    public void setVerbose(final boolean v) {
-        verbose = v;
-    }
-
-    public boolean isVerbose() {
-        return verbose;
-    }
-
-    public int getPort() {
-        return connector.getAddress().getPort();
-    }
-
-    public InetAddress getAddress() {
-        return connector.getAddress().getAddress();
-    }
-
-    public Object get(final String var) {
-        return storage.get(var);
-    }
-
-    /**
-     * Get MID from stored values.
-     *
-     * The MID may be stored either by {@link MessageExpectation#storeMID(String)} or
-     * {@link MessageExpectation#storeBoth(String)}.
-     *
-     * @param var name of variable
-     * @return MID
-     * @throws NoSuchElementException, if nothing so stored under the name, or the item doesn't contain a MID.
-     */
-    public int getMID(final String var) {
-        final Object item = storage.get(var);
-        if (null != item) {
-            if (item instanceof Integer) {
-                // saveMID
-                return (Integer) item;
-            }
-            if (item instanceof Object[]) {
-                // saveBoth
-                final Object[] items = (Object[]) item;
-                return (Integer) items[0];
-            }
-            throw new NoSuchElementException("Variable '" + var + "' is no MID (" + item.getClass() + ")");
-        }
-        throw new NoSuchElementException("No variable '" + var + "'");
-    }
-
-    /**
-     * Get token from stored values.
-     *
-     * The token may be stored either by {@link MessageExpectation#storeToken(String)} or
-     * {@link MessageExpectation#storeBoth(String)}.
-     *
-     * @param var name of variable
-     * @return token
-     * @throws NoSuchElementException, if nothing so stored under the name, or the item doesn't contain a token.
-     */
-    public byte[] getToken(final String var) {
-        final Object item = storage.get(var);
-        if (null != item) {
-            if (item instanceof byte[]) {
-                // saveToken
-                return (byte[]) item;
-            }
-            if (item instanceof Object[]) {
-                // saveBoth
-                final Object[] items = (Object[]) item;
-                return (byte[]) items[1];
-            }
-            throw new NoSuchElementException("Variable '" + var + "' is no token (" + item.getClass() + ")");
-        }
-        throw new NoSuchElementException("No variable '" + var + "'");
-    }
-
-    public MultiMessageExpectation startMultiExpectation() {
-        multi = new MultiMessageExpectation();
-        return multi;
-    }
-
-    public void goMultiExpectation() throws Exception {
-        assertNotNull("multi expectations not started!", multi);
-        multi.go();
-        multi = null;
-    }
-
-    public RequestExpectation expectRequest() {
-        return new RequestExpectation();
-    }
-
-    public RequestExpectation expectRequest(final Type type, final Code code, final String path) {
-        return new RequestExpectation().type(type).code(code).path(path);
-    }
-
-    public ResponseExpectation expectResponse() {
-        return new ResponseExpectation();
-    }
-
-    public ResponseExpectation expectResponse(final Type type, final ResponseCode code, final byte[] token, final int mid) {
-        return expectResponse().type(type).code(code).token(token).mid(mid);
-    }
-
-    public EmptyMessageExpectation expectEmpty(final Type type, final int mid) {
-        return new EmptyMessageExpectation(type, mid);
-    }
-
-    public RequestProperty sendRequest(final Type type, final Code code, final byte[] token, final int mid) {
-        if (type == null) {
-            throw new NullPointerException();
-        }
-        if (code == null) {
-            throw new NullPointerException();
-        }
-        if (token == null) {
-            throw new NullPointerException();
-        }
-        if (mid < 0 || mid > Message.MAX_MID) {
-            throw new RuntimeException();
-        }
-        return new RequestProperty(type, code, token, mid);
-    }
-
-    public ResponseProperty sendResponse(final Type type, final ResponseCode code) {
-        if (type == null) {
-            throw new NullPointerException();
-        }
-        if (code == null) {
-            throw new NullPointerException();
-        }
-        return new ResponseProperty(type, code);
-    }
-
-    public EmptyMessageProperty sendEmpty(final Type type) {
-        if (type == null) {
-            throw new NullPointerException();
-        }
-        return sendEmpty(type, Message.NONE);
-    }
-
-    public EmptyMessageProperty sendEmpty(final Type type, final int mid) {
-        return new EmptyMessageProperty(type, mid);
-    }
-
-    public void send(final RawData raw) {
-        if (raw.getAddress() == null || raw.getPort() == 0) {
-            throw new RuntimeException("Message has no destination address/port");
-        }
-        connector.send(raw);
-    }
-
-    public void setDestination(final InetSocketAddress destination) {
-        this.destination = destination;
-    }
-
-    /**
-     * Receive next expected message.
-     *
-     * Apply smart deduplication based on {@link #lastIncomingMessage} and the MID, if the repeated MID is not expected.
-     * If no next message arrives, reports an assert.
-     *
-     * <code>
-     *    ... expectRequest.storeMID("A").type(CON) ...
-     *
-     *        // wait until retransmission
-     *
-     *        // OK, expecting the MID suppresses deduplication
-     *    ... expectRequest.sameMID("A").type(CON) ...
-     *        // will fail, not expecting the MID,
-     *        // the retransmission would be dropped by deduplication
-     *    ... expectRequest.type(CON)...
-     *
-     * </code>
-     *
-     * MID expectations are based on {@link MessageExpectation#mid(int)}, {@link MessageExpectation#sameMID(String)} or
-     * {@link MessageExpectation#sameBoth(String)}.
-     *
-     * @param midExpectation MID expectation
-     * @return next received message
-     * @throws InterruptedException if waiting for message is interrupted.
-     */
-    public Message receiveNextExpectedMessage(final MidExpectation midExpectation) throws InterruptedException {
-        while (true) {
-            final Message msg = receiveNextMessage(2, TimeUnit.SECONDS);
-            assertNotNull("did not receive message within expected time frame (2 secs)", msg);
-
-            if (null != midExpectation && null != lastIncomingMessage && lastIncomingMessage.getMID() == msg.getMID()
-                    && lastIncomingMessage.getType() == msg.getType() && !midExpectation.expectMID(msg)) {
-                // received message with same MID but not expected
-                // => discard message!
-                print("discarding duplicate message: " + msg);
-            } else {
-                lastIncomingMessage = msg;
-                return msg;
-            }
-        }
-    }
-
-    /**
-     * Receive next message.
-     *
-     * @param timeout timeout for waiting for a message
-     * @param unit timeunit for waiting for a message
-     * @return received Message, or {@code null}, if no message arrived in time.
-     * @throws InterruptedException if waiting for message is interrupted.
-     */
-    public Message receiveNextMessage(final int timeout, final TimeUnit unit) throws InterruptedException {
-        final RawData raw = incoming.poll(timeout, unit); // or take()?
-        if (raw != null) {
-            final Message message = parser.parseMessage(raw);
-            message.setSource(raw.getAddress());
-            message.setSourcePort(raw.getPort());
-            return message;
-        }
-        return null;
-    }
-
-    public abstract class MessageExpectation implements Action {
-
-        /**
-         * List of MID expectation. Used for smart deduplication.
-         */
-        private final List<MidExpectation> midExpectations = new LinkedList<>();
-        private final List<Expectation<Message>> expectations = new LinkedList<>();
-
-        public MessageExpectation mid(final int mid) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    assertEquals("Wrong MID:", mid, message.getMID());
-                    print("Correct MID: " + mid);
-                }
-
-                @Override
-                public String toString() {
-                    return "Expected MID: " + mid;
-                }
-            });
-            midExpectations.add(new MidExpectation() {
-
-                @Override
-                public boolean expectMID(final Message message) {
-                    return message.getMID() == mid;
-                }
-
-            });
-            return this;
-        }
-
-        /**
-         * Check, if the MID stored under var is the same as the MID of the message. The MID may be stored either by
-         * {@link #storeMID(String)} or {@link #storeBoth(String)}.
-         *
-         * Provides a fluent API to chain expectations.
-         *
-         * @param var variable name with the stored MID
-         * @return this for fluent API
-         */
-        public MessageExpectation sameMID(final String var) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    final int expected = getMID(var);
-                    assertEquals("Wrong MID:", expected, message.getMID());
-                    print("Correct MID: " + expected);
-                }
-
-                @Override
-                public String toString() {
-                    final int expected = getMID(var);
-                    return "Expected MID: " + expected;
-                }
-            });
-            midExpectations.add(new MidExpectation() {
-
-                @Override
-                public boolean expectMID(final Message message) {
-                    final int expected = getMID(var);
-                    return message.getMID() == expected;
-                }
-
-            });
-            return this;
-        }
-
-        /**
-         * Check, if the MID of the response is not already contained in the MID set with the provided name. After the
-         * check, add the MID to the set.
-         *
-         * Provides a fluent API to chain expectations.
-         *
-         * @param var name of MID set
-         * @return this MessageExpectation
-         */
-        public MessageExpectation newMID(final String var) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message response) {
-                    final int mid = response.getMID();
-                    @SuppressWarnings("unchecked")
-                    Set<Integer> usedMIDs = (Set<Integer>) storage.get(var);
-                    if (usedMIDs != null && !usedMIDs.isEmpty()) {
-                        assertFalse("MID: " + mid + " is not new! " + usedMIDs, usedMIDs.contains(mid));
-                    }
-                    if (usedMIDs == null) {
-                        usedMIDs = new HashSet<>();
-                    }
-                    usedMIDs.add(mid);
-                    storage.put(var, usedMIDs);
-                }
-
-                @Override
-                public String toString() {
-                    @SuppressWarnings("unchecked")
-                    final
-                    Set<Integer> usedMIDs = (Set<Integer>) storage.get(var);
-                    return "Expected new MID, not " + usedMIDs;
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation type(final Type... types) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    final Type type = message.getType();
-                    assertTrue("Unexpected type: " + type + ", expected: " + Arrays.toString(types),
-                            Arrays.asList(types).contains(type));
-                }
-
-                @Override
-                public String toString() {
-                    if (types.length == 1) {
-                        return "Expected type: " + types[0];
-                    } else {
-                        return "Expected types: " + Arrays.toString(types);
-                    }
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation token(final byte[] token) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    assertArrayEquals("Wrong token:", token, message.getToken());
-                    print("Correct token: " + Utils.toHexString(token));
-                }
-
-                @Override
-                public String toString() {
-                    return "Expected token: " + Utils.toHexString(token);
-                }
-            });
-            return this;
-        }
-
-        /**
-         * Check, if the token stored under var is the same as the token of the message. The token may be stored either
-         * by {@link #storeToken(String)} or {@link #storeBoth(String)}.
-         *
-         * Provides a fluent API to chain expectations.
-         *
-         * @param var variable name with the stored token
-         * @return this for fluent API
-         */
-        public MessageExpectation sameToken(final String var) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    final byte[] expected = getToken(var);
-                    assertEquals("Wrong token:", expected, message.getToken());
-                    print("Correct token: " + Utils.toHexString(expected));
-                }
-
-                @Override
-                public String toString() {
-                    final byte[] expected = getToken(var);
-                    return "Expected token: " + Utils.toHexString(expected);
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation size1(final int expectedSize) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    assertThat("Wrong size1", message.getOptions().getSize1(), is(expectedSize));
-                }
-
-                @Override
-                public String toString() {
-                    return "Expected Size1 option: " + expectedSize;
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation payload(final String payload) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    final int expectedLength = payload.length();
-                    final int actualLength = message.getPayloadSize();
-                    assertEquals("Wrong payload length: ", expectedLength, actualLength);
-                    assertEquals("Wrong payload:", payload, message.getPayloadString());
-                    print("Correct payload (" + actualLength + " bytes):" + System.lineSeparator()
-                    + message.getPayloadString());
-                }
-
-                @Override
-                public String toString() {
-                    return "Expected payload: '" + payload + "'";
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation payload(final String payload, final int from, final int to) {
-            payload(payload.substring(from, to));
-            return this;
-        }
-
-        public MessageExpectation block1(final int num, final boolean m, final int size) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    assertTrue("No Block1 option:", message.getOptions().hasBlock1());
-                    final BlockOption block1 = message.getOptions().getBlock1();
-                    assertEquals("Wrong Block1 num:", num, block1.getNum());
-                    assertEquals("Wrong Block1 m:", m, block1.isM());
-                    assertEquals("Wrong Block1 size:", size, block1.getSize());
-                    print("Correct Block1 option: " + block1);
-                }
-
-                @Override
-                public String toString() {
-                    final BlockOption option = new BlockOption(BlockOption.size2Szx(size), m, num);
-                    return "Expected Block1 option: " + option;
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation block2(final int num, final boolean m, final int size) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    assertTrue("No Block2 option:", message.getOptions().hasBlock2());
-                    final BlockOption block2 = message.getOptions().getBlock2();
-                    assertEquals("Wrong Block2 num:", num, block2.getNum());
-                    assertEquals("Wrong Block2 m:", m, block2.isM());
-                    assertEquals("Wrong Block2 size:", size, block2.getSize());
-                    print("Correct Block2 option: " + block2);
-                }
-
-                @Override
-                public String toString() {
-                    final BlockOption option = new BlockOption(BlockOption.size2Szx(size), m, num);
-                    return "Expected Block2 option: " + option;
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation observe(final int observe) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    assertTrue("No observe option:", message.getOptions().hasObserve());
-                    final int actual = message.getOptions().getObserve();
-                    assertEquals("Wrong observe sequence number:", observe, actual);
-                    print("Correct observe sequence number: " + observe);
-                }
-
-                @Override
-                public String toString() {
-                    return "Expected observe sequence number: " + observe;
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation hasEtag(final byte[] etag) {
-
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    assertThat(message.getOptions().getETags(), hasItem(etag));
-                }
-
-                @Override
-                public String toString() {
-                    return String.format("Expected etag: %s", Utils.toHexString(etag));
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation noOption(final int... numbers) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    final List<Option> options = message.getOptions().asSortedList();
-                    for (final Option option : options) {
-                        for (final int n : numbers) {
-                            if (option.getNumber() == n) {
-                                fail("Must not have option number " + n + " but has " + option);
-                            }
-                        }
-                    }
-                }
-
-                @Override
-                public String toString() {
-                    final StringBuilder result = new StringBuilder("Expected no options: [");
-                    if (0 < numbers.length) {
-                        final int end = numbers.length - 1;
-                        int index = 0;
-                        for (; index < end; ++index) {
-                            result.append(OptionNumberRegistry.toString(numbers[index])).append(",");
-                        }
-                        result.append(OptionNumberRegistry.toString(numbers[index]));
-                    }
-                    result.append(']');
-                    return result.toString();
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation storeMID(final String var) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    storage.put(var, message.getMID());
-                }
-
-                @Override
-                public String toString() {
-                    return "";
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation storeToken(final String var) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    storage.put(var, message.getToken());
-                }
-
-                @Override
-                public String toString() {
-                    return "";
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation storeBoth(final String var) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message request) {
-                    final Object[] pair = new Object[2];
-                    pair[0] = request.getMID();
-                    pair[1] = request.getToken();
-                    storage.put(var, pair);
-                }
-            });
-            return this;
-        }
-
-        public MessageExpectation sameBoth(final String var) {
-            expectations.add(new Expectation<Message>() {
-
-                @Override
-                public void check(final Message message) {
-                    final Object[] pair = (Object[]) storage.get(var);
-                    assertEquals("Wrong MID:", pair[0], message.getMID());
-                    assertArrayEquals("Wrong token:", (byte[]) pair[1], message.getToken());
-                    print("Correct MID: " + message.getMID() + " and token: " + Utils.toHexString(message.getToken()));
-                }
-
-                @Override
-                public String toString() {
-                    final Object[] pair = (Object[]) storage.get(var);
-                    return "Expected MID: " + pair[0] + " and token " + Utils.toHexString((byte[]) pair[1]);
-                }
-            });
-            midExpectations.add(new MidExpectation() {
-
-                @Override
-                public boolean expectMID(final Message message) {
-                    final int expected = getMID(var);
-                    return message.getMID() == expected;
-                }
-
-            });
-            return this;
-        }
-
-        public void check(final Message message) {
-            for (final Expectation<Message> expectation : expectations) {
-                expectation.check(message);
-            }
-        }
-
-        /**
-         * Check, if the message with the contained MID is expected.
-         *
-         * @param message message to check
-         * @return true, message is expected, don't drop it for deduplication. false, message is not expected and could
-         *         be dropped.
-         */
-        public boolean expectMID(final Message message) {
-            for (final MidExpectation expectation : midExpectations) {
-                if (expectation.expectMID(message)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public void go() throws Exception {
-            if (null != multi) {
-                add(multi);
-                return;
-            }
-
-            final Message msg = receiveNextExpectedMessage(new MidExpectation() {
-
-                @Override
-                public boolean expectMID(final Message message) {
-                    return MessageExpectation.this.expectMID(message);
-                }
-            });
-
-            go(msg);
-        }
-
-        @Override
-        public String toString() {
-            final StringBuilder result = new StringBuilder("{");
-            for (final Expectation<Message> expectation : expectations) {
-                final String info = expectation.toString();
-                if (!info.isEmpty()) {
-                    result.append(info).append(",");
-                }
-            }
-            final int end = result.length() - 1;
-            if (0 <= end && ',' == result.charAt(end)) {
-                result.setLength(end);
-            }
-            result.append("}");
-            return result.toString();
-        }
-
-        public abstract void go(Message msg) throws Exception;
-
-        public abstract void add(MultiMessageExpectation multi);
-    }
-
-    public class RequestExpectation extends MessageExpectation {
-
-        private final List<Expectation<Request>> expectations = new LinkedList<>();
-
-        @Override
-        public RequestExpectation mid(final int mid) {
-            super.mid(mid);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation type(final Type... type) {
-            super.type(type);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation token(final byte[] token) {
-            super.token(token);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation payload(final String payload) {
-            super.payload(payload);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation payload(final String payload, final int from, final int to) {
-            super.payload(payload, from, to);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation block1(final int num, final boolean m, final int size) {
-            super.block1(num, m, size);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation block2(final int num, final boolean m, final int size) {
-            super.block2(num, m, size);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation observe(final int observe) {
-            super.observe(observe);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation hasEtag(final byte[] etag) {
-            super.hasEtag(etag);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation noOption(final int... numbers) {
-            super.noOption(numbers);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation storeMID(final String var) {
-            super.storeMID(var);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation sameMID(final String var) {
-            super.sameMID(var);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation storeToken(final String var) {
-            super.storeToken(var);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation sameToken(final String var) {
-            super.sameToken(var);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation storeBoth(final String var) {
-            super.storeBoth(var);
-            return this;
-        }
-
-        @Override
-        public RequestExpectation sameBoth(final String var) {
-            super.sameBoth(var);
-            return this;
-        }
-
-        public RequestExpectation code(final Code code) {
-            expectations.add(new Expectation<Request>() {
-
-                @Override
-                public void check(final Request request) {
-                    assertEquals(code, request.getCode());
-                    print("Correct code: " + code + " (" + code.value + ")");
-                }
-            });
-            return this;
-        }
-
-        public RequestExpectation path(final String path) {
-            expectations.add(new Expectation<Request>() {
-
-                @Override
-                public void check(final Request request) {
-                    assertEquals(path, request.getOptions().getUriPathString());
-                    print("Correct URI path: " + path);
-                }
-            });
-            return this;
-        }
-
-        @Override
-        public RequestExpectation size1(final int expectedSize) {
-            super.size1(expectedSize);
-            return this;
-        }
-
-        public void check(final Request request) {
-            super.check(request);
-            for (final Expectation<Request> expectation : expectations) {
-                expectation.check(request);
-            }
-        }
-
-        @Override
-        public void go(final Message msg) throws Exception {
-            if (CoAP.isRequest(msg.getRawCode())) {
-                check((Request) msg);
-            } else {
-                fail("Expected request for " + this + ", but received " + msg);
-            }
-        }
-
-        @Override
-        public void add(final MultiMessageExpectation multi) {
-            multi.add(this);
-        }
-    }
-
-    public class ResponseExpectation extends MessageExpectation {
-
-        private final List<Expectation<Response>> expectations = new LinkedList<>();
-
-        @Override
-        public ResponseExpectation mid(final int mid) {
-            super.mid(mid);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation type(final Type... type) {
-            super.type(type);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation token(final byte[] token) {
-            super.token(token);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation payload(final String payload) {
-            super.payload(payload);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation payload(final String payload, final int from, final int to) {
-            super.payload(payload, from, to);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation block1(final int num, final boolean m, final int size) {
-            super.block1(num, m, size);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation block2(final int num, final boolean m, final int size) {
-            super.block2(num, m, size);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation observe(final int observe) {
-            super.observe(observe);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation hasEtag(final byte[] etag) {
-            super.hasEtag(etag);
-            return this;
-        }
-
-        @Override
-        public MessageExpectation size1(final int expectedSize) {
-            super.size1(expectedSize);
-            return this;
-        }
-
-        public ResponseExpectation size2(final int expectedSize) {
-            expectations.add(new Expectation<Response>() {
-
-                @Override
-                public void check(final Response response) {
-                    assertThat("Wrong size2", response.getOptions().getSize2(), is(expectedSize));
-                }
-
-                @Override
-                public String toString() {
-                    return "Expected Size2 option: " + expectedSize;
-                }
-            });
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation noOption(final int... numbers) {
-            super.noOption(numbers);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation storeMID(final String var) {
-            super.storeMID(var);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation sameMID(final String var) {
-            super.sameMID(var);
-            return this;
-        }
-
-        @Override
-        public ResponseExpectation newMID(final String var) {
-            super.newMID(var);
-            return this;
-        }
-
-        public ResponseExpectation code(final ResponseCode code) {
-            expectations.add(new Expectation<Response>() {
-
-                @Override
-                public void check(final Response response) {
-                    assertEquals(code, response.getCode());
-                    print("Correct code: " + code + " (" + code.value + ")");
-                }
-            });
-            return this;
-        }
-
-        public ResponseExpectation storeType(final String key) {
-            expectations.add(new Expectation<Response>() {
-
-                @Override
-                public void check(final Response response) {
-                    final Type type = response.getType();
-                    storage.put(key, type);
-                }
-            });
-            return this;
-        }
-
-        public ResponseExpectation storeObserve(final String key) {
-            expectations.add(new Expectation<Response>() {
-
-                @Override
-                public void check(final Response response) {
-                    assertTrue("Has no observe option", response.getOptions().hasObserve());
-                    storage.put(key, response.getOptions().getObserve());
-                }
-            });
-            return this;
-        }
-
-        public ResponseExpectation storeETag(final String var) {
-            expectations.add(new Expectation<Response>() {
-
-                @Override
-                public void check(final Response response) {
-                    assertTrue("Response has no ETag", response.getOptions().getETagCount() > 0);
-                    storage.put(var, response.getOptions().getETags().get(0));
-                }
-            });
-            return this;
-        }
-
-        public ResponseExpectation sameETag(final String var) {
-            expectations.add(new Expectation<Response>() {
-
-                @Override
-                public void check(final Response response) {
-                    assertTrue("Response has no ETag", response.getOptions().getETagCount() > 0);
-                    final Object obj = storage.get(var);
-                    assertThat("Object stored under " + var + " is not an ETag", obj, is(instanceOf(byte[].class)));
-                    assertThat("Response contains wrong ETag", (byte[]) obj,
-                            is(response.getOptions().getETags().get(0)));
-                }
-            });
-            return this;
-        }
-
-        public ResponseExpectation largerObserve(final String key) {
-            expectations.add(new Expectation<Response>() {
-
-                @Override
-                public void check(final Response response) {
-                    assertTrue("Has no observe option", response.getOptions().hasObserve());
-                    final Object value = storage.get(key);
-                    if (value == null) {
-                        throw new IllegalArgumentException("Key " + key + " not found");
-                    }
-                    final int V1 = (Integer) value;
-                    final int V2 = response.getOptions().getObserve();
-                    final boolean fresh = V1 < V2 && V2 - V1 < 1 << 23 || V1 > V2 && V1 - V2 > 1 << 23;
-                    assertTrue("Was not a fresh notification. Last obs=" + V1 + ", new=" + V2, fresh);
-                }
-            });
-            return this;
-        }
-
-        public ResponseExpectation checkObs(final String former, final String next) {
-            largerObserve(former);
-            storeObserve(next);
-            return this;
-        }
-
-        public ResponseExpectation loadObserve(final String key) {
-            expectations.add(new Expectation<Response>() {
-
-                @Override
-                public void check(final Response response) {
-                    assertTrue("No observe option:", response.getOptions().hasObserve());
-                    final int expected = (Integer) storage.get(key);
-                    final int actual = response.getOptions().getObserve();
-                    assertEquals("Wrong observe sequence number:", expected, actual);
-                    print("Correct observe sequence number: " + expected);
-                }
-            });
-            return this;
-        }
-
-        public void check(final Response response) {
-            super.check(response);
-            for (final Expectation<Response> expectation : expectations) {
-                expectation.check(response);
-            }
-        }
-
-        @Override
-        public void go(final Message msg) throws Exception {
-            if (CoAP.isResponse(msg.getRawCode())) {
-                check((Response) msg);
-            } else {
-                fail("Expected response for " + this + ", but received " + msg);
-            }
-        }
-
-        @Override
-        public void add(final MultiMessageExpectation multi) {
-            multi.add(this);
-        }
-
-    }
-
-    public class EmptyMessageExpectation extends MessageExpectation {
-
-        public EmptyMessageExpectation(final Type type, final int mid) {
-            super();
-            type(type).mid(mid);
-        }
-
-        public EmptyMessageExpectation(final Type type, final String midVar) {
-            super();
-            type(type).sameMID(midVar);
-        }
-
-        @Override
-        public void go(final Message msg) throws Exception {
-            if (CoAP.isEmptyMessage(msg.getRawCode())) {
-                check(msg);
-            } else {
-                fail("Expected empty message for " + this + ", but received " + msg);
-            }
-        }
-
-        @Override
-        public void add(final MultiMessageExpectation multi) {
-            multi.add(this);
-        }
-    }
-
-    public class MultiMessageExpectation implements Action {
-
-        private int counter;
-        private EmptyMessageExpectation emptyExpectation;
-        private RequestExpectation requestExpectation;
-        private ResponseExpectation responseExpectation;
-
-        public MultiMessageExpectation add(final EmptyMessageExpectation emptyExpectation) {
-            if (null == emptyExpectation) {
-                throw new IllegalArgumentException("no empty message expectation!");
-            }
-            if (null != this.emptyExpectation) {
-                throw new IllegalStateException("empty message expectation already set!");
-            }
-            this.emptyExpectation = emptyExpectation;
-            counter++;
-            return this;
-        }
-
-        public MultiMessageExpectation add(final RequestExpectation requestExpectation) {
-            if (null == requestExpectation) {
-                throw new IllegalStateException("no request expectation!");
-            }
-            if (null != this.requestExpectation) {
-                throw new IllegalStateException("request expectation already set!");
-            }
-            this.requestExpectation = requestExpectation;
-            counter++;
-            return this;
-        }
-
-        public MultiMessageExpectation add(final ResponseExpectation responseExpectation) {
-            if (null == responseExpectation) {
-                throw new IllegalStateException("no response expectation!");
-            }
-            if (null != this.responseExpectation) {
-                throw new IllegalStateException("response expectation already set!");
-            }
-            this.responseExpectation = responseExpectation;
-            counter++;
-            return this;
-        }
-
-        public boolean expectMID(final Message message) {
-            final int rawCode = message.getRawCode();
-            if (CoAP.isEmptyMessage(rawCode)) {
-                if (null != emptyExpectation) {
-                    return emptyExpectation.expectMID(message);
-                }
-            } else if (CoAP.isRequest(rawCode)) {
-                if (null != requestExpectation) {
-                    return requestExpectation.expectMID(message);
-                }
-            } else if (CoAP.isResponse(rawCode)) {
-                if (null != responseExpectation) {
-                    return responseExpectation.expectMID(message);
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public void go() throws Exception {
-            assertTrue("No expectations added!)", 0 < counter);
-            while (0 < counter) {
-                final Message msg = receiveNextExpectedMessage(new MidExpectation() {
-
-                    @Override
-                    public boolean expectMID(final Message message) {
-                        return MultiMessageExpectation.this.expectMID(message);
-                    }
-                });
-                final int rawCode = msg.getRawCode();
-                if (CoAP.isEmptyMessage(rawCode)) {
-                    if (null != emptyExpectation) {
-                        emptyExpectation.go(msg);
-                        emptyExpectation = null;
-                        --counter;
-                    } else {
-                        fail("No empty message expected " + msg);
-                    }
-                } else if (CoAP.isRequest(rawCode)) {
-                    if (null != requestExpectation) {
-                        requestExpectation.go(msg);
-                        requestExpectation = null;
-                        --counter;
-                    } else {
-                        fail("No request expected " + msg);
-                    }
-                } else if (CoAP.isResponse(rawCode)) {
-                    if (null != responseExpectation) {
-                        responseExpectation.go(msg);
-                        responseExpectation = null;
-                        --counter;
-                    } else {
-                        fail("No response expected " + msg);
-                    }
-                }
-            }
-        }
-    }
-
-    public static interface Expectation<T> {
-
-        public void check(T t);
-    }
-
-    public static interface Property<T> {
-
-        public void set(T t);
-    }
-
-    public abstract class MessageProperty implements Action {
-
-        private final List<Property<Message>> properties = new LinkedList<>();
-
-        private final Type type;
-        private byte[] token;
-        private int mid;
-
-        public MessageProperty(final Type type) {
-            this.type = type;
-        }
-
-        public MessageProperty(final Type type, final byte[] token, final int mid) {
-            this.type = type;
-            this.token = token;
-            this.mid = mid;
-        }
-
-        public void setProperties(final Message message) {
-            message.setType(type);
-            message.setToken(token);
-            message.setMID(mid);
-            for (final Property<Message> property : properties) {
-                property.set(message);
-            }
-        }
-
-        public MessageProperty payload(final String payload, final int from, final int to) {
-            properties.add(new Property<Message>() {
-
-                @Override
-                public void set(final Message message) {
-                    message.setPayload(payload.substring(from, to));
-                }
-            });
-            return this;
-        }
-
-        public MessageProperty mid(final int mid) {
-            this.mid = mid;
-            return this;
-        }
-
-        public MessageProperty block1(final int num, final boolean m, final int size) {
-            properties.add(new Property<Message>() {
-
-                @Override
-                public void set(final Message message) {
-                    message.getOptions().setBlock1(BlockOption.size2Szx(size), m, num);
-                }
-            });
-            return this;
-        }
-
-        public MessageProperty block2(final int num, final boolean m, final int size) {
-            properties.add(new Property<Message>() {
-
-                @Override
-                public void set(final Message message) {
-                    message.getOptions().setBlock2(BlockOption.size2Szx(size), m, num);
-                }
-            });
-            return this;
-        }
-
-        public MessageProperty size1(final int size) {
-            properties.add(new Property<Message>() {
-
-                @Override
-                public void set(final Message message) {
-                    message.getOptions().setSize1(size);
-                }
-            });
-            return this;
-        }
-
-        public MessageProperty size2(final int size) {
-            properties.add(new Property<Message>() {
-
-                @Override
-                public void set(final Message message) {
-                    message.getOptions().setSize2(size);
-                }
-            });
-            return this;
-        }
-
-        public MessageProperty observe(final int observe) {
-            properties.add(new Property<Message>() {
-
-                @Override
-                public void set(final Message message) {
-                    message.getOptions().setObserve(observe);
-                }
-            });
-            return this;
-        }
-
-        public MessageProperty etag(final byte[] tag) {
-            properties.add(new Property<Message>() {
-
-                @Override
-                public void set(final Message message) {
-                    message.getOptions().addETag(tag);
-                }
-            });
-            return this;
-        }
-
-        public MessageProperty loadMID(final String var) {
-            properties.add(new Property<Message>() {
-
-                @Override
-                public void set(final Message message) {
-                    final int mid = getMID(var);
-                    message.setMID(mid);
-                }
-            });
-            return this;
-        }
-
-        public MessageProperty loadToken(final String var) {
-            properties.add(new Property<Message>() {
-
-                @Override
-                public void set(final Message message) {
-                    final byte[] tok = getToken(var);
-                    message.setToken(tok);
-                }
-            });
-            return this;
-        }
-    }
-
-    public class EmptyMessageProperty extends MessageProperty {
-
-        public EmptyMessageProperty(final Type type, final int mid) {
-            super(type, new byte[0], mid);
-        }
-
-        public EmptyMessageProperty(final Type type, final String midVar) {
-            super(type);
-            super.loadMID(midVar);
-        }
-
-        @Override
-        public void go() {
-            final EmptyMessage message = new EmptyMessage(null);
-            if (destination != null) {
-                message.setDestination(destination.getAddress());
-                message.setDestinationPort(destination.getPort());
-            }
-            setProperties(message);
-
-            final RawData raw = serializer.serializeEmptyMessage(message);
-            send(raw);
-        }
-    }
-
-    public class RequestProperty extends MessageProperty {
-
-        private final List<Property<Request>> properties = new LinkedList<>();
-
-        private final Code code;
-
-        public RequestProperty(final Type type, final Code code, final byte[] token, final int mid) {
-            super(type, token, mid);
-            this.code = code;
-        }
-
-        @Override
-        public RequestProperty block1(final int num, final boolean m, final int size) {
-            super.block1(num, m, size);
-            return this;
-        }
-
-        @Override
-        public RequestProperty block2(final int num, final boolean m, final int size) {
-            super.block2(num, m, size);
-            return this;
-        }
-
-        @Override
-        public RequestProperty size1(final int size) {
-            super.size1(size);
-            return this;
-        }
-
-        @Override
-        public RequestProperty size2(final int size) {
-            super.size2(size);
-            return this;
-        }
-
-        @Override
-        public RequestProperty observe(final int observe) {
-            super.observe(observe);
-            return this;
-        }
-
-        @Override
-        public RequestProperty etag(final byte[] tag) {
-            super.etag(tag);
-            return this;
-        }
-
-        public RequestProperty payload(final String payload) {
-            properties.add(new Property<Request>() {
-
-                @Override
-                public void set(final Request request) {
-                    request.setPayload(payload);
-                }
-            });
-            return this;
-        }
-
-        @Override
-        public RequestProperty payload(final String payload, final int from, final int to) {
-            super.payload(payload, from, to);
-            return this;
-        }
-
-        public RequestProperty path(final String path) {
-            properties.add(new Property<Request>() {
-
-                @Override
-                public void set(final Request request) {
-                    request.getOptions().setUriPath(path);
-                }
-            });
-            return this;
-        }
-
-        public RequestProperty loadETag(final String var) {
-            properties.add(new Property<Request>() {
-
-                @Override
-                public void set(final Request request) {
-                    final Object obj = storage.get(var);
-                    assertThat("Object stored under variable " + var + " is not a byte array", obj,
-                            is(instanceOf(byte[].class)));
-                    request.getOptions().addETag((byte[]) obj);
-                }
-            });
-            return this;
-        }
-
-        public void setProperties(final Request request) {
-            super.setProperties(request);
-            for (final Property<Request> property : properties) {
-                property.set(request);
-            }
-        }
-
-        @Override
-        public void go() {
-            final Request request = new Request(code);
-            if (destination != null) {
-                request.setDestination(destination.getAddress());
-                request.setDestinationPort(destination.getPort());
-            }
-            setProperties(request);
-
-            final RawData raw = serializer.serializeRequest(request);
-            send(raw);
-        }
-    }
-
-    public class ResponseProperty extends MessageProperty {
-
-        private final List<Property<Response>> properties = new LinkedList<>();
-
-        private final ResponseCode code;
-
-        public ResponseProperty(final Type type, final ResponseCode code) {
-            super(type);
-            this.code = code;
-        }
-
-        @Override
-        public ResponseProperty loadToken(final String var) {
-            super.loadToken(var);
-            return this;
-        }
-
-        @Override
-        public ResponseProperty loadMID(final String var) {
-            super.loadMID(var);
-            return this;
-        }
-
-        @Override
-        public ResponseProperty mid(final int mid) {
-            super.mid(mid);
-            return this;
-        }
-
-        @Override
-        public ResponseProperty block1(final int num, final boolean m, final int size) {
-            super.block1(num, m, size);
-            return this;
-        }
-
-        @Override
-        public ResponseProperty block2(final int num, final boolean m, final int size) {
-            super.block2(num, m, size);
-            return this;
-        }
-
-        @Override
-        public ResponseProperty size1(final int size) {
-            super.size1(size);
-            return this;
-        }
-
-        @Override
-        public ResponseProperty size2(final int size) {
-            super.size2(size);
-            return this;
-        }
-
-        @Override
-        public ResponseProperty observe(final int observe) {
-            super.observe(observe);
-            return this;
-        }
-
-        @Override
-        public ResponseProperty etag(final byte[] tag) {
-            super.etag(tag);
-            return this;
-        }
-
-        public ResponseProperty payload(final String payload) {
-            properties.add(new Property<Response>() {
-
-                @Override
-                public void set(final Response response) {
-                    response.setPayload(payload);
-                }
-            });
-            return this;
-        }
-
-        @Override
-        public ResponseProperty payload(final String payload, final int from, final int to) {
-            super.payload(payload, from, to);
-            return this;
-        }
-
-        public ResponseProperty path(final String path) {
-            properties.add(new Property<Response>() {
-
-                @Override
-                public void set(final Response response) {
-                    response.getOptions().setUriPath(path);
-                }
-            });
-            return this;
-        }
-
-        public ResponseProperty loadBoth(final String var) {
-            properties.add(new Property<Response>() {
-
-                @Override
-                public void set(final Response response) {
-                    final Object[] pair = (Object[]) storage.get(var);
-                    if (pair == null) {
-                        throw new NullPointerException(
-                                "Did not find MID and token for variable " + var + ". Did you forgot a go()?");
-                    }
-                    response.setMID((Integer) pair[0]);
-                    response.setToken((byte[]) pair[1]);
-                }
-            });
-            return this;
-        }
-
-        public void setProperties(final Response response) {
-            super.setProperties(response);
-            for (final Property<Response> property : properties) {
-                property.set(response);
-            }
-        }
-
-        @Override
-        public void go() {
-            final Response response = new Response(code);
-            if (destination != null) {
-                response.setDestination(destination.getAddress());
-                response.setDestinationPort(destination.getPort());
-            }
-            setProperties(response);
-
-            final RawData raw = serializer.serializeResponse(response);
-            send(raw);
-        }
-    }
-
-    public static interface Action {
-
-        /**
-         * The method go() must be called when an action is ready. If you think there is a smarter way than such a
-         * method at the end of each action, first make sure the smarter way also works for sending messages before
-         * changing this.
-         */
-        public void go() throws Exception;
-    }
-
-    public static interface MidExpectation {
-
-        public boolean expectMID(Message message);
-    }
-
-}
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
index c7d73445..78d5b8ba 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
@@ -13,6 +13,8 @@
  * Contributors:
  *     Zebra Technologies - initial API and implementation
  *     Achim Kraus (Bosch Software Innovations GmbH) - replace close() with destroy()
+ *     Achim Kraus (Bosch Software Innovations GmbH) - use destination context
+ *                                                     instead of address for response
  *******************************************************************************/
 
 package org.eclipse.leshan.integration.tests;
@@ -34,6 +36,8 @@ import org.eclipse.californium.core.coap.Response;
 import org.eclipse.californium.core.network.CoapEndpoint;
 import org.eclipse.californium.core.network.serialization.UdpDataSerializer;
 import org.eclipse.californium.elements.Connector;
+import org.eclipse.californium.elements.AddressEndpointContext;
+import org.eclipse.californium.elements.EndpointContext;
 import org.eclipse.californium.elements.RawData;
 import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.client.californium.LeshanClient;
@@ -368,8 +372,8 @@ public class ObserveTest {
         OptionSet options = new OptionSet().setContentFormat(contentFormat)
                 .setObserve(firstCoapResponse.getOptions().getObserve() + 1);
         response.setOptions(options);
-        response.setDestination(helper.server.getUnsecuredAddress().getAddress());
-        response.setDestinationPort(helper.server.getUnsecuredAddress().getPort());
+        EndpointContext context = new AddressEndpointContext(helper.server.getUnsecuredAddress().getAddress(), helper.server.getUnsecuredAddress().getPort());
+        response.setDestinationContext(context);
 
         // serialize response
         UdpDataSerializer serializer = new UdpDataSerializer();
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 481ac246..a70203de 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
@@ -35,6 +35,7 @@ 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.elements.AddressEndpointContext;
 import org.eclipse.leshan.Link;
 import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.client.californium.LeshanClient;
@@ -294,8 +295,7 @@ public class RegistrationTest {
 
         // create a register request without the list of supported object
         Request coapRequest = new Request(Code.POST);
-        coapRequest.setDestination(helper.server.getUnsecuredAddress().getAddress());
-        coapRequest.setDestinationPort(helper.server.getUnsecuredAddress().getPort());
+        coapRequest.setDestinationContext(new AddressEndpointContext(helper.server.getUnsecuredAddress()));
         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 8b5d59a9..d6a75294 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
@@ -19,10 +19,23 @@ import static org.eclipse.leshan.integration.tests.IntegrationTestHelper.LIFETIM
 import static org.eclipse.leshan.integration.tests.SecureIntegrationTestHelper.*;
 import static org.junit.Assert.*;
 
+import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.Certificate;
 
+import org.eclipse.californium.elements.AddressEndpointContext;
+import org.eclipse.californium.elements.EndpointContext;
+import org.eclipse.californium.elements.EndpointMismatchException;
+import org.eclipse.californium.elements.RawData;
+import org.eclipse.californium.elements.util.SimpleMessageCallback;
+import org.eclipse.californium.scandium.DTLSConnector;
+import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
+import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder;
+import org.eclipse.californium.scandium.dtls.pskstore.StaticPskStore;
+import org.eclipse.leshan.core.request.ReadRequest;
+import org.eclipse.leshan.core.request.exception.SendFailedException;
 import org.eclipse.leshan.server.registration.Registration;
 import org.eclipse.leshan.server.security.EditableSecurityStore;
 import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
@@ -73,6 +86,66 @@ public class SecurityTest {
         helper.assertClientRegisterered();
     }
 
+    @Test
+    public void dont_sent_request_if_identity_change()
+            throws NonUniqueSecurityInfoException, InterruptedException, IOException {
+        // Create PSK server & start it
+        helper.createServer(); // default server support PSK
+        helper.server.start();
+
+        // Create PSK Client
+        helper.createPSKClient();
+
+        // Add client credentials to the server
+        helper.getSecurityStore()
+                .add(SecurityInfo.newPreSharedKeyInfo(helper.getCurrentEndpoint(), GOOD_PSK_ID, GOOD_PSK_KEY));
+
+        // Check client is not registered
+        helper.assertClientNotRegisterered();
+
+        // Start it and wait for registration
+        helper.client.start();
+        helper.waitForRegistration(1);
+
+        // Check client is well registered
+        helper.assertClientRegisterered();
+
+        // Ensure we can send a read request
+        helper.server.send(helper.getCurrentRegistration(), new ReadRequest(3, 0, 1));
+
+        // Get the previous address and stop client silently (no deregistration)
+        InetSocketAddress securedAddress = helper.client.getSecuredAddress();
+        helper.client.destroy(false);
+        helper.client = null;
+
+        // add new credential to the server
+        helper.getSecurityStore().add(SecurityInfo.newPreSharedKeyInfo(GOOD_ENDPOINT, "anotherPSK", GOOD_PSK_KEY));
+
+        // Create a new connector for the same address/port and start a new DTLS handshake
+        Builder builder = new DtlsConnectorConfig.Builder();
+        builder.setAddress(securedAddress);
+        builder.setPskStore(new StaticPskStore("anotherPSK", GOOD_PSK_KEY));
+        DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
+        dtlsConnector.start();
+        SimpleMessageCallback callback = new SimpleMessageCallback();
+        dtlsConnector.send(RawData.outbound(new byte[0], new AddressEndpointContext(helper.server.getSecuredAddress()),
+                callback, false));
+
+        // Wait until new handshake DTLS is done
+        EndpointContext endpointContext = callback.getEndpointContext(1000);
+        assertEquals(endpointContext.getPeerIdentity().getName(), "anotherPSK");
+
+        // Try to send a read request this should failed with an SendFailedException.
+        try {
+            helper.server.send(helper.getCurrentRegistration(), new ReadRequest(3, 0, 1));
+        } catch (SendFailedException e) {
+            assertTrue("must be caused by an EndpointMismatchException",
+                    e.getCause() instanceof EndpointMismatchException);
+        } finally {
+            dtlsConnector.destroy();
+        }
+    }
+
     @Test
     public void register_update_deregister_reregister_device_with_psk_to_server_with_psk()
             throws NonUniqueSecurityInfoException {
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CaliforniumTestSupport.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CaliforniumTestSupport.java
index 1e7a88ff..ce508666 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CaliforniumTestSupport.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CaliforniumTestSupport.java
@@ -15,6 +15,7 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.californium.impl;
 
+import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
@@ -22,6 +23,7 @@ import java.util.Random;
 import java.util.concurrent.ThreadLocalRandom;
 
 import org.eclipse.leshan.LwM2m;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.server.registration.Registration;
 
 public class CaliforniumTestSupport {
@@ -34,8 +36,8 @@ public class CaliforniumTestSupport {
     public void givenASimpleClient() throws UnknownHostException {
         registrationAddress = InetSocketAddress.createUnresolved("localhost", LwM2m.DEFAULT_COAP_PORT);
 
-        Registration.Builder builder = new Registration.Builder("ID", "urn:client", InetAddress.getLocalHost(), 10000,
-                registrationAddress);
+        Registration.Builder builder = new Registration.Builder("ID", "urn:client",
+                Identity.unsecure(Inet4Address.getLoopbackAddress(), 1000), registrationAddress);
 
         registration = builder.build();
     }
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilderTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilderTest.java
index 92fe362b..48f42f06 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilderTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilderTest.java
@@ -40,6 +40,7 @@ import org.eclipse.leshan.core.request.CreateRequest;
 import org.eclipse.leshan.core.request.DeleteRequest;
 import org.eclipse.leshan.core.request.DiscoverRequest;
 import org.eclipse.leshan.core.request.ExecuteRequest;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.request.ObserveRequest;
 import org.eclipse.leshan.core.request.ReadRequest;
 import org.eclipse.leshan.core.request.WriteAttributesRequest;
@@ -72,8 +73,8 @@ public class CoapRequestBuilderTest {
     }
 
     private Registration newRegistration(String rootpath) throws UnknownHostException {
-        Builder b = new Registration.Builder("regid", "endpoint", Inet4Address.getByName("127.0.0.1"), 12354,
-                new InetSocketAddress(0));
+        Builder b = new Registration.Builder("regid", "endpoint",
+                Identity.unsecure(Inet4Address.getLoopbackAddress(), 12354), new InetSocketAddress(0));
         if (rootpath != null) {
             Map<String, String> attr = new HashMap<>();
             attr.put("rt", "oma.lwm2m");
@@ -87,16 +88,16 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration();
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         ReadRequest request = new ReadRequest(3, 0);
         builder.visit(request);
 
         // verify
         Request coapRequest = builder.getRequest();
         assertEquals(CoAP.Code.GET, coapRequest.getCode());
-        assertEquals("127.0.0.1", coapRequest.getDestination().getHostAddress());
-        assertEquals(12354, coapRequest.getDestinationPort());
+        assertEquals("127.0.0.1", coapRequest.getDestinationContext().getPeerAddress().getAddress().getHostAddress());
+        assertEquals(12354, coapRequest.getDestinationContext().getPeerAddress().getPort());
         assertEquals("coap://127.0.0.1:12354/3/0", coapRequest.getURI());
     }
 
@@ -105,8 +106,8 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration("/lwm2m");
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         ReadRequest request = new ReadRequest(3, 0, 1);
         builder.visit(request);
 
@@ -120,8 +121,8 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration("/");
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         ReadRequest request = new ReadRequest(3);
         builder.visit(request);
 
@@ -135,16 +136,16 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration();
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         DiscoverRequest request = new DiscoverRequest(3, 0);
         builder.visit(request);
 
         // verify
         Request coapRequest = builder.getRequest();
         assertEquals(CoAP.Code.GET, coapRequest.getCode());
-        assertEquals("127.0.0.1", coapRequest.getDestination().getHostAddress());
-        assertEquals(12354, coapRequest.getDestinationPort());
+        assertEquals("127.0.0.1", coapRequest.getDestinationContext().getPeerAddress().getAddress().getHostAddress());
+        assertEquals(12354, coapRequest.getDestinationContext().getPeerAddress().getPort());
         assertEquals(MediaTypeRegistry.APPLICATION_LINK_FORMAT, coapRequest.getOptions().getAccept());
         assertEquals("coap://127.0.0.1:12354/3/0", coapRequest.getURI());
     }
@@ -154,16 +155,16 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration();
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         WriteRequest request = new WriteRequest(Mode.UPDATE, 3, 0, LwM2mSingleResource.newStringResource(4, "value"));
         builder.visit(request);
 
         // verify
         Request coapRequest = builder.getRequest();
         assertEquals(CoAP.Code.POST, coapRequest.getCode());
-        assertEquals("127.0.0.1", coapRequest.getDestination().getHostAddress());
-        assertEquals(12354, coapRequest.getDestinationPort());
+        assertEquals("127.0.0.1", coapRequest.getDestinationContext().getPeerAddress().getAddress().getHostAddress());
+        assertEquals(12354, coapRequest.getDestinationContext().getPeerAddress().getPort());
         assertEquals(ContentFormat.TLV.getCode(), coapRequest.getOptions().getContentFormat());
         assertNotNull(coapRequest.getPayload());
         // assert it is encoded as array of resources TLV
@@ -178,8 +179,8 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration();
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         WriteRequest request = new WriteRequest(3, 0, 14, "value");
         builder.visit(request);
 
@@ -193,8 +194,8 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration();
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         WriteAttributesRequest request = new WriteAttributesRequest(3, 0, 14,
                 new ObserveSpec.Builder().minPeriod(10).maxPeriod(100).build());
         builder.visit(request);
@@ -202,8 +203,8 @@ public class CoapRequestBuilderTest {
         // verify
         Request coapRequest = builder.getRequest();
         assertEquals(CoAP.Code.PUT, coapRequest.getCode());
-        assertEquals("127.0.0.1", coapRequest.getDestination().getHostAddress());
-        assertEquals(12354, coapRequest.getDestinationPort());
+        assertEquals("127.0.0.1", coapRequest.getDestinationContext().getPeerAddress().getAddress().getHostAddress());
+        assertEquals(12354, coapRequest.getDestinationContext().getPeerAddress().getPort());
         assertEquals("coap://127.0.0.1:12354/3/0/14?pmin=10&pmax=100", coapRequest.getURI());
     }
 
@@ -212,16 +213,16 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration();
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         ExecuteRequest request = new ExecuteRequest(3, 0, 12, "params");
         builder.visit(request);
 
         // verify
         Request coapRequest = builder.getRequest();
         assertEquals(CoAP.Code.POST, coapRequest.getCode());
-        assertEquals("127.0.0.1", coapRequest.getDestination().getHostAddress());
-        assertEquals(12354, coapRequest.getDestinationPort());
+        assertEquals("127.0.0.1", coapRequest.getDestinationContext().getPeerAddress().getAddress().getHostAddress());
+        assertEquals(12354, coapRequest.getDestinationContext().getPeerAddress().getPort());
         assertEquals("coap://127.0.0.1:12354/3/0/12", coapRequest.getURI());
         assertEquals("params", coapRequest.getPayloadString());
     }
@@ -231,16 +232,16 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration();
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         CreateRequest request = new CreateRequest(12, LwM2mSingleResource.newStringResource(0, "value"));
         builder.visit(request);
 
         // verify
         Request coapRequest = builder.getRequest();
         assertEquals(CoAP.Code.POST, coapRequest.getCode());
-        assertEquals("127.0.0.1", coapRequest.getDestination().getHostAddress());
-        assertEquals(12354, coapRequest.getDestinationPort());
+        assertEquals("127.0.0.1", coapRequest.getDestinationContext().getPeerAddress().getAddress().getHostAddress());
+        assertEquals(12354, coapRequest.getDestinationContext().getPeerAddress().getPort());
         assertEquals("coap://127.0.0.1:12354/12", coapRequest.getURI());
         assertEquals(ContentFormat.TLV.getCode(), coapRequest.getOptions().getContentFormat());
         assertNotNull(coapRequest.getPayload());
@@ -254,8 +255,8 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration();
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         CreateRequest request = new CreateRequest(12,
                 new LwM2mObjectInstance(26, LwM2mSingleResource.newStringResource(0, "value")));
         builder.visit(request);
@@ -263,8 +264,8 @@ public class CoapRequestBuilderTest {
         // verify
         Request coapRequest = builder.getRequest();
         assertEquals(CoAP.Code.POST, coapRequest.getCode());
-        assertEquals("127.0.0.1", coapRequest.getDestination().getHostAddress());
-        assertEquals(12354, coapRequest.getDestinationPort());
+        assertEquals("127.0.0.1", coapRequest.getDestinationContext().getPeerAddress().getAddress().getHostAddress());
+        assertEquals(12354, coapRequest.getDestinationContext().getPeerAddress().getPort());
         assertEquals("coap://127.0.0.1:12354/12", coapRequest.getURI());
         assertEquals(ContentFormat.TLV.getCode(), coapRequest.getOptions().getContentFormat());
         assertNotNull(coapRequest.getPayload());
@@ -279,16 +280,16 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration();
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         DeleteRequest request = new DeleteRequest(12, 0);
         builder.visit(request);
 
         // verify
         Request coapRequest = builder.getRequest();
         assertEquals(CoAP.Code.DELETE, coapRequest.getCode());
-        assertEquals("127.0.0.1", coapRequest.getDestination().getHostAddress());
-        assertEquals(12354, coapRequest.getDestinationPort());
+        assertEquals("127.0.0.1", coapRequest.getDestinationContext().getPeerAddress().getAddress().getHostAddress());
+        assertEquals(12354, coapRequest.getDestinationContext().getPeerAddress().getPort());
         assertEquals("coap://127.0.0.1:12354/12/0", coapRequest.getURI());
     }
 
@@ -297,8 +298,8 @@ public class CoapRequestBuilderTest {
         Registration reg = newRegistration();
 
         // test
-        CoapRequestBuilder builder = new CoapRequestBuilder(new InetSocketAddress(reg.getAddress(), reg.getPort()),
-                reg.getRootPath(), reg.getId(), reg.getEndpoint(), model, encoder);
+        CoapRequestBuilder builder = new CoapRequestBuilder(reg.getIdentity(), reg.getRootPath(), reg.getId(),
+                reg.getEndpoint(), model, encoder);
         ObserveRequest request = new ObserveRequest(12, 0);
         builder.visit(request);
 
@@ -306,8 +307,8 @@ public class CoapRequestBuilderTest {
         Request coapRequest = builder.getRequest();
         assertEquals(CoAP.Code.GET, coapRequest.getCode());
         assertEquals(0, coapRequest.getOptions().getObserve().intValue());
-        assertEquals("127.0.0.1", coapRequest.getDestination().getHostAddress());
-        assertEquals(12354, coapRequest.getDestinationPort());
+        assertEquals("127.0.0.1", coapRequest.getDestinationContext().getPeerAddress().getAddress().getHostAddress());
+        assertEquals(12354, coapRequest.getDestinationContext().getPeerAddress().getPort());
         assertEquals("coap://127.0.0.1:12354/12/0", coapRequest.getURI());
     }
 }
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStoreTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStoreTest.java
index 37f624e8..9f575ab7 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStoreTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStoreTest.java
@@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets;
 import org.eclipse.leshan.Link;
 import org.eclipse.leshan.LwM2m;
 import org.eclipse.leshan.core.request.BindingMode;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.server.registration.Registration;
 import org.eclipse.leshan.server.registration.RegistrationStore;
 import org.eclipse.leshan.server.registration.RegistrationUpdate;
@@ -54,7 +55,8 @@ public class InMemoryRegistrationStoreTest {
         givenASimpleRegistration(lifetime);
         store.addRegistration(registration);
 
-        RegistrationUpdate update = new RegistrationUpdate(registrationId, address, port, null, null, null, null);
+        RegistrationUpdate update = new RegistrationUpdate(registrationId, Identity.unsecure(address, port), null, null,
+                null, null);
         UpdatedRegistration updatedRegistration = store.updateRegistration(update);
         Assert.assertEquals(lifetime, updatedRegistration.getUpdatedRegistration().getLifeTimeInSec());
         Assert.assertSame(binding, updatedRegistration.getUpdatedRegistration().getBindingMode());
@@ -81,7 +83,8 @@ public class InMemoryRegistrationStoreTest {
         store.addRegistration(registration);
         Assert.assertFalse(registration.isAlive());
 
-        RegistrationUpdate update = new RegistrationUpdate(registrationId, address, port, lifetime, null, null, null);
+        RegistrationUpdate update = new RegistrationUpdate(registrationId, Identity.unsecure(address, port), lifetime,
+                null, null, null);
         UpdatedRegistration updatedRegistration = store.updateRegistration(update);
         Assert.assertTrue(updatedRegistration.getUpdatedRegistration().isAlive());
 
@@ -91,7 +94,7 @@ public class InMemoryRegistrationStoreTest {
 
     private void givenASimpleRegistration(Long lifetime) {
 
-        Registration.Builder builder = new Registration.Builder(registrationId, ep, address, port,
+        Registration.Builder builder = new Registration.Builder(registrationId, ep, Identity.unsecure(address, port),
                 InetSocketAddress.createUnresolved("localhost", LwM2m.DEFAULT_COAP_PORT));
 
         registration = builder.lifeTimeInSec(lifetime).smsNumber(sms).bindingMode(binding).objectLinks(objectLinks)
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/ObservationServiceTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/ObservationServiceTest.java
index 7d71ecea..e9937a53 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/ObservationServiceTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/ObservationServiceTest.java
@@ -23,9 +23,11 @@ import java.util.Set;
 
 import org.eclipse.californium.core.coap.Request;
 import org.eclipse.leshan.LwM2m;
+import org.eclipse.leshan.core.californium.EndpointContextUtil;
 import org.eclipse.leshan.core.node.LwM2mPath;
 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
 import org.eclipse.leshan.core.observation.Observation;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.request.ObserveRequest;
 import org.eclipse.leshan.server.californium.CaliforniumRegistrationStore;
 import org.eclipse.leshan.server.californium.ObserveUtil;
@@ -139,8 +141,7 @@ public class ObservationServiceTest {
         coapRequest.getOptions().addUriPath(String.valueOf(target.getObjectInstanceId()));
         coapRequest.getOptions().addUriPath(String.valueOf(target.getResourceId()));
         coapRequest.setObserve();
-        coapRequest.setDestination(support.registration.getAddress());
-        coapRequest.setDestinationPort(support.registration.getPort());
+        coapRequest.setDestinationContext(EndpointContextUtil.extractContext(support.registration.getIdentity()));
         Map<String, String> context = ObserveUtil.createCoapObserveRequestContext(registration.getEndpoint(),
                 registrationId, new ObserveRequest(target.toString()));
         coapRequest.setUserContext(context);
@@ -158,8 +159,8 @@ public class ObservationServiceTest {
                 LwM2m.DEFAULT_COAP_PORT);
         Registration.Builder builder;
         try {
-            builder = new Registration.Builder(registrationId, registrationId + "_ep", InetAddress.getLocalHost(),
-                    10000, registrationAddress);
+            builder = new Registration.Builder(registrationId, registrationId + "_ep",
+                    Identity.unsecure(InetAddress.getLocalHost(), 10000), registrationAddress);
             return builder.build();
         } catch (UnknownHostException e) {
             throw new RuntimeException(e);
diff --git a/leshan-server-cluster/src/test/java/org/eclipse/leshan/server/cluster/serialization/ClientSerDesTest.java b/leshan-server-cluster/src/test/java/org/eclipse/leshan/server/cluster/serialization/ClientSerDesTest.java
index 388d066b..ba339451 100644
--- a/leshan-server-cluster/src/test/java/org/eclipse/leshan/server/cluster/serialization/ClientSerDesTest.java
+++ b/leshan-server-cluster/src/test/java/org/eclipse/leshan/server/cluster/serialization/ClientSerDesTest.java
@@ -24,7 +24,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.eclipse.leshan.Link;
-import org.eclipse.leshan.server.cluster.serialization.RegistrationSerDes;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.server.registration.Registration;
 import org.junit.Test;
 
@@ -39,8 +39,8 @@ public class ClientSerDesTest {
         objs[0] = new Link("/0/1024/2", att);
         objs[1] = new Link("/0/2");
 
-        Registration.Builder builder = new Registration.Builder("registrationId", "endpoint", Inet4Address.getByName("127.0.0.1"),
-                1, new InetSocketAddress(212)).objectLinks(objs);
+        Registration.Builder builder = new Registration.Builder("registrationId", "endpoint",
+                Identity.unsecure(Inet4Address.getLoopbackAddress(), 1), new InetSocketAddress(212)).objectLinks(objs);
 
         builder.registrationDate(new Date(100L));
         builder.lastUpdate(new Date(101L));
diff --git a/leshan-server-cluster/src/test/java/org/eclipse/leshan/server/cluster/serialization/ClientUpdateSerDesTest.java b/leshan-server-cluster/src/test/java/org/eclipse/leshan/server/cluster/serialization/ClientUpdateSerDesTest.java
index abc951eb..b8087a26 100644
--- a/leshan-server-cluster/src/test/java/org/eclipse/leshan/server/cluster/serialization/ClientUpdateSerDesTest.java
+++ b/leshan-server-cluster/src/test/java/org/eclipse/leshan/server/cluster/serialization/ClientUpdateSerDesTest.java
@@ -24,6 +24,7 @@ import java.util.Map;
 import org.eclipse.leshan.Link;
 import org.eclipse.leshan.LwM2m;
 import org.eclipse.leshan.core.request.BindingMode;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.server.registration.RegistrationUpdate;
 import org.junit.Test;
 
@@ -38,8 +39,9 @@ public class ClientUpdateSerDesTest {
         objs[0] = new Link("/0/1024/2", att);
         objs[1] = new Link("/0/2");
 
-        RegistrationUpdate ru = new RegistrationUpdate("myId", Inet4Address.getByName("127.0.0.1"),
-                LwM2m.DEFAULT_COAP_PORT, 60000l, null, BindingMode.U, objs);
+        RegistrationUpdate ru = new RegistrationUpdate("myId",
+                Identity.unsecure(Inet4Address.getByName("127.0.0.1"), LwM2m.DEFAULT_COAP_PORT), 60000l, null,
+                BindingMode.U, objs);
 
         byte[] ser = RegistrationUpdateSerDes.bSerialize(ru);
         RegistrationUpdate ru2 = RegistrationUpdateSerDes.deserialize(ser);
diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
index 8fcc64ea..9ec7fcd6 100644
--- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
+++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
@@ -108,14 +108,14 @@ public class BootstrapHandlerTest {
         }
 
         @Override
-        public <T extends LwM2mResponse> T send(String clientEndpoint, InetSocketAddress client, boolean secure,
-                DownlinkRequest<T> request, long timeout) throws InterruptedException {
+        public <T extends LwM2mResponse> T send(String clientEndpoint, Identity destination, DownlinkRequest<T> request,
+                long timeout) throws InterruptedException {
             return null;
         }
 
         @SuppressWarnings("unchecked")
         @Override
-        public <T extends LwM2mResponse> void send(String clientEndpoint, InetSocketAddress client, boolean secure,
+        public <T extends LwM2mResponse> void send(String clientEndpoint, Identity destination,
                 DownlinkRequest<T> request, long timeout, ResponseCallback<T> responseCallback,
                 ErrorCallback errorCallback) {
             if (request instanceof BootstrapDeleteRequest) {
diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationSortObjectLinksTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationSortObjectLinksTest.java
index a3288ba2..12321b2b 100644
--- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationSortObjectLinksTest.java
+++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationSortObjectLinksTest.java
@@ -21,6 +21,7 @@ import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 
 import org.eclipse.leshan.Link;
+import org.eclipse.leshan.core.request.Identity;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -33,8 +34,8 @@ public class RegistrationSortObjectLinksTest {
         objs[1] = new Link("/0/2");
         objs[2] = null;
 
-        Registration.Builder builder = new Registration.Builder("registrationId", "endpoint", Inet4Address.getByName("127.0.0.1"),
-                1, new InetSocketAddress(212)).objectLinks(objs);
+        Registration.Builder builder = new Registration.Builder("registrationId", "endpoint",
+                Identity.unsecure(Inet4Address.getLocalHost(), 1), new InetSocketAddress(212)).objectLinks(objs);
 
         Registration r = builder.build();