Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*
    * Copyright 2012 The Netty Project
    *
    * The Netty Project licenses this file to you under the Apache License,
    * version 2.0 (the "License"); you may not use this file except in compliance
    * with the License. You may obtain a copy of the License at:
    *
    *   http://www.apache.org/licenses/LICENSE-2.0
    *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   * License for the specific language governing permissions and limitations
   * under the License.
   */
  package divconq.net.ssl;
  
  
  import java.util.List;
Adds SSL · TLS and StartTLS support to a io.netty.channel.Channel. Please refer to the "SecureChat" example in the distribution or the web site for the detailed usage.

Beginning the handshake

You must make sure not to write a message while the handshake is in progress unless you are renegotiating. You will be notified by the io.netty.util.concurrent.Future which is returned by the handshakeFuture() method when the handshake process succeeds or fails.

Beside using the handshake io.netty.channel.ChannelFuture to get notified about the completation of the handshake it's also possible to detect it by implement the io.netty.channel.ChannelInboundHandler.userEventTriggered(io.netty.channel.ChannelHandlerContext,java.lang.Object) method and check for a SslHandshakeCompletionEvent.

Handshake

The handshake will be automaticly issued for you once the io.netty.channel.Channel is active and javax.net.ssl.SSLEngine.getUseClientMode() returns true. So no need to bother with it by your self.

Closing the session

To close the SSL session, the close() method should be called to send the close_notify message to the remote peer. One exception is when you close the io.netty.channel.Channel - SslHandler intercepts the close request and send the close_notify message before the channel closure automatically. Once the SSL session is closed, it is not reusable, and consequently you should create a new SslHandler with a new javax.net.ssl.SSLEngine as explained in the following section.

Restarting the session

To restart the SSL session, you must remove the existing closed SslHandler from the io.netty.channel.ChannelPipeline, insert a new SslHandler with a new javax.net.ssl.SSLEngine into the pipeline, and start the handshake process as described in the first section.

Implementing StartTLS

StartTLS is the communication pattern that secures the wire in the middle of the plaintext connection. Please note that it is different from SSL · TLS, that secures the wire from the beginning of the connection. Typically, StartTLS is composed of three steps:

  1. Client sends a StartTLS request to server.
  2. Server sends a StartTLS response to client.
  3. Client begins SSL handshake.
If you implement a server, you need to:
  1. create a new SslHandler instance with startTls flag set to true,
  2. insert the SslHandler to the io.netty.channel.ChannelPipeline, and
  3. write a StartTLS response.
Please note that you must insert SslHandler before sending the StartTLS response. Otherwise the client can send begin SSL handshake before SslHandler is inserted to the io.netty.channel.ChannelPipeline, causing data corruption.

The client-side implementation is much simpler.

  1. Write a StartTLS request,
  2. wait for the StartTLS response,
  3. create a new SslHandler instance with startTls flag set to false,
  4. insert the SslHandler to the io.netty.channel.ChannelPipeline, and
  5. Initiate SSL handshake.

Known issues

Because of a known issue with the current implementation of the SslEngine that comes with Java it may be possible that you see blocked IO-Threads while a full GC is done.

So if you are affected you can workaround this problem by adjust the cache settings like shown below:

     SslContext context = ...;
     context.getServerSessionContext().setSessionCacheSize(someSaneSize);
     context.getServerSessionContext().setSessionTime(someSameTimeout);
 

What values to use here depends on the nature of your application and should be set based on monitoring and debugging of it. For more details see #832 in our issue tracker.

 
 public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundHandler {
 
     private static final InternalLogger logger =
             InternalLoggerFactory.getInstance(SslHandler.class);
 
     private static final Pattern IGNORABLE_CLASS_IN_STACK = Pattern.compile(
             "^.*(?:Socket|Datagram|Sctp|Udt)Channel.*$");
     private static final Pattern IGNORABLE_ERROR_MESSAGE = Pattern.compile(
             "^.*(?:connection.*(?:reset|closed|abort|broken)|broken.*pipe).*$".);
 
     private static final SSLException SSLENGINE_CLOSED = new SSLException("SSLEngine closed already");
     private static final SSLException HANDSHAKE_TIMED_OUT = new SSLException("handshake timed out");
     private static final ClosedChannelException CHANNEL_CLOSED = new ClosedChannelException();
 
     static {
     }
 
     private volatile ChannelHandlerContext ctx;
     private final SSLEngine engine;
     private final int maxPacketBufferSize;
     private final Executor delegatedTaskExecutor;
 
     // BEGIN Platform-dependent flags
 
    
trus if and only if javax.net.ssl.SSLEngine expects a direct buffer.
 
     private final boolean wantsDirectBuffer;
    
true if and only if javax.net.ssl.SSLEngine.wrap(java.nio.ByteBuffer,java.nio.ByteBuffer) requires the output buffer to be always as large as maxPacketBufferSize even if the input buffer contains small amount of data.

If this flag is false, we allocate a smaller output buffer.

 
     private final boolean wantsLargeOutboundNetworkBuffer;
    
true if and only if javax.net.ssl.SSLEngine.unwrap(java.nio.ByteBuffer,java.nio.ByteBuffer) expects a heap buffer rather than a direct buffer. For an unknown reason, JDK8 SSLEngine causes JVM to crash when its cipher suite uses Galois Counter Mode (GCM).
 
     private boolean wantsInboundHeapBuffer;
 
     // END Platform-dependent flags
 
     private final boolean startTls;
     private boolean sentFirstMessage;
     private boolean flushedBeforeHandshakeDone;
 
     private final LazyChannelPromise handshakePromise = new LazyChannelPromise();
     private final LazyChannelPromise sslCloseFuture = new LazyChannelPromise();
 
     /*
      * Set by wrap*() methods when something is produced.
      * {@link #channelReadComplete(ChannelHandlerContext)} will check this flag, clear it, and call ctx.flush().
      */
     private boolean needsFlush;
 
     private int packetLength;
 
     private volatile long handshakeTimeoutMillis = 10000;
     private volatile long closeNotifyTimeoutMillis = 3000;
 
     /*
      * Creates a new instance.
      *
      * @param engine  the {@link SSLEngine} this handler will use
      */
     public SslHandler(SSLEngine engine) {
         this(enginefalse);
     }
 
     /*
      * Creates a new instance.
      *
      * @param engine    the {@link SSLEngine} this handler will use
      * @param startTls  {@code true} if the first write request shouldn't be
      *                  encrypted by the {@link SSLEngine}
      */
     public SslHandler(SSLEngine engineboolean startTls) {
         this(enginestartTls.);
     }
 
     /*
      * @deprecated Use {@link #SslHandler(SSLEngine)} instead.
      */
     @Deprecated
     public SslHandler(SSLEngine engineExecutor delegatedTaskExecutor) {
         this(enginefalsedelegatedTaskExecutor);
     }
 
     /*
      * @deprecated Use {@link #SslHandler(SSLEngine, boolean)} instead.
      */
     @Deprecated
     public SslHandler(SSLEngine engineboolean startTlsExecutor delegatedTaskExecutor) {
         if (engine == null) {
             throw new NullPointerException("engine");
         }
         if (delegatedTaskExecutor == null) {
             throw new NullPointerException("delegatedTaskExecutor");
         }
         this. = engine;
         this. = delegatedTaskExecutor;
         this. = startTls;
          = engine.getSession().getPacketBufferSize();
 
          = true;
          = false;
     }
 
     public long getHandshakeTimeoutMillis() {
         return ;
     }
 
     public void setHandshakeTimeout(long handshakeTimeoutTimeUnit unit) {
         if (unit == null) {
             throw new NullPointerException("unit");
         }
 
         setHandshakeTimeoutMillis(unit.toMillis(handshakeTimeout));
     }
 
     public void setHandshakeTimeoutMillis(long handshakeTimeoutMillis) {
         if (handshakeTimeoutMillis < 0) {
             throw new IllegalArgumentException(
                     "handshakeTimeoutMillis: " + handshakeTimeoutMillis + " (expected: >= 0)");
         }
         this. = handshakeTimeoutMillis;
     }
 
     public long getCloseNotifyTimeoutMillis() {
         return ;
     }
 
     public void setCloseNotifyTimeout(long closeNotifyTimeoutTimeUnit unit) {
         if (unit == null) {
             throw new NullPointerException("unit");
         }
 
         setCloseNotifyTimeoutMillis(unit.toMillis(closeNotifyTimeout));
     }
 
     public void setCloseNotifyTimeoutMillis(long closeNotifyTimeoutMillis) {
         if (closeNotifyTimeoutMillis < 0) {
             throw new IllegalArgumentException(
                     "closeNotifyTimeoutMillis: " + closeNotifyTimeoutMillis + " (expected: >= 0)");
         }
         this. = closeNotifyTimeoutMillis;
     }
 
     /*
      * Returns the {@link SSLEngine} which is used by this handler.
      */
     public SSLEngine engine() {
         return ;
     }
 
     /*
      * Returns a {@link Future} that will get notified once the handshake completes.
      */
     public Future<ChannelhandshakeFuture() {
         return ;
     }
 
     /*
      * Sends an SSL {@code close_notify} message to the specified channel and
      * destroys the underlying {@link SSLEngine}.
      */
     public ChannelFuture close() {
         return close(.newPromise());
     }
 
     /*
      * See {@link #close()}
      */
     public ChannelFuture close(final ChannelPromise future) {
         final ChannelHandlerContext ctx = this.;
         ctx.executor().execute(new Runnable() {
             @Override
             public void run() {
                 .closeOutbound();
                 try {
                     write(ctx.future);
                     flush(ctx);
                 } catch (Exception e) {
                     if (!future.tryFailure(e)) {
                         .warn("flush() raised a masked exception."e);
                     }
                 }
             }
         });
 
         return future;
     }
 
     /*
      * Return the {@link ChannelFuture} that will get notified if the inbound of the {@link SSLEngine} will get closed.
      *
      * This method will return the same {@link ChannelFuture} all the time.
      *
      * For more informations see the apidocs of {@link SSLEngine}
      *
      */
     public Future<ChannelsslCloseFuture() {
         return ;
     }
 
     @Override
     public void handlerRemoved0(ChannelHandlerContext ctxthrows Exception {
         if (!.isEmpty()) {
             // Check if queue is not empty first because create a new ChannelException is expensive
             .removeAndFailAll(new ChannelException("Pending write on removal of SslHandler"));
         }
     }
 
     @Override
     public void bind(ChannelHandlerContext ctxSocketAddress localAddressChannelPromise promisethrows Exception {
         ctx.bind(localAddresspromise);
     }
 
     @Override
     public void connect(ChannelHandlerContext ctxSocketAddress remoteAddressSocketAddress localAddress,
                         ChannelPromise promisethrows Exception {
         ctx.connect(remoteAddresslocalAddresspromise);
     }
 
     @Override
     public void deregister(ChannelHandlerContext ctxChannelPromise promisethrows Exception {
         ctx.deregister(promise);
     }
 
     @Override
     public void disconnect(final ChannelHandlerContext ctx,
                            final ChannelPromise promisethrows Exception {
         closeOutboundAndChannel(ctxpromisetrue);
     }
 
     @Override
     public void close(final ChannelHandlerContext ctx,
                       final ChannelPromise promisethrows Exception {
         closeOutboundAndChannel(ctxpromisefalse);
     }
 
     @Override
     public void read(ChannelHandlerContext ctx) {
         ctx.read();
     }
 
     @Override
     public void write(final ChannelHandlerContext ctxObject msgChannelPromise promisethrows Exception {
         .add(msgpromise);
     }
 
     @Override
     public void flush(ChannelHandlerContext ctxthrows Exception {
         // Do not encrypt the first write request if this handler is
         // created with startTLS flag turned on.
         if ( && !) {
              = true;
             .removeAndWriteAll();
             ctx.flush();
             return;
         }
         if (.isEmpty()) {
         }
         if (!.isDone()) {
              = true;
         }
         wrap(ctxfalse);
         ctx.flush();
     }
 
     private void wrap(ChannelHandlerContext ctxboolean inUnwrapthrows SSLException {
         ByteBuf out = null;
         ChannelPromise promise = null;
         try {
             for (;;) {
                 Object msg = .current();
                 if (msg == null) {
                     break;
                 }
 
                 if (!(msg instanceof ByteBuf)) {
                     .removeAndWrite();
                     continue;
                 }
 
                 ByteBuf buf = (ByteBufmsg;
                 if (out == null) {
                     out = allocateOutNetBuf(ctxbuf.readableBytes());
                 }
 
                 SSLEngineResult result = wrap(bufout);
 
                 if (!buf.isReadable()) {
                     promise = .remove();
                 } else {
                     promise = null;
                 }
 
                 if (result.getStatus() == .) {
                     // SSLEngine has been closed already.
                     // Any further write attempts should be denied.
                     .removeAndFailAll();
                     return;
                 } else {
                     switch (result.getHandshakeStatus()) {
                         case :
                             runDelegatedTasks();
                             break;
                         case :
                             setHandshakeSuccess();
                             // deliberate fall-through
                         case :
                             setHandshakeSuccessIfStillHandshaking();
                             // deliberate fall-through
                         case :
                             finishWrap(ctxoutpromiseinUnwrap);
                             promise = null;
                             out = null;
                             break;
                         case :
                             return;
                         default:
                             throw new IllegalStateException(
                                     "Unknown handshake status: " + result.getHandshakeStatus());
                     }
                 }
             }
         } catch (SSLException e) {
             setHandshakeFailure(e);
             throw e;
         } finally {
             finishWrap(ctxoutpromiseinUnwrap);
         }
     }
 
     private void finishWrap(ChannelHandlerContext ctxByteBuf outChannelPromise promiseboolean inUnwrap) {
         if (out == null) {
             out = .;
         } else if (!out.isReadable()) {
             out.release();
             out = .;
         }
 
         if (promise != null) {
             ctx.write(outpromise);
         } else {
             ctx.write(out);
         }
 
         if (inUnwrap) {
              = true;
         }
     }
 
     private void wrapNonAppData(ChannelHandlerContext ctxboolean inUnwrapthrows SSLException {
         ByteBuf out = null;
         try {
             for (;;) {
                 if (out == null) {
                     out = allocateOutNetBuf(ctx, 0);
                 }
                 SSLEngineResult result = wrap(.out);
 
                 if (result.bytesProduced() > 0) {
                     ctx.write(out);
                     if (inUnwrap) {
                          = true;
                     }
                     out = null;
                 }
 
                 switch (result.getHandshakeStatus()) {
                     case :
                         setHandshakeSuccess();
                         break;
                     case :
                         runDelegatedTasks();
                         break;
                     case :
                         if (!inUnwrap) {
                             unwrapNonAppData(ctx);
                         }
                         break;
                     case :
                         break;
                     case :
                         setHandshakeSuccessIfStillHandshaking();
                         // Workaround for TLS False Start problem reported at:
                         // https://github.com/netty/netty/issues/1108#issuecomment-14266970
                         if (!inUnwrap) {
                             unwrapNonAppData(ctx);
                         }
                         break;
                     default:
                         throw new IllegalStateException("Unknown handshake status: " + result.getHandshakeStatus());
                 }
 
                 if (result.bytesProduced() == 0) {
                     break;
                 }
             }
         } catch (SSLException e) {
             setHandshakeFailure(e);
             throw e;
         }  finally {
             if (out != null) {
                 out.release();
             }
         }
     }
 
     private SSLEngineResult wrap(SSLEngine engineByteBuf inByteBuf outthrows SSLException {
         ByteBuffer in0 = in.nioBuffer();
         if (!in0.isDirect()) {
             ByteBuffer newIn0 = ByteBuffer.allocateDirect(in0.remaining());
             newIn0.put(in0).flip();
             in0 = newIn0;
         }
 
         for (;;) {
             ByteBuffer out0 = out.nioBuffer(out.writerIndex(), out.writableBytes());
             SSLEngineResult result = engine.wrap(in0out0);
             in.skipBytes(result.bytesConsumed());
             out.writerIndex(out.writerIndex() + result.bytesProduced());
 
             switch (result.getStatus()) {
                 case :
                     out.ensureWritable();
                     break;
                 default:
                     return result;
             }
         }
     }
 
     @Override
     public void channelInactive(ChannelHandlerContext ctxthrows Exception {
         // Make sure to release SSLEngine,
         // and notify the handshake future if the connection has been closed during handshake.
         super.channelInactive(ctx);
     }
 
     @Override
     public void exceptionCaught(ChannelHandlerContext ctxThrowable causethrows Exception {
         if (ignoreException(cause)) {
             // It is safe to ignore the 'connection reset by peer' or
             // 'broken pipe' error after sending close_notify.
             if (.isDebugEnabled()) {
                 .debug(
                         "Swallowing a harmless 'connection reset by peer / broken pipe' error that occurred " +
                                 "while writing close_notify in response to the peer's close_notify"cause);
             }
 
             // Close the connection explicitly just in case the transport
             // did not close the connection automatically.
             if (ctx.channel().isActive()) {
                 ctx.close();
             }
         } else {
             ctx.fireExceptionCaught(cause);
         }
     }
 
     /*
      * Checks if the given {@link Throwable} can be ignore and just "swallowed"
      *
      * When an ssl connection is closed a close_notify message is sent.
      * After that the peer also sends close_notify however, it's not mandatory to receive
      * the close_notify. The party who sent the initial close_notify can close the connection immediately
      * then the peer will get connection reset error.
      *
      */
     private boolean ignoreException(Throwable t) {
         if (!(t instanceof SSLException) && t instanceof IOException && .isDone()) {
             String message = String.valueOf(t.getMessage()).toLowerCase();
 
             // first try to match connection reset / broke peer based on the regex. This is the fastest way
             // but may fail on different jdk impls or OS's
             if (.matcher(message).matches()) {
                 return true;
             }
 
             // Inspect the StackTraceElements to see if it was a connection reset / broken pipe or not
             StackTraceElement[] elements = t.getStackTrace();
             for (StackTraceElement elementelements) {
                 String classname = element.getClassName();
                 String methodname = element.getMethodName();
 
                 // skip all classes that belong to the io.netty package
                 if (classname.startsWith("io.netty.")) {
                     continue;
                 }
 
                 // check if the method name is read if not skip it
                 if (!"read".equals(methodname)) {
                     continue;
                 }
 
                 // This will also match against SocketInputStream which is used by openjdk 7 and maybe
                 // also others
                 if (.matcher(classname).matches()) {
                     return true;
                 }
 
                 try {
                     // No match by now.. Try to load the class via classloader and inspect it.
                     // This is mainly done as other JDK implementations may differ in name of
                     // the impl.
                     Class<?> clazz = PlatformDependent.getClassLoader(getClass()).loadClass(classname);
 
                     if (SocketChannel.class.isAssignableFrom(clazz)
                             || DatagramChannel.class.isAssignableFrom(clazz)) {
                         return true;
                     }
 
                     // also match against SctpChannel via String matching as it may not present.
                     if (PlatformDependent.javaVersion() >= 7
                             && "com.sun.nio.sctp.SctpChannel".equals(clazz.getSuperclass().getName())) {
                         return true;
                     }
                 } catch (ClassNotFoundException e) {
                     // This should not happen just ignore
                 }
             }
         }
 
         return false;
     }
 
     /*
      * Returns {@code true} if the given {@link ByteBuf} is encrypted. Be aware that this method
      * will not increase the readerIndex of the given {@link ByteBuf}.
      *
      * @param   buffer
      *                  The {@link ByteBuf} to read from. Be aware that it must have at least 5 bytes to read,
      *                  otherwise it will throw an {@link IllegalArgumentException}.
      * @return encrypted
      *                  {@code true} if the {@link ByteBuf} is encrypted, {@code false} otherwise.
      * @throws IllegalArgumentException
      *                  Is thrown if the given {@link ByteBuf} has not at least 5 bytes to read.
      */
     public static boolean isEncrypted(ByteBuf buffer) {
         if (buffer.readableBytes() < 5) {
             throw new IllegalArgumentException("buffer must have at least 5 readable bytes");
         }
         return getEncryptedPacketLength(bufferbuffer.readerIndex()) != -1;
     }
 
     /*
      * Return how much bytes can be read out of the encrypted data. Be aware that this method will not increase
      * the readerIndex of the given {@link ByteBuf}.
      *
      * @param   buffer
      *                  The {@link ByteBuf} to read from. Be aware that it must have at least 5 bytes to read,
      *                  otherwise it will throw an {@link IllegalArgumentException}.
      * @return length
      *                  The length of the encrypted packet that is included in the buffer. This will
      *                  return {@code -1} if the given {@link ByteBuf} is not encrypted at all.
      * @throws IllegalArgumentException
      *                  Is thrown if the given {@link ByteBuf} has not at least 5 bytes to read.
      */
     private static int getEncryptedPacketLength(ByteBuf bufferint offset) {
         int packetLength = 0;
 
         // SSLv3 or TLS - Check ContentType
         boolean tls;
         switch (buffer.getUnsignedByte(offset)) {
             case 20:  // change_cipher_spec
             case 21:  // alert
             case 22:  // handshake
             case 23:  // application_data
                 tls = true;
                 break;
             default:
                 // SSLv2 or bad data
                 tls = false;
         }
 
         if (tls) {
             // SSLv3 or TLS - Check ProtocolVersion
             int majorVersion = buffer.getUnsignedByte(offset + 1);
             if (majorVersion == 3) {
                 // SSLv3 or TLS
                 packetLength = buffer.getUnsignedShort(offset + 3) + 5;
                 if (packetLength <= 5) {
                     // Neither SSLv3 or TLSv1 (i.e. SSLv2 or bad data)
                     tls = false;
                 }
             } else {
                 // Neither SSLv3 or TLSv1 (i.e. SSLv2 or bad data)
                 tls = false;
             }
         }
 
         if (!tls) {
             // SSLv2 or bad data - Check the version
             boolean sslv2 = true;
             int headerLength = (buffer.getUnsignedByte(offset) & 0x80) != 0 ? 2 : 3;
             int majorVersion = buffer.getUnsignedByte(offset + headerLength + 1);
             if (majorVersion == 2 || majorVersion == 3) {
                 // SSLv2
                 if (headerLength == 2) {
                     packetLength = (buffer.getShort(offset) & 0x7FFF) + 2;
                 } else {
                     packetLength = (buffer.getShort(offset) & 0x3FFF) + 3;
                 }
                 if (packetLength <= headerLength) {
                     sslv2 = false;
                 }
             } else {
                 sslv2 = false;
             }
 
             if (!sslv2) {
                 return -1;
             }
         }
         return packetLength;
     }
 
     @Override
     protected void decode(ChannelHandlerContext ctxByteBuf inList<Objectoutthrows SSLException {
 
         final int startOffset = in.readerIndex();
         final int endOffset = in.writerIndex();
         int offset = startOffset;
         int totalLength = 0;
 
         // If we calculated the length of the current SSL record before, use that information.
         if ( > 0) {
             if (endOffset - startOffset < ) {
                 return;
             } else {
                 offset += ;
                 totalLength = ;
                  = 0;
             }
         }
 
         boolean nonSslRecord = false;
 
         while (totalLength < .) {
             final int readableBytes = endOffset - offset;
             if (readableBytes < 5) {
                 break;
             }
 
             final int packetLength = getEncryptedPacketLength(inoffset);
             if (packetLength == -1) {
                 nonSslRecord = true;
                 break;
             }
 
             assert packetLength > 0;
 
             if (packetLength > readableBytes) {
                 // wait until the whole packet can be read
                 this. = packetLength;
                 break;
             }
 
             int newTotalLength = totalLength + packetLength;
             if (newTotalLength > .) {
                 // Don't read too much.
                 break;
             }
 
             // We have a whole packet.
             // Increment the offset to handle the next packet.
             offset += packetLength;
             totalLength = newTotalLength;
         }
 
         if (totalLength > 0) {
             // The buffer contains one or more full SSL records.
             // Slice out the whole packet so unwrap will only be called with complete packets.
             // Also directly reset the packetLength. This is needed as unwrap(..) may trigger
             // decode(...) again via:
             // 1) unwrap(..) is called
             // 2) wrap(...) is called from within unwrap(...)
             // 3) wrap(...) calls unwrapLater(...)
             // 4) unwrapLater(...) calls decode(...)
             //
             // See https://github.com/netty/netty/issues/1534
 
             in.skipBytes(totalLength);
             final ByteBuffer inNetBuf = in.nioBuffer(startOffsettotalLength);
             unwrap(ctxinNetBuftotalLength);
             assert !inNetBuf.hasRemaining() || .isInboundDone();
         }
 
         if (nonSslRecord) {
             // Not an SSL/TLS packet
             NotSslRecordException e = new NotSslRecordException(
                     "not an SSL/TLS record: " + ByteBufUtil.hexDump(in));
             in.skipBytes(in.readableBytes());
             ctx.fireExceptionCaught(e);
             setHandshakeFailure(e);
         }
     }
 
     @Override
     public void channelReadComplete(ChannelHandlerContext ctxthrows Exception {
         if () {
              = false;
             ctx.flush();
         }
         
         // if not auto reading but no handshake, then read anyway
         if (!.isDone() && !ctx.channel().config().isAutoRead()) {
         	ctx.read();
         }
         
         super.channelReadComplete(ctx);
     }
 
     /*
      * Calls {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer)} with an empty buffer to handle handshakes, etc.
      */
     private void unwrapNonAppData(ChannelHandlerContext ctxthrows SSLException {
         unwrap(ctx..nioBuffer(), 0);
     }
 
     /*
      * Unwraps inbound SSL records.
      */
     private void unwrap(
             ChannelHandlerContext ctxByteBuffer packetint initialOutAppBufCapacitythrows SSLException {
 
         // If SSLEngine expects a heap buffer for unwrapping, do the conversion.
         final ByteBuffer oldPacket;
         final ByteBuf newPacket;
         final int oldPos = packet.position();
         if ( && packet.isDirect()) {
             newPacket = ctx.alloc().heapBuffer(packet.limit() - oldPos);
             newPacket.writeBytes(packet);
             oldPacket = packet;
             packet = newPacket.nioBuffer();
         } else {
             oldPacket = null;
             newPacket = null;
         }
 
         boolean wrapLater = false;
         ByteBuf decodeOut = allocate(ctxinitialOutAppBufCapacity);
         try {
             for (;;) {
                 final SSLEngineResult result = unwrap(packetdecodeOut);
                 final Status status = result.getStatus();
                 final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
                 final int produced = result.bytesProduced();
                 final int consumed = result.bytesConsumed();
 
                 if (status == .) {
                     // notify about the CLOSED state of the SSLEngine. See #137
                     .trySuccess(ctx.channel());
                     break;
                 }
 
                 switch (handshakeStatus) {
                     case :
                         break;
                     case :
                         wrapNonAppData(ctxtrue);
                         break;
                     case :
                         runDelegatedTasks();
                         break;
                     case :
                         setHandshakeSuccess();
                         wrapLater = true;
                         continue;
                     case :
                         if (setHandshakeSuccessIfStillHandshaking()) {
                             wrapLater = true;
                             continue;
                         }
                         if () {
                             // We need to call wrap(...) in case there was a flush done before the handshake completed.
                             //
                             // See https://github.com/netty/netty/pull/2437
                              = false;
                             wrapLater = true;
                         }
 
                         break;
                     default:
                         throw new IllegalStateException("Unknown handshake status: " + handshakeStatus);
                 }
 
                 if (status == . || consumed == 0 && produced == 0) {
                     break;
                 }
             }
 
             if (wrapLater) {
                 wrap(ctxtrue);
             }
         } catch (SSLException e) {
             setHandshakeFailure(e);
             throw e;
         } finally {
             // If we converted packet into a heap buffer at the beginning of this method,
             // we should synchronize the position of the original buffer.
             if (newPacket != null) {
                 oldPacket.position(oldPos + packet.position());
                 newPacket.release();
             }
 
             if (decodeOut.isReadable()) {
                 ctx.fireChannelRead(decodeOut);
             } else {
                 decodeOut.release();
             }
         }
     }
 
     private static SSLEngineResult unwrap(SSLEngine engineByteBuffer inByteBuf outthrows SSLException {
         int overflows = 0;
         for (;;) {
             ByteBuffer out0 = out.nioBuffer(out.writerIndex(), out.writableBytes());
             SSLEngineResult result = engine.unwrap(inout0);
             out.writerIndex(out.writerIndex() + result.bytesProduced());
             switch (result.getStatus()) {
                 case :
                     int max = engine.getSession().getApplicationBufferSize();
                     switch (overflows ++) {
                         case 0:
                             out.ensureWritable(Math.min(maxin.remaining()));
                             break;
                         default:
                             out.ensureWritable(max);
                     }
                     break;
                default:
                    return result;
            }
        }
    }
    /*
     * Fetches all delegated tasks from the {@link SSLEngine} and runs them via the {@link #delegatedTaskExecutor}.
     * If the {@link #delegatedTaskExecutor} is {@link ImmediateExecutor}, just call {@link Runnable#run()} directly
     * instead of using {@link Executor#execute(Runnable)}.  Otherwise, run the tasks via
     * the {@link #delegatedTaskExecutor} and wait until the tasks are finished.
     */
    private void runDelegatedTasks() {
            for (;;) {
                Runnable task = .getDelegatedTask();
                if (task == null) {
                    break;
                }
                task.run();
            }
        } else {
            final List<Runnabletasks = new ArrayList<Runnable>(2);
            for (;;) {
                final Runnable task = .getDelegatedTask();
                if (task == null) {
                    break;
                }
                tasks.add(task);
            }
            if (tasks.isEmpty()) {
                return;
            }
            final CountDownLatch latch = new CountDownLatch(1);
            .execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        for (Runnable tasktasks) {
                            task.run();
                        }
                    } catch (Exception e) {
                        .fireExceptionCaught(e);
                    } finally {
                        latch.countDown();
                    }
                }
            });
            boolean interrupted = false;
            while (latch.getCount() != 0) {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                    // Interrupt later.
                    interrupted = true;
                }
            }
            if (interrupted) {
                Thread.currentThread().interrupt();
            }
        }
    }
    /*
     * Works around some Android {@link SSLEngine} implementations that skip {@link HandshakeStatus#FINISHED} and
     * go straight into {@link HandshakeStatus#NOT_HANDSHAKING} when handshake is finished.
     *
     * @return {@code true} if and only if the workaround has been applied and thus {@link #handshakeFuture} has been
     *         marked as success by this method
     */
    private boolean setHandshakeSuccessIfStillHandshaking() {
        if (!.isDone()) {
            setHandshakeSuccess();
            return true;
        }
        return false;
    }
    /*
     * Notify all the handshake futures about the successfully handshake
     */
    private void setHandshakeSuccess() {
        // Work around the JVM crash which occurs when a cipher suite with GCM enabled.
        final String cipherSuite = String.valueOf(.getSession().getCipherSuite());
        if (! && (cipherSuite.contains("_GCM_") || cipherSuite.contains("-GCM-"))) {
             = true;
        }
        if (.trySuccess(.channel())) {
            if (.isDebugEnabled()) {
                .debug(.channel() + " HANDSHAKEN: " + .getSession().getCipherSuite());
            }
        }
    }
    /*
     * Notify all the handshake futures about the failure during the handshake.
     */
    private void setHandshakeFailure(Throwable cause) {
        // Release all resources such as internal buffers that SSLEngine
        // is managing.
        .closeOutbound();
        try {
            .closeInbound();
        } catch (SSLException e) {
            // only log in debug mode as it most likely harmless and latest chrome still trigger
            // this all the time.
            //
            // See https://github.com/netty/netty/issues/1340
            String msg = e.getMessage();
            if (msg == null || !msg.contains("possible truncation attack")) {
                .debug("SSLEngine.closeInbound() raised an exception."e);
            }
        }
        notifyHandshakeFailure(cause);
    }
    private void notifyHandshakeFailure(Throwable cause) {
        if (.tryFailure(cause)) {
            .fireUserEventTriggered(new SslHandshakeCompletionEvent(cause));
            .close();
        }
    }
    private void closeOutboundAndChannel(
            final ChannelHandlerContext ctxfinal ChannelPromise promiseboolean disconnectthrows Exception {
        if (!ctx.channel().isActive()) {
            if (disconnect) {
                ctx.disconnect(promise);
            } else {
                ctx.close(promise);
            }
            return;
        }
        .closeOutbound();
        ChannelPromise closeNotifyFuture = ctx.newPromise();
        write(ctx.closeNotifyFuture);
        flush(ctx);
        safeClose(ctxcloseNotifyFuturepromise);
    }
    @Override
    public void handlerAdded(final ChannelHandlerContext ctxthrows Exception {
        this. = ctx;
         = new PendingWriteQueue(ctx);
        if (ctx.channel().isActive() && .getUseClientMode()) {
            // channelActive() event has been fired already, which means this.channelActive() will
            // not be invoked. We have to initialize here instead.
            handshake();
        } else {
            // channelActive() event has not been fired yet.  this.channelOpen() will be invoked
            // and initialization will occur there.
        }
    }
    private Future<Channelhandshake() {
        final ScheduledFuture<?> timeoutFuture;
        if ( > 0) {
            timeoutFuture = .executor().schedule(new Runnable() {
                @Override
                public void run() {
                    if (.isDone()) {
                        return;
                    }
                    notifyHandshakeFailure();
                }
            }, .);
        } else {
            timeoutFuture = null;
        }
            @Override
            public void operationComplete(Future<Channelfthrows Exception {
                if (timeoutFuture != null) {
                    timeoutFuture.cancel(false);
                }
            }
        });
        try {
            .beginHandshake();
            wrapNonAppData(false);
            .