protected SSLEngine createSSLEngine(String sniHostName, List<Cipher> clientRequestedCiphers) {
    SSLHostConfig sslHostConfig = getSSLHostConfig(sniHostName);

    SSLHostConfigCertificate certificate = selectCertificate(sslHostConfig, clientRequestedCiphers);

    SSLContextWrapper sslContextWrapper = certificate.getSslContextWrapper();
    if (sslContextWrapper == null) {
      throw new IllegalStateException(sm.getString("endpoint.jsse.noSslContext", sniHostName));
    }

    SSLEngine engine = sslContextWrapper.getSSLContext().createSSLEngine();
    switch (sslHostConfig.getCertificateVerification()) {
      case NONE:
        engine.setNeedClientAuth(false);
        engine.setWantClientAuth(false);
        break;
      case OPTIONAL:
      case OPTIONAL_NO_CA:
        engine.setWantClientAuth(true);
        break;
      case REQUIRED:
        engine.setNeedClientAuth(true);
        break;
    }
    engine.setUseClientMode(false);
    engine.setEnabledCipherSuites(sslContextWrapper.getEnabledCiphers());
    engine.setEnabledProtocols(sslContextWrapper.getEnabledProtocols());

    SSLParameters sslParameters = engine.getSSLParameters();
    sslParameters.setUseCipherSuitesOrder(sslHostConfig.getHonorCipherOrder());
    // In case the getter returns a defensive copy
    engine.setSSLParameters(sslParameters);

    return engine;
  }
  /**
   * Returns server ssl engine.
   *
   * @param context - SSLContext to get SSLEngine from.
   * @param useSNI - flag used to enable or disable using SNI extension. Needed for Kerberos.
   */
  public static SSLEngine getServerSSLEngine(SSLContext context, boolean useSNI) {

    SSLEngine serverEngine = context.createSSLEngine();
    serverEngine.setUseClientMode(false);
    if (useSNI) {
      SNIMatcher matcher = SNIHostName.createSNIMatcher(SNI_PATTERN);
      List<SNIMatcher> matchers = new ArrayList<>();
      matchers.add(matcher);
      SSLParameters params = serverEngine.getSSLParameters();
      params.setSNIMatchers(matchers);
      serverEngine.setSSLParameters(params);
    }
    return serverEngine;
  }
  /**
   * Returns client ssl engine.
   *
   * @param context - SSLContext to get SSLEngine from.
   * @param useSNI - flag used to enable or disable using SNI extension. Needed for Kerberos.
   */
  public static SSLEngine getClientSSLEngine(SSLContext context, boolean useSNI) {

    SSLEngine clientEngine = context.createSSLEngine(HOST, 80);
    clientEngine.setUseClientMode(true);
    if (useSNI) {
      SNIHostName serverName = new SNIHostName(SERVER_NAME);
      List<SNIServerName> serverNames = new ArrayList<>();
      serverNames.add(serverName);
      SSLParameters params = clientEngine.getSSLParameters();
      params.setServerNames(serverNames);
      clientEngine.setSSLParameters(params);
    }
    return clientEngine;
  }
  /**
   * Does the handshake of the two specified engines according to the {@code mode} specified.
   *
   * @param clientEngine - Client SSLEngine.
   * @param serverEngine - Server SSLEngine.
   * @param maxPacketSize - Maximum packet size for MFLN of zero for no limit.
   * @param mode - Handshake mode according to {@link HandshakeMode} enum.
   * @param enableReplicatedPacks - Set {@code true} to enable replicated packet sending.
   * @throws SSLException - thrown on engine errors.
   */
  public static void doHandshake(
      SSLEngine clientEngine,
      SSLEngine serverEngine,
      int maxPacketSize,
      HandshakeMode mode,
      boolean enableReplicatedPacks)
      throws SSLException {

    System.out.println("=============================================");
    System.out.println("Starting handshake " + mode.name());
    int loop = 0;
    if (maxPacketSize < 0) {
      throw new Error("Test issue: maxPacketSize is less than zero!");
    }
    SSLParameters params = clientEngine.getSSLParameters();
    params.setMaximumPacketSize(maxPacketSize);
    clientEngine.setSSLParameters(params);
    params = serverEngine.getSSLParameters();
    params.setMaximumPacketSize(maxPacketSize);
    serverEngine.setSSLParameters(params);
    SSLEngine firstEngine;
    SSLEngine secondEngine;
    switch (mode) {
      case INITIAL_HANDSHAKE:
        firstEngine = clientEngine;
        secondEngine = serverEngine;
        doUnwrapForNotHandshakingStatus = false;
        clientEngine.beginHandshake();
        serverEngine.beginHandshake();
        break;
      case REHANDSHAKE_BEGIN_CLIENT:
        firstEngine = clientEngine;
        secondEngine = serverEngine;
        doUnwrapForNotHandshakingStatus = true;
        clientEngine.beginHandshake();
        break;
      case REHANDSHAKE_BEGIN_SERVER:
        firstEngine = serverEngine;
        secondEngine = clientEngine;
        doUnwrapForNotHandshakingStatus = true;
        serverEngine.beginHandshake();
        break;
      default:
        throw new Error("Test issue: unknown handshake mode");
    }
    endHandshakeLoop = false;
    while (!endHandshakeLoop) {
      if (++loop > MAX_HANDSHAKE_LOOPS) {
        throw new Error("Too much loops for handshaking");
      }
      System.out.println("============================================");
      System.out.println("Handshake loop " + loop + ": round 1");
      System.out.println("==========================");
      handshakeProcess(firstEngine, secondEngine, maxPacketSize, enableReplicatedPacks);
      if (endHandshakeLoop) {
        break;
      }
      System.out.println("Handshake loop " + loop + ": round 2");
      System.out.println("==========================");
      handshakeProcess(secondEngine, firstEngine, maxPacketSize, enableReplicatedPacks);
    }
  }
    @Override
    protected void initChannel(Channel channel) throws Exception {
      SslContext sslContext;

      SSLParameters sslParams = new SSLParameters();

      if (redisURI.isVerifyPeer()) {
        sslContext = SslContext.newClientContext(SslProvider.JDK);
        if (JavaRuntime.AT_LEAST_JDK_7) {
          sslParams.setEndpointIdentificationAlgorithm("HTTPS");
        }
      } else {
        sslContext =
            SslContext.newClientContext(SslProvider.JDK, InsecureTrustManagerFactory.INSTANCE);
      }

      SSLEngine sslEngine =
          sslContext.newEngine(channel.alloc(), redisURI.getHost(), redisURI.getPort());
      sslEngine.setSSLParameters(sslParams);

      removeIfExists(channel.pipeline(), SslHandler.class);

      if (channel.pipeline().get("first") == null) {
        channel
            .pipeline()
            .addFirst(
                "first",
                new ChannelDuplexHandler() {

                  @Override
                  public void channelActive(ChannelHandlerContext ctx) throws Exception {
                    eventBus.publish(new ConnectedEvent(local(ctx), remote(ctx)));
                    super.channelActive(ctx);
                  }

                  @Override
                  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                    eventBus.publish(new DisconnectedEvent(local(ctx), remote(ctx)));
                    super.channelInactive(ctx);
                  }
                });
      }

      SslHandler sslHandler = new SslHandler(sslEngine, redisURI.isStartTls());
      channel.pipeline().addLast(sslHandler);
      if (channel.pipeline().get("channelActivator") == null) {
        channel
            .pipeline()
            .addLast(
                "channelActivator",
                new RedisChannelInitializerImpl() {

                  private Command<?, ?, ?> pingCommand;

                  @Override
                  public Future<Boolean> channelInitialized() {
                    return initializedFuture;
                  }

                  @Override
                  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                    initializedFuture = SettableFuture.create();
                    pingCommand = null;
                    super.channelInactive(ctx);
                  }

                  @Override
                  public void channelActive(ChannelHandlerContext ctx) throws Exception {
                    if (initializedFuture.isDone()) {
                      super.channelActive(ctx);
                    }
                  }

                  @Override
                  public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
                      throws Exception {
                    if (evt instanceof SslHandshakeCompletionEvent && !initializedFuture.isDone()) {

                      SslHandshakeCompletionEvent event = (SslHandshakeCompletionEvent) evt;
                      if (event.isSuccess()) {
                        if (pingBeforeActivate) {
                          pingCommand = INITIALIZING_CMD_BUILDER.ping();
                          pingBeforeActivate(pingCommand, initializedFuture, ctx, handlers);
                        } else {
                          ctx.fireChannelActive();
                        }
                      } else {
                        initializedFuture.setException(event.cause());
                      }
                    }

                    if (evt instanceof ConnectionEvents.Close) {
                      if (ctx.channel().isOpen()) {
                        ctx.channel().close();
                      }
                    }

                    if (evt instanceof ConnectionEvents.Activated) {
                      if (!initializedFuture.isDone()) {
                        initializedFuture.set(true);
                        eventBus.publish(new ConnectionActivatedEvent(local(ctx), remote(ctx)));
                      }
                    }

                    super.userEventTriggered(ctx, evt);
                  }

                  @Override
                  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
                      throws Exception {

                    if (!initializedFuture.isDone()) {
                      initializedFuture.setException(cause);
                    }
                    super.exceptionCaught(ctx, cause);
                  }
                });
      }

      for (ChannelHandler handler : handlers) {
        removeIfExists(channel.pipeline(), handler.getClass());
        channel.pipeline().addLast(handler);
      }
    }