Skip to content

Commit 9a9269d

Browse files
committed
Added MqttSslAdapterHandler to handle exceptionCaught
1 parent cb9f9bd commit 9a9269d

File tree

2 files changed

+117
-14
lines changed

2 files changed

+117
-14
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2018-present HiveMQ and the HiveMQ Community
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.hivemq.client.internal.mqtt.handler.ssl;
18+
19+
import io.netty.channel.Channel;
20+
import io.netty.channel.ChannelHandlerContext;
21+
import io.netty.channel.ChannelInboundHandlerAdapter;
22+
import io.netty.handler.ssl.SslHandler;
23+
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
24+
import org.jetbrains.annotations.NotNull;
25+
import org.jetbrains.annotations.Nullable;
26+
27+
import javax.net.ssl.HostnameVerifier;
28+
import javax.net.ssl.SSLHandshakeException;
29+
import java.util.function.BiConsumer;
30+
import java.util.function.Consumer;
31+
32+
/**
33+
* @author Silvio Giebl
34+
*/
35+
class MqttSslAdapterHandler extends ChannelInboundHandlerAdapter {
36+
37+
public static final @NotNull String NAME = "ssl.adapter";
38+
39+
private final @NotNull SslHandler sslHandler;
40+
private final @NotNull String host;
41+
private final @Nullable HostnameVerifier hostnameVerifier;
42+
private final @NotNull Consumer<Channel> onSuccess;
43+
private final @NotNull BiConsumer<Channel, Throwable> onError;
44+
private boolean handshakeDone = false;
45+
46+
public MqttSslAdapterHandler(
47+
final @NotNull SslHandler sslHandler,
48+
final @NotNull String host,
49+
final @Nullable HostnameVerifier hostnameVerifier,
50+
final @NotNull Consumer<Channel> onSuccess,
51+
final @NotNull BiConsumer<Channel, Throwable> onError) {
52+
53+
this.sslHandler = sslHandler;
54+
this.host = host;
55+
this.hostnameVerifier = hostnameVerifier;
56+
this.onSuccess = onSuccess;
57+
this.onError = onError;
58+
}
59+
60+
@Override
61+
public void userEventTriggered(final @NotNull ChannelHandlerContext ctx, final @NotNull Object evt) {
62+
if (evt instanceof SslHandshakeCompletionEvent) {
63+
handshakeComplete(ctx, (SslHandshakeCompletionEvent) evt);
64+
} else {
65+
ctx.fireUserEventTriggered(evt);
66+
}
67+
}
68+
69+
private void handshakeComplete(
70+
final @NotNull ChannelHandlerContext ctx, final @NotNull SslHandshakeCompletionEvent evt) {
71+
72+
if (setHandshakeDone()) {
73+
if (evt.isSuccess()) {
74+
ctx.pipeline().remove(this);
75+
if ((hostnameVerifier == null) || hostnameVerifier.verify(host, sslHandler.engine().getSession())) {
76+
onSuccess.accept(ctx.channel());
77+
} else {
78+
onError.accept(ctx.channel(), new SSLHandshakeException("Hostname verification failed"));
79+
}
80+
} else {
81+
// this handler is not removed here as the exception might also be fired so exceptionCaught is called
82+
// otherwise "An exceptionCaught() event was fired, and it reached at the tail of the pipeline" would be
83+
// logged
84+
onError.accept(ctx.channel(), evt.cause());
85+
}
86+
}
87+
}
88+
89+
@Override
90+
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx, final @NotNull Throwable cause) {
91+
// to ensure that additional exceptions are not swallowed, the handler is removed on the first exception
92+
ctx.pipeline().remove(this);
93+
if (setHandshakeDone()) {
94+
onError.accept(ctx.channel(), cause);
95+
}
96+
// the exception is not fired in the else branch to avoid that
97+
// "An exceptionCaught() event was fired, and it reached at the tail of the pipeline" is logged although it was
98+
// already handled via the SslHandshakeCompletionEvent
99+
}
100+
101+
private boolean setHandshakeDone() {
102+
if (!handshakeDone) {
103+
handshakeDone = true;
104+
return true;
105+
}
106+
return false;
107+
}
108+
109+
@Override
110+
public boolean isSharable() {
111+
return false;
112+
}
113+
}

src/main/java/com/hivemq/client/internal/mqtt/handler/ssl/MqttSslInitializer.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
import javax.net.ssl.HostnameVerifier;
3030
import javax.net.ssl.SSLException;
31-
import javax.net.ssl.SSLHandshakeException;
3231
import javax.net.ssl.SSLParameters;
3332
import java.net.InetSocketAddress;
3433
import java.util.function.BiConsumer;
@@ -73,20 +72,11 @@ public static void initChannel(
7372
sslHandler.engine().setSSLParameters(sslParameters);
7473
}
7574

76-
sslHandler.handshakeFuture().addListener(future -> {
77-
if (future.isSuccess()) {
78-
if ((hostnameVerifier != null) &&
79-
!hostnameVerifier.verify(serverAddress.getHostString(), sslHandler.engine().getSession())) {
80-
onError.accept(channel, new SSLHandshakeException("Hostname verification failed"));
81-
} else {
82-
onSuccess.accept(channel);
83-
}
84-
} else {
85-
onError.accept(channel, future.cause());
86-
}
87-
});
75+
final MqttSslAdapterHandler sslAdapterHandler =
76+
new MqttSslAdapterHandler(sslHandler, serverAddress.getHostString(), hostnameVerifier, onSuccess,
77+
onError);
8878

89-
channel.pipeline().addLast(SSL_HANDLER_NAME, sslHandler);
79+
channel.pipeline().addLast(SSL_HANDLER_NAME, sslHandler).addLast(MqttSslAdapterHandler.NAME, sslAdapterHandler);
9080
}
9181

9282
static @NotNull SslContext createSslContext(final @NotNull MqttClientSslConfigImpl sslConfig) throws SSLException {

0 commit comments

Comments
 (0)