Repository: eclipse/leshan
Pull request #812 (2020-02-18 17:14:29)

Add UnconnectedPeerException and consider HanshakeTimeout as "regular" timeout
About considering "consider HanshakeTimeout as regular timeout" (see https://github.com/eclipse/californium/issues/1223)


Patch:
diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapAsyncRequestObserver.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapAsyncRequestObserver.java
index c233e996..5fb86879 100644
--- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapAsyncRequestObserver.java
+++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapAsyncRequestObserver.java
@@ -25,9 +25,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.eclipse.californium.core.coap.Request;
 import org.eclipse.californium.core.coap.Response;
+import org.eclipse.californium.elements.exception.EndpointUnconnectedException;
+import org.eclipse.californium.scandium.dtls.DtlsHandshakeTimeoutException;
 import org.eclipse.leshan.core.request.exception.RequestCanceledException;
 import org.eclipse.leshan.core.request.exception.RequestRejectedException;
 import org.eclipse.leshan.core.request.exception.SendFailedException;
+import org.eclipse.leshan.core.request.exception.TimeoutException;
+import org.eclipse.leshan.core.request.exception.TimeoutException.Type;
+import org.eclipse.leshan.core.request.exception.UnconnectedPeerException;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -111,8 +116,8 @@ public class CoapAsyncRequestObserver extends AbstractRequestObserver {
     public void onTimeout() {
         cancelCleaningTask();
         if (eventRaised.compareAndSet(false, true)) {
-            errorCallback.onError(new org.eclipse.leshan.core.request.exception.TimeoutException("Request %s timed out",
-                    coapRequest.getURI()));
+            errorCallback.onError(new TimeoutException(Type.COAP_TIMEOUT,
+                    "Request %s timed out : CoAP or blockwise timeout", coapRequest.getURI()));
         } else {
             LOG.debug("OnTimeout callback ignored because an event was already raised for this request {}",
                     coapRequest);
@@ -124,8 +129,8 @@ public class CoapAsyncRequestObserver extends AbstractRequestObserver {
         cancelCleaningTask();
         if (eventRaised.compareAndSet(false, true)) {
             if (responseTimedOut.get()) {
-                errorCallback.onError(new org.eclipse.leshan.core.request.exception.TimeoutException(
-                        "Request %s timed out", coapRequest.getURI()));
+                errorCallback.onError(new TimeoutException(Type.RESPONSE_TIMEOUT,
+                        "Request %s timed out : no response received", coapRequest.getURI()));
             } else {
                 errorCallback.onError(new RequestCanceledException("Request %s cancelled", coapRequest.getURI()));
             }
@@ -150,7 +155,17 @@ public class CoapAsyncRequestObserver extends AbstractRequestObserver {
     public void onSendError(Throwable error) {
         cancelCleaningTask();
         if (eventRaised.compareAndSet(false, true)) {
-            errorCallback.onError(new SendFailedException(error, "Unable to send request %s", coapRequest.getURI()));
+            if (error instanceof DtlsHandshakeTimeoutException) {
+                errorCallback.onError(new TimeoutException(Type.DTLS_HANDSHAKE_TIMEOUT, error,
+                        "Request %s timeout : dtls handshake timeout", coapRequest.getURI()));
+            } else if (error instanceof EndpointUnconnectedException) {
+                errorCallback.onError(new UnconnectedPeerException(error,
+                        "Unable to send request %s : peer is not connected (no DTLS connection)",
+                        coapRequest.getURI()));
+            } else {
+                errorCallback
+                        .onError(new SendFailedException(error, "Unable to send request %s", coapRequest.getURI()));
+            }
         } else {
             LOG.debug("onSendError callback ignored because an event was already raised for this request {}",
                     coapRequest);
diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapSyncRequestObserver.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapSyncRequestObserver.java
index 8fcbeb69..355d2e88 100644
--- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapSyncRequestObserver.java
+++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/CoapSyncRequestObserver.java
@@ -24,9 +24,12 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import org.eclipse.californium.core.coap.Request;
 import org.eclipse.californium.core.coap.Response;
+import org.eclipse.californium.scandium.dtls.DtlsHandshakeTimeoutException;
+import org.eclipse.californium.elements.exception.EndpointUnconnectedException;
 import org.eclipse.leshan.core.request.exception.RequestCanceledException;
 import org.eclipse.leshan.core.request.exception.RequestRejectedException;
 import org.eclipse.leshan.core.request.exception.SendFailedException;
+import org.eclipse.leshan.core.request.exception.UnconnectedPeerException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -94,7 +97,14 @@ public class CoapSyncRequestObserver extends AbstractRequestObserver {
 
     @Override
     public void onSendError(Throwable error) {
-        exception.set(new SendFailedException(error, "Request %s cannot be sent", coapRequest, error.getMessage()));
+        if (error instanceof DtlsHandshakeTimeoutException) {
+            coapTimeout.set(true);
+        } else if (error instanceof EndpointUnconnectedException) {
+            exception.set(new UnconnectedPeerException(error,
+                    "Unable to send request %s : peer is not connected (no DTLS connection)", coapRequest.getURI()));
+        } else {
+            exception.set(new SendFailedException(error, "Request %s cannot be sent", coapRequest, error.getMessage()));
+        }
         latch.countDown();
     }
 
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/exception/TimeoutException.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/exception/TimeoutException.java
index 1e4a6251..0ba74d6f 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/exception/TimeoutException.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/exception/TimeoutException.java
@@ -17,9 +17,35 @@ package org.eclipse.leshan.core.request.exception;
 
 public class TimeoutException extends Exception {
 
+    public enum Type {
+        RESPONSE_TIMEOUT, COAP_TIMEOUT, DTLS_HANDSHAKE_TIMEOUT
+    }
+
     private static final long serialVersionUID = -8966041387554358975L;
 
-    public TimeoutException(String message, Object... args) {
+    private Type type;
+
+    public TimeoutException(Type type, String message, Object... args) {
         super(String.format(message, args));
+        this.type = type;
+    }
+
+    public TimeoutException(Type type, Throwable cause, String message, Object... args) {
+        super(String.format(message, args), cause);
+        this.type = type;
+    }
+
+    /**
+     * Get the kind of timeout.
+     * <p>
+     * See https://github.com/eclipse/leshan/wiki/Request-Timeout for more details.
+     * <p>
+     * Current implementation is not able to make differences between CoAP and Blockwise timeout, all is regroup over
+     * <code>COAP_TIMEOUT</code>.
+     * 
+     * @return the kind of timeout
+     */
+    public Type getType() {
+        return type;
     }
 }
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/exception/UnconnectedPeerException.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/exception/UnconnectedPeerException.java
new file mode 100644
index 00000000..522ee615
--- /dev/null
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/exception/UnconnectedPeerException.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 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;
+
+public class UnconnectedPeerException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public UnconnectedPeerException(String m) {
+        super(m);
+    }
+
+    public UnconnectedPeerException(String m, Object... args) {
+        super(String.format(m, args));
+    }
+
+    public UnconnectedPeerException(Throwable e) {
+        super(e);
+    }
+
+    public UnconnectedPeerException(String m, Throwable e) {
+        super(m, e);
+    }
+
+    public UnconnectedPeerException(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/request/CaliforniumLwM2mRequestSender.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumLwM2mRequestSender.java
index 116649a8..20ea86c0 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumLwM2mRequestSender.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CaliforniumLwM2mRequestSender.java
@@ -30,6 +30,7 @@ import org.eclipse.leshan.core.request.exception.InvalidResponseException;
 import org.eclipse.leshan.core.request.exception.RequestCanceledException;
 import org.eclipse.leshan.core.request.exception.RequestRejectedException;
 import org.eclipse.leshan.core.request.exception.SendFailedException;
+import org.eclipse.leshan.core.request.exception.UnconnectedPeerException;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.response.ObserveResponse;
@@ -88,6 +89,7 @@ public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender, CoapRe
      * @throws RequestCanceledException if the request is cancelled.
      * @throws SendFailedException if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.
      * @throws InvalidResponseException if the response received is malformed.
+     * @throws UnconnectedPeerException if client is not connected (no dtls connection available).
      */
     @Override
     public <T extends LwM2mResponse> T send(Registration destination, DownlinkRequest<T> request, long timeout)
@@ -126,6 +128,7 @@ public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender, CoapRe
      *        <li>{@link RequestCanceledException} if the request is cancelled.</li>
      *        <li>{@link SendFailedException} if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.</li>
      *        <li>{@link InvalidResponseException} if the response received is malformed.</li>
+     *        <li>{@link UnconnectedPeerException} if client is not connected (no dtls connection available).</li>
      *        <li>{@link TimeoutException} if the timeout expires (see
      *        https://github.com/eclipse/leshan/wiki/Request-Timeout).</li>
      *        <li>or any other RuntimeException for unexpected issue.
@@ -171,6 +174,7 @@ public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender, CoapRe
      * @throws RequestRejectedException if the request is rejected by foreign peer.
      * @throws RequestCanceledException if the request is cancelled.
      * @throws SendFailedException if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.
+     * @throws UnconnectedPeerException if client is not connected (no dtls connection available).
      */
     @Override
     public Response sendCoapRequest(Registration destination, Request coapRequest, long timeoutInMs)
@@ -197,6 +201,7 @@ public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender, CoapRe
      *        <li>{@link RequestRejectedException} if the request is rejected by foreign peer.</li>
      *        <li>{@link RequestCanceledException} if the request is cancelled.</li>
      *        <li>{@link SendFailedException} if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.</li>
+     *        <li>{@link UnconnectedPeerException} if client is not connected (no dtls connection available).</li>
      *        <li>{@link TimeoutException} if the timeout expires (see
      *        https://github.com/eclipse/leshan/wiki/Request-Timeout).</li>
      *        <li>or any other RuntimeException for unexpected issue.
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestSender.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestSender.java
index 07a71628..94264392 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestSender.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/CoapRequestSender.java
@@ -19,8 +19,10 @@ import org.eclipse.californium.core.coap.Request;
 import org.eclipse.californium.core.coap.Response;
 import org.eclipse.leshan.core.californium.CoapResponseCallback;
 import org.eclipse.leshan.core.request.exception.ClientSleepingException;
+import org.eclipse.leshan.core.request.exception.RequestCanceledException;
 import org.eclipse.leshan.core.request.exception.RequestRejectedException;
 import org.eclipse.leshan.core.request.exception.SendFailedException;
+import org.eclipse.leshan.core.request.exception.UnconnectedPeerException;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.eclipse.leshan.core.response.ResponseCallback;
 import org.eclipse.leshan.server.registration.Registration;
@@ -48,6 +50,7 @@ public interface CoapRequestSender {
      * @throws RequestRejectedException if the request is rejected by foreign peer.
      * @throws RequestCanceledException if the request is cancelled.
      * @throws SendFailedException if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.
+     * @throws UnconnectedPeerException if client is not connected (no dtls connection available).
      * @throws ClientSleepingException if client is currently sleeping.
      */
     Response sendCoapRequest(final Registration destination, final Request coapRequest, long timeoutInMs)
@@ -70,6 +73,7 @@ public interface CoapRequestSender {
      *        <li>{@link RequestCanceledException} if the request is cancelled.</li>
      *        <li>{@link SendFailedException} if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.</li>
      *        <li>{@link ClientSleepingException} if client is currently sleeping.</li>
+     *        <li>{@link UnconnectedPeerException} if client is not connected (no dtls connection available).</li>
      *        <li>{@link TimeoutException} if the timeout expires (see
      *        https://github.com/eclipse/leshan/wiki/Request-Timeout).</li>
      *        <li>or any other RuntimeException for unexpected issue.
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java
index d9fdc6d6..71035116 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java
@@ -46,6 +46,7 @@ import org.eclipse.leshan.core.request.exception.InvalidResponseException;
 import org.eclipse.leshan.core.request.exception.RequestCanceledException;
 import org.eclipse.leshan.core.request.exception.RequestRejectedException;
 import org.eclipse.leshan.core.request.exception.SendFailedException;
+import org.eclipse.leshan.core.request.exception.UnconnectedPeerException;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.response.ResponseCallback;
@@ -116,6 +117,7 @@ public class RequestSender implements Destroyable {
      * @throws RequestRejectedException if the request is rejected by foreign peer.
      * @throws RequestCanceledException if the request is cancelled.
      * @throws SendFailedException if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.
+     * @throws UnconnectedPeerException if client is not connected (no dtls connection available).
      * @throws InvalidResponseException if the response received is malformed.
      */
     public <T extends LwM2mResponse> T sendLwm2mRequest(final String endpointName, Identity destination,
@@ -179,6 +181,7 @@ public class RequestSender implements Destroyable {
      *        <li>{@link RequestCanceledException} if the request is cancelled.</li>
      *        <li>{@link SendFailedException} if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.</li>
      *        <li>{@link InvalidResponseException} if the response received is malformed.</li>
+     *        <li>{@link UnconnectedPeerException} if client is not connected (no dtls connection available).</li>
      *        <li>{@link TimeoutException} if the timeout expires (see
      *        https://github.com/eclipse/leshan/wiki/Request-Timeout).</li>
      *        <li>or any other RuntimeException for unexpected issue.
@@ -246,6 +249,7 @@ public class RequestSender implements Destroyable {
      * @throws RequestRejectedException if the request is rejected by foreign peer.
      * @throws RequestCanceledException if the request is cancelled.
      * @throws SendFailedException if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.
+     * @throws UnconnectedPeerException if client is not connected (no dtls connection available).
      */
     public Response sendCoapRequest(Identity destination, String sessionId, Request coapRequest, long timeoutInMs,
             boolean allowConnectionInitiation) throws InterruptedException {
@@ -291,6 +295,7 @@ public class RequestSender implements Destroyable {
      *        <li>{@link RequestRejectedException} if the request is rejected by foreign peer.</li>
      *        <li>{@link RequestCanceledException} if the request is cancelled.</li>
      *        <li>{@link SendFailedException} if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.</li>
+     *        <li>{@link UnconnectedPeerException} if client is not connected (no dtls connection available).</li>
      *        <li>{@link TimeoutException} if the timeout expires (see
      *        https://github.com/eclipse/leshan/wiki/Request-Timeout).</li>
      *        <li>or any other RuntimeException for unexpected issue.
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/QueueModeLwM2mRequestSender.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/QueueModeLwM2mRequestSender.java
index 233c1257..4cda919f 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/QueueModeLwM2mRequestSender.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/queue/QueueModeLwM2mRequestSender.java
@@ -18,6 +18,7 @@ package org.eclipse.leshan.server.queue;
 import org.eclipse.leshan.core.request.DownlinkRequest;
 import org.eclipse.leshan.core.request.exception.ClientSleepingException;
 import org.eclipse.leshan.core.request.exception.TimeoutException;
+import org.eclipse.leshan.core.request.exception.UnconnectedPeerException;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.response.ResponseCallback;
@@ -66,16 +67,23 @@ public class QueueModeLwM2mRequestSender implements LwM2mRequestSender {
         }
 
         // Use delegation to send the request
-        T response = delegatedSender.send(destination, request, timeout);
-        if (response != null) {
-            // Set the client awake. This will restart the timer.
-            presenceService.setAwake(destination);
-        } else {
-            // If the timeout expires, this means the client does not respond.
+        try {
+            T response = delegatedSender.send(destination, request, timeout);
+            if (response != null) {
+                // Set the client awake. This will restart the timer.
+                presenceService.setAwake(destination);
+            } else {
+                // If the timeout expires, this means the client does not respond.
+                presenceService.setSleeping(destination);
+            }
+
+            // Wait for response, then return it
+            return response;
+        } catch (UnconnectedPeerException e) {
+            // if peer is not connected (No DTLS connection available)
             presenceService.setSleeping(destination);
+            throw e;
         }
-        // Wait for response, then return it
-        return response;
     }
 
     /**
@@ -114,6 +122,9 @@ public class QueueModeLwM2mRequestSender implements LwM2mRequestSender {
                 if (e instanceof TimeoutException) {
                     // If the timeout expires, this means the client does not respond.
                     presenceService.setSleeping(destination);
+                } else if (e instanceof UnconnectedPeerException) {
+                    // if peer is not connected (No DTLS connection available)
+                    presenceService.setSleeping(destination);
                 }
 
                 // Call the user's callback
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/LwM2mRequestSender.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/LwM2mRequestSender.java
index 512e7e82..4f705fca 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/LwM2mRequestSender.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/request/LwM2mRequestSender.java
@@ -20,8 +20,10 @@ import org.eclipse.leshan.core.node.codec.CodecException;
 import org.eclipse.leshan.core.request.DownlinkRequest;
 import org.eclipse.leshan.core.request.exception.ClientSleepingException;
 import org.eclipse.leshan.core.request.exception.InvalidResponseException;
+import org.eclipse.leshan.core.request.exception.RequestCanceledException;
 import org.eclipse.leshan.core.request.exception.RequestRejectedException;
 import org.eclipse.leshan.core.request.exception.SendFailedException;
+import org.eclipse.leshan.core.request.exception.UnconnectedPeerException;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.response.ResponseCallback;
@@ -51,6 +53,7 @@ public interface LwM2mRequestSender {
      * @throws RequestCanceledException if the request is cancelled.
      * @throws SendFailedException if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.
      * @throws InvalidResponseException if the response received is malformed.
+     * @throws UnconnectedPeerException if client is not connected (no dtls connection available).
      * @throws ClientSleepingException if client is currently sleeping.
      */
     <T extends LwM2mResponse> T send(Registration destination, DownlinkRequest<T> request, long timeoutInMs)
@@ -73,6 +76,7 @@ public interface LwM2mRequestSender {
      *        <li>{@link RequestCanceledException} if the request is cancelled.</li>
      *        <li>{@link SendFailedException} if the request can not be sent. E.g. error at CoAP or DTLS/UDP layer.</li>
      *        <li>{@link InvalidResponseException} if the response received is malformed.</li>
+     *        <li>{@link UnconnectedPeerException} if client is not connected (no dtls connection available).</li>
      *        <li>{@link ClientSleepingException} if client is currently sleeping.</li>
      *        <li>{@link TimeoutException} if the timeout expires (see
      *        https://github.com/eclipse/leshan/wiki/Request-Timeout).</li>


Test patch:
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/FailingTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/FailingTest.java
index 5496aa66..39594a7b 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/FailingTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/FailingTest.java
@@ -15,6 +15,8 @@
  *******************************************************************************/
 package org.eclipse.leshan.integration.tests;
 
+import static org.junit.Assert.assertEquals;
+
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -132,7 +134,7 @@ public class FailingTest {
         // Request should timedout in ~1s we don't send ACK
         callback.waitForResponse(1500);
         Assert.assertTrue("we should timeout", callback.getException() instanceof TimeoutException);
-
+        assertEquals(TimeoutException.Type.COAP_TIMEOUT, ((TimeoutException) callback.getException()).getType());
     }
 
     @Test
@@ -157,5 +159,6 @@ public class FailingTest {
         Assert.assertTrue("we should still wait for response", callback.getException() == null);
         callback.waitForResponse(2000);
         Assert.assertTrue("we should timeout", callback.getException() instanceof TimeoutException);
+        assertEquals(TimeoutException.Type.RESPONSE_TIMEOUT, ((TimeoutException) callback.getException()).getType());
     }
 }
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java
index 577219e0..6a9c84d6 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java
@@ -317,6 +317,10 @@ public class SecureIntegrationTestHelper extends IntegrationTestHelper {
         LeshanServerBuilder builder = super.createServerBuilder();
         securityStore = new InMemorySecurityStore();
         builder.setSecurityStore(securityStore);
+        Builder dtlsConfig = new DtlsConnectorConfig.Builder();
+        dtlsConfig.setMaxRetransmissions(1);
+        dtlsConfig.setRetransmissionTimeout(300);
+        builder.setDtlsConfig(dtlsConfig);
         return builder;
     }
 
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 3ad2aeae..ed8e47d9 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
@@ -35,12 +35,13 @@ import org.eclipse.californium.elements.EndpointContext;
 import org.eclipse.californium.elements.RawData;
 import org.eclipse.californium.elements.auth.PreSharedKeyIdentity;
 import org.eclipse.californium.elements.exception.EndpointMismatchException;
-import org.eclipse.californium.elements.exception.EndpointUnconnectedException;
 import org.eclipse.californium.elements.util.SimpleMessageCallback;
 import org.eclipse.californium.scandium.DTLSConnector;
 import org.eclipse.californium.scandium.dtls.DTLSSession;
 import org.eclipse.leshan.core.request.ReadRequest;
 import org.eclipse.leshan.core.request.exception.SendFailedException;
+import org.eclipse.leshan.core.request.exception.TimeoutException;
+import org.eclipse.leshan.core.request.exception.UnconnectedPeerException;
 import org.eclipse.leshan.core.response.ReadResponse;
 import org.eclipse.leshan.server.registration.Registration;
 import org.eclipse.leshan.server.security.EditableSecurityStore;
@@ -268,6 +269,46 @@ public class SecurityTest {
         assertNotNull(session);
     }
 
+    @Test
+    public void server_initiates_dtls_handshake_timeout() throws NonUniqueSecurityInfoException, InterruptedException {
+        // 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 for registration
+        helper.assertClientNotRegisterered();
+        helper.client.start();
+        helper.waitForRegistrationAtServerSide(1);
+        Registration registration = helper.getCurrentRegistration();
+        helper.assertClientRegisterered();
+
+        // Remove DTLS connection at server side.
+        ((DTLSConnector) helper.server.coap().getSecuredEndpoint().getConnector()).clearConnectionState();
+
+        // stop client
+        helper.client.stop(false);
+
+        // try to send request synchronously
+        ReadResponse readResponse = helper.server.send(registration, new ReadRequest(3), 1000);
+        assertNull(readResponse);
+
+        // try to send request asynchronously
+        Callback<ReadResponse> callback = new Callback<>();
+        helper.server.send(registration, new ReadRequest(3), 1000, callback, callback);
+        callback.waitForResponse(1100);
+        assertTrue(callback.getException() instanceof TimeoutException);
+        assertEquals(TimeoutException.Type.DTLS_HANDSHAKE_TIMEOUT,
+                ((TimeoutException) callback.getException()).getType());
+
+    }
+
     @Test
     public void server_does_not_initiate_dtls_handshake_with_queue_mode()
             throws NonUniqueSecurityInfoException, InterruptedException {
@@ -296,8 +337,9 @@ public class SecurityTest {
         try {
             helper.server.send(registration, new ReadRequest(3), 1000);
             fail("Read request SHOULD have failed");
-        } catch (SendFailedException e) {
-            assertTrue(e.getCause() instanceof EndpointUnconnectedException);
+        } catch (UnconnectedPeerException e) {
+            // expected result
+            assertFalse("client is still awake", helper.server.getPresenceService().isClientAwake(registration));
         }
     }