|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  */ | 
|  |  | 
|  | package sun.security.ssl; | 
|  |  | 
|  | import java.io.IOException; | 
|  | import java.nio.ByteBuffer; | 
|  | import java.nio.ReadOnlyBufferException; | 
|  | import java.security.AccessController; | 
|  | import java.security.PrivilegedActionException; | 
|  | import java.security.PrivilegedExceptionAction; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.concurrent.locks.ReentrantLock; | 
|  | import java.util.function.BiFunction; | 
|  | import javax.net.ssl.SSLEngine; | 
|  | import javax.net.ssl.SSLEngineResult; | 
|  | import javax.net.ssl.SSLEngineResult.HandshakeStatus; | 
|  | import javax.net.ssl.SSLEngineResult.Status; | 
|  | import javax.net.ssl.SSLException; | 
|  | import javax.net.ssl.SSLHandshakeException; | 
|  | import javax.net.ssl.SSLKeyException; | 
|  | import javax.net.ssl.SSLParameters; | 
|  | import javax.net.ssl.SSLPeerUnverifiedException; | 
|  | import javax.net.ssl.SSLProtocolException; | 
|  | import javax.net.ssl.SSLSession; | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  */ | 
|  | final class SSLEngineImpl extends SSLEngine implements SSLTransport { | 
|  |     private final SSLContextImpl        sslContext; | 
|  |     final TransportContext              conContext; | 
|  |     private final ReentrantLock         engineLock = new ReentrantLock(); | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     SSLEngineImpl(SSLContextImpl sslContext) { | 
|  |         this(sslContext, null, -1); | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     SSLEngineImpl(SSLContextImpl sslContext, | 
|  |             String host, int port) { | 
|  |         super(host, port); | 
|  |         this.sslContext = sslContext; | 
|  |         HandshakeHash handshakeHash = new HandshakeHash(); | 
|  |         if (sslContext.isDTLS()) { | 
|  |             this.conContext = new TransportContext(sslContext, this, | 
|  |                     new DTLSInputRecord(handshakeHash), | 
|  |                     new DTLSOutputRecord(handshakeHash)); | 
|  |         } else { | 
|  |             this.conContext = new TransportContext(sslContext, this, | 
|  |                     new SSLEngineInputRecord(handshakeHash), | 
|  |                     new SSLEngineOutputRecord(handshakeHash)); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (host != null) { | 
|  |             this.conContext.sslConfig.serverNames = | 
|  |                     Utilities.addToSNIServerNameList( | 
|  |                             conContext.sslConfig.serverNames, host); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void beginHandshake() throws SSLException { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             if (conContext.isUnsureMode) { | 
|  |                 throw new IllegalStateException( | 
|  |                         "Client/Server mode has not yet been set."); | 
|  |             } | 
|  |  | 
|  |             try { | 
|  |                 conContext.kickstart(); | 
|  |             } catch (IOException ioe) { | 
|  |                 throw conContext.fatal(Alert.HANDSHAKE_FAILURE, | 
|  |                     "Couldn't kickstart handshaking", ioe); | 
|  |             } catch (Exception ex) {      | 
|  |                 throw conContext.fatal(Alert.INTERNAL_ERROR, | 
|  |                     "Fail to begin handshake", ex); | 
|  |             } | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public SSLEngineResult wrap(ByteBuffer[] appData, | 
|  |             int offset, int length, ByteBuffer netData) throws SSLException { | 
|  |         return wrap(appData, offset, length, new ByteBuffer[]{ netData }, 0, 1); | 
|  |     } | 
|  |  | 
|  |      | 
|  |     public SSLEngineResult wrap( | 
|  |         ByteBuffer[] srcs, int srcsOffset, int srcsLength, | 
|  |         ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws SSLException { | 
|  |  | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             if (conContext.isUnsureMode) { | 
|  |                 throw new IllegalStateException( | 
|  |                         "Client/Server mode has not yet been set."); | 
|  |             } | 
|  |  | 
|  |              | 
|  |             checkTaskThrown(); | 
|  |  | 
|  |              | 
|  |             checkParams(srcs, srcsOffset, srcsLength, | 
|  |                     dsts, dstsOffset, dstsLength); | 
|  |  | 
|  |             try { | 
|  |                 return writeRecord( | 
|  |                     srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength); | 
|  |             } catch (SSLProtocolException spe) { | 
|  |                  | 
|  |                 throw conContext.fatal(Alert.UNEXPECTED_MESSAGE, spe); | 
|  |             } catch (IOException ioe) { | 
|  |                 throw conContext.fatal(Alert.INTERNAL_ERROR, | 
|  |                     "problem wrapping app data", ioe); | 
|  |             } catch (Exception ex) {      | 
|  |                 throw conContext.fatal(Alert.INTERNAL_ERROR, | 
|  |                     "Fail to wrap application data", ex); | 
|  |             } | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private SSLEngineResult writeRecord( | 
|  |         ByteBuffer[] srcs, int srcsOffset, int srcsLength, | 
|  |         ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { | 
|  |  | 
|  |          | 
|  |         if (conContext.needHandshakeFinishedStatus) { | 
|  |             conContext.needHandshakeFinishedStatus = false; | 
|  |             return new SSLEngineResult( | 
|  |                     Status.OK, HandshakeStatus.FINISHED, 0, 0); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (isOutboundDone()) { | 
|  |             return new SSLEngineResult( | 
|  |                     Status.CLOSED, conContext.getHandshakeStatus(), 0, 0); | 
|  |         } | 
|  |  | 
|  |         HandshakeContext hc = conContext.handshakeContext; | 
|  |         HandshakeStatus hsStatus = null; | 
|  |         if (!conContext.isNegotiated && !conContext.isBroken && | 
|  |                 !conContext.isInboundClosed() && | 
|  |                 !conContext.isOutboundClosed()) { | 
|  |             conContext.kickstart(); | 
|  |  | 
|  |             hsStatus = conContext.getHandshakeStatus(); | 
|  |             if (hsStatus == HandshakeStatus.NEED_UNWRAP) { | 
|  |                  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |                  */ | 
|  |                 if (!sslContext.isDTLS() || hc == null || | 
|  |                         !hc.sslConfig.enableRetransmissions || | 
|  |                         conContext.outputRecord.firstMessage) { | 
|  |  | 
|  |                     return new SSLEngineResult(Status.OK, hsStatus, 0, 0); | 
|  |                 }   // otherwise, need retransmission | 
|  |             } | 
|  |         } | 
|  |  | 
|  |         if (hsStatus == null) { | 
|  |             hsStatus = conContext.getHandshakeStatus(); | 
|  |         } | 
|  |  | 
|  |          | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |          */ | 
|  |         if (hsStatus == HandshakeStatus.NEED_TASK) { | 
|  |             return new SSLEngineResult(Status.OK, hsStatus, 0, 0); | 
|  |         } | 
|  |  | 
|  |         int dstsRemains = 0; | 
|  |         for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) { | 
|  |             dstsRemains += dsts[i].remaining(); | 
|  |         } | 
|  |  | 
|  |         // Check destination buffer size. | 
|  |         // | 
|  |         // We can be smarter about using smaller buffer sizes later.  For | 
|  |          | 
|  |         if (dstsRemains < conContext.conSession.getPacketBufferSize()) { | 
|  |             return new SSLEngineResult( | 
|  |                 Status.BUFFER_OVERFLOW, conContext.getHandshakeStatus(), 0, 0); | 
|  |         } | 
|  |  | 
|  |         int srcsRemains = 0; | 
|  |         for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { | 
|  |             srcsRemains += srcs[i].remaining(); | 
|  |         } | 
|  |  | 
|  |         Ciphertext ciphertext = null; | 
|  |         try { | 
|  |             // Acquire the buffered to-be-delivered records or retransmissions. | 
|  |             // | 
|  |              | 
|  |             if (!conContext.outputRecord.isEmpty() || (hc != null && | 
|  |                     hc.sslConfig.enableRetransmissions && | 
|  |                     hc.sslContext.isDTLS() && | 
|  |                     hsStatus == HandshakeStatus.NEED_UNWRAP)) { | 
|  |                 ciphertext = encode(null, 0, 0, | 
|  |                         dsts, dstsOffset, dstsLength); | 
|  |             } | 
|  |  | 
|  |             if (ciphertext == null && srcsRemains != 0) { | 
|  |                 ciphertext = encode(srcs, srcsOffset, srcsLength, | 
|  |                         dsts, dstsOffset, dstsLength); | 
|  |             } | 
|  |         } catch (IOException ioe) { | 
|  |             if (ioe instanceof SSLException) { | 
|  |                 throw ioe; | 
|  |             } else { | 
|  |                 throw new SSLException("Write problems", ioe); | 
|  |             } | 
|  |         } | 
|  |  | 
|  |          | 
|  |  | 
|  |          */ | 
|  |         Status status = (isOutboundDone() ? Status.CLOSED : Status.OK); | 
|  |         if (ciphertext != null && ciphertext.handshakeStatus != null) { | 
|  |             hsStatus = ciphertext.handshakeStatus; | 
|  |         } else { | 
|  |             hsStatus = conContext.getHandshakeStatus(); | 
|  |             if (ciphertext == null && !conContext.isNegotiated && | 
|  |                     conContext.isInboundClosed() && | 
|  |                     hsStatus == HandshakeStatus.NEED_WRAP) { | 
|  |                 // Even the outboud is open, no futher data could be wrapped as: | 
|  |                 //     1. the outbound is empty | 
|  |                 //     2. no negotiated connection | 
|  |                 //     3. the inbound has closed, cannot complete the handshake | 
|  |                 // | 
|  |                 // Mark the engine as closed if the handshake status is | 
|  |                 // NEED_WRAP. Otherwise, it could lead to dead loops in | 
|  |                  | 
|  |                 status = Status.CLOSED; | 
|  |             } | 
|  |         } | 
|  |  | 
|  |         int deltaSrcs = srcsRemains; | 
|  |         for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { | 
|  |             deltaSrcs -= srcs[i].remaining(); | 
|  |         } | 
|  |  | 
|  |         int deltaDsts = dstsRemains; | 
|  |         for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) { | 
|  |             deltaDsts -= dsts[i].remaining(); | 
|  |         } | 
|  |  | 
|  |         return new SSLEngineResult(status, hsStatus, deltaSrcs, deltaDsts, | 
|  |                 ciphertext != null ? ciphertext.recordSN : -1L); | 
|  |     } | 
|  |  | 
|  |     private Ciphertext encode( | 
|  |         ByteBuffer[] srcs, int srcsOffset, int srcsLength, | 
|  |         ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { | 
|  |  | 
|  |         Ciphertext ciphertext; | 
|  |         try { | 
|  |             ciphertext = conContext.outputRecord.encode( | 
|  |                 srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength); | 
|  |         } catch (SSLHandshakeException she) { | 
|  |              | 
|  |             throw conContext.fatal(Alert.HANDSHAKE_FAILURE, she); | 
|  |         } catch (IOException e) { | 
|  |             throw conContext.fatal(Alert.UNEXPECTED_MESSAGE, e); | 
|  |         } | 
|  |  | 
|  |         if (ciphertext == null) { | 
|  |             return null; | 
|  |         } | 
|  |  | 
|  |          | 
|  |         boolean needRetransmission = | 
|  |                 conContext.sslContext.isDTLS() && | 
|  |                 conContext.handshakeContext != null && | 
|  |                 conContext.handshakeContext.sslConfig.enableRetransmissions; | 
|  |         HandshakeStatus hsStatus = | 
|  |                 tryToFinishHandshake(ciphertext.contentType); | 
|  |         if (needRetransmission && | 
|  |                 hsStatus == HandshakeStatus.FINISHED && | 
|  |                 conContext.sslContext.isDTLS() && | 
|  |                 ciphertext.handshakeType == SSLHandshake.FINISHED.id) { | 
|  |             // Retransmit the last flight for DTLS. | 
|  |             // | 
|  |             // The application data transactions may begin immediately | 
|  |             // after the last flight.  If the last flight get lost, the | 
|  |             // application data may be discarded accordingly.  As could | 
|  |             // be an issue for some applications.  This impact can be | 
|  |              | 
|  |             if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { | 
|  |                 SSLLogger.finest("retransmit the last flight messages"); | 
|  |             } | 
|  |  | 
|  |             conContext.outputRecord.launchRetransmission(); | 
|  |             hsStatus = HandshakeStatus.NEED_WRAP; | 
|  |         } | 
|  |  | 
|  |         if (hsStatus == null) { | 
|  |             hsStatus = conContext.getHandshakeStatus(); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (conContext.outputRecord.seqNumIsHuge() || | 
|  |                 conContext.outputRecord.writeCipher.atKeyLimit()) { | 
|  |             hsStatus = tryKeyUpdate(hsStatus); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (conContext.conSession.updateNST && | 
|  |                 !conContext.sslConfig.isClientMode) { | 
|  |             hsStatus = tryNewSessionTicket(hsStatus); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         ciphertext.handshakeStatus = hsStatus; | 
|  |  | 
|  |         return ciphertext; | 
|  |     } | 
|  |  | 
|  |     private HandshakeStatus tryToFinishHandshake(byte contentType) { | 
|  |         HandshakeStatus hsStatus = null; | 
|  |         if ((contentType == ContentType.HANDSHAKE.id) && | 
|  |                 conContext.outputRecord.isEmpty()) { | 
|  |             if (conContext.handshakeContext == null) { | 
|  |                 hsStatus = HandshakeStatus.FINISHED; | 
|  |             } else if (conContext.isPostHandshakeContext()) { | 
|  |                  | 
|  |                 hsStatus = conContext.finishPostHandshake(); | 
|  |             } else if (conContext.handshakeContext.handshakeFinished) { | 
|  |                 hsStatus = conContext.finishHandshake(); | 
|  |             } | 
|  |         }   // Otherwise, the followed call to getHSStatus() will help. | 
|  |  | 
|  |         return hsStatus; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private HandshakeStatus tryKeyUpdate( | 
|  |             HandshakeStatus currentHandshakeStatus) throws IOException { | 
|  |         // Don't bother to kickstart if handshaking is in progress, or if the | 
|  |          | 
|  |         if ((conContext.handshakeContext == null) && | 
|  |                 !conContext.isOutboundClosed() && | 
|  |                 !conContext.isInboundClosed() && | 
|  |                 !conContext.isBroken) { | 
|  |             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | 
|  |                 SSLLogger.finest("trigger key update"); | 
|  |             } | 
|  |             beginHandshake(); | 
|  |             return conContext.getHandshakeStatus(); | 
|  |         } | 
|  |  | 
|  |         return currentHandshakeStatus; | 
|  |     } | 
|  |  | 
|  |     // Try to generate a PostHandshake NewSessionTicket message.  This is | 
|  |      | 
|  |     private HandshakeStatus tryNewSessionTicket( | 
|  |             HandshakeStatus currentHandshakeStatus) throws IOException { | 
|  |         // Don't bother to kickstart if handshaking is in progress, or if the | 
|  |          | 
|  |         if ((conContext.handshakeContext == null) && | 
|  |                 conContext.protocolVersion.useTLS13PlusSpec() && | 
|  |                 !conContext.isOutboundClosed() && | 
|  |                 !conContext.isInboundClosed() && | 
|  |                 !conContext.isBroken) { | 
|  |             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | 
|  |                 SSLLogger.finest("trigger NST"); | 
|  |             } | 
|  |             conContext.conSession.updateNST = false; | 
|  |             NewSessionTicket.t13PosthandshakeProducer.produce( | 
|  |                     new PostHandshakeContext(conContext)); | 
|  |             return conContext.getHandshakeStatus(); | 
|  |         } | 
|  |  | 
|  |         return currentHandshakeStatus; | 
|  |     } | 
|  |  | 
|  |     private static void checkParams( | 
|  |             ByteBuffer[] srcs, int srcsOffset, int srcsLength, | 
|  |             ByteBuffer[] dsts, int dstsOffset, int dstsLength) { | 
|  |  | 
|  |         if ((srcs == null) || (dsts == null)) { | 
|  |             throw new IllegalArgumentException( | 
|  |                     "source or destination buffer is null"); | 
|  |         } | 
|  |  | 
|  |         if ((dstsOffset < 0) || (dstsLength < 0) || | 
|  |                 (dstsOffset > dsts.length - dstsLength)) { | 
|  |             throw new IndexOutOfBoundsException( | 
|  |                     "index out of bound of the destination buffers"); | 
|  |         } | 
|  |  | 
|  |         if ((srcsOffset < 0) || (srcsLength < 0) || | 
|  |                 (srcsOffset > srcs.length - srcsLength)) { | 
|  |             throw new IndexOutOfBoundsException( | 
|  |                     "index out of bound of the source buffers"); | 
|  |         } | 
|  |  | 
|  |         for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) { | 
|  |             if (dsts[i] == null) { | 
|  |                 throw new IllegalArgumentException( | 
|  |                         "destination buffer[" + i + "] == null"); | 
|  |             } | 
|  |  | 
|  |              | 
|  |  | 
|  |              */ | 
|  |             if (dsts[i].isReadOnly()) { | 
|  |                 throw new ReadOnlyBufferException(); | 
|  |             } | 
|  |         } | 
|  |  | 
|  |         for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { | 
|  |             if (srcs[i] == null) { | 
|  |                 throw new IllegalArgumentException( | 
|  |                         "source buffer[" + i + "] == null"); | 
|  |             } | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public SSLEngineResult unwrap(ByteBuffer src, | 
|  |             ByteBuffer[] dsts, int offset, int length) throws SSLException { | 
|  |         return unwrap( | 
|  |                 new ByteBuffer[]{src}, 0, 1, dsts, offset, length); | 
|  |     } | 
|  |  | 
|  |      | 
|  |     public SSLEngineResult unwrap( | 
|  |         ByteBuffer[] srcs, int srcsOffset, int srcsLength, | 
|  |         ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws SSLException { | 
|  |  | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             if (conContext.isUnsureMode) { | 
|  |                 throw new IllegalStateException( | 
|  |                         "Client/Server mode has not yet been set."); | 
|  |             } | 
|  |  | 
|  |              | 
|  |             checkTaskThrown(); | 
|  |  | 
|  |              | 
|  |             checkParams(srcs, srcsOffset, srcsLength, | 
|  |                     dsts, dstsOffset, dstsLength); | 
|  |  | 
|  |             try { | 
|  |                 return readRecord( | 
|  |                     srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength); | 
|  |             } catch (SSLProtocolException spe) { | 
|  |                  | 
|  |                 throw conContext.fatal(Alert.UNEXPECTED_MESSAGE, | 
|  |                         spe.getMessage(), spe); | 
|  |             } catch (IOException ioe) { | 
|  |                  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |                  */ | 
|  |                 throw conContext.fatal(Alert.INTERNAL_ERROR, | 
|  |                         "problem unwrapping net record", ioe); | 
|  |             } catch (Exception ex) {      | 
|  |                 throw conContext.fatal(Alert.INTERNAL_ERROR, | 
|  |                     "Fail to unwrap network record", ex); | 
|  |             } | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private SSLEngineResult readRecord( | 
|  |         ByteBuffer[] srcs, int srcsOffset, int srcsLength, | 
|  |         ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { | 
|  |  | 
|  |          | 
|  |  | 
|  |          */ | 
|  |         if (isInboundDone()) { | 
|  |             return new SSLEngineResult( | 
|  |                     Status.CLOSED, conContext.getHandshakeStatus(), 0, 0); | 
|  |         } | 
|  |  | 
|  |         HandshakeStatus hsStatus = null; | 
|  |         if (!conContext.isNegotiated && !conContext.isBroken && | 
|  |                 !conContext.isInboundClosed() && | 
|  |                 !conContext.isOutboundClosed()) { | 
|  |             conContext.kickstart(); | 
|  |  | 
|  |              | 
|  |  | 
|  |  | 
|  |              */ | 
|  |             hsStatus = conContext.getHandshakeStatus(); | 
|  |             if (hsStatus == HandshakeStatus.NEED_WRAP) { | 
|  |                 return new SSLEngineResult(Status.OK, hsStatus, 0, 0); | 
|  |             } | 
|  |         } | 
|  |  | 
|  |         if (hsStatus == null) { | 
|  |             hsStatus = conContext.getHandshakeStatus(); | 
|  |         } | 
|  |  | 
|  |          | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |          */ | 
|  |         if (hsStatus == HandshakeStatus.NEED_TASK) { | 
|  |             return new SSLEngineResult(Status.OK, hsStatus, 0, 0); | 
|  |         } | 
|  |  | 
|  |         if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) { | 
|  |             Plaintext plainText; | 
|  |             try { | 
|  |                 plainText = decode(null, 0, 0, | 
|  |                         dsts, dstsOffset, dstsLength); | 
|  |             } catch (IOException ioe) { | 
|  |                 if (ioe instanceof SSLException) { | 
|  |                     throw ioe; | 
|  |                 } else { | 
|  |                     throw new SSLException("readRecord", ioe); | 
|  |                 } | 
|  |             } | 
|  |  | 
|  |             Status status = (isInboundDone() ? Status.CLOSED : Status.OK); | 
|  |             if (plainText.handshakeStatus != null) { | 
|  |                 hsStatus = plainText.handshakeStatus; | 
|  |             } else { | 
|  |                 hsStatus = conContext.getHandshakeStatus(); | 
|  |             } | 
|  |  | 
|  |             return new SSLEngineResult( | 
|  |                     status, hsStatus, 0, 0, plainText.recordSN); | 
|  |         } | 
|  |  | 
|  |         int srcsRemains = 0; | 
|  |         for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { | 
|  |             srcsRemains += srcs[i].remaining(); | 
|  |         } | 
|  |  | 
|  |         if (srcsRemains == 0) { | 
|  |             return new SSLEngineResult( | 
|  |                 Status.BUFFER_UNDERFLOW, hsStatus, 0, 0); | 
|  |         } | 
|  |  | 
|  |          | 
|  |  | 
|  |  | 
|  |          */ | 
|  |         int packetLen; | 
|  |         try { | 
|  |             packetLen = conContext.inputRecord.bytesInCompletePacket( | 
|  |                     srcs, srcsOffset, srcsLength); | 
|  |         } catch (SSLException ssle) { | 
|  |              | 
|  |             if (sslContext.isDTLS()) { | 
|  |                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { | 
|  |                     SSLLogger.finest("Discard invalid DTLS records", ssle); | 
|  |                 } | 
|  |  | 
|  |                  | 
|  |                 for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { | 
|  |                     srcs[i].position(srcs[i].limit()); | 
|  |                 } | 
|  |  | 
|  |                 Status status = (isInboundDone() ? Status.CLOSED : Status.OK); | 
|  |                 if (hsStatus == null) { | 
|  |                     hsStatus = conContext.getHandshakeStatus(); | 
|  |                 } | 
|  |  | 
|  |                 return new SSLEngineResult(status, hsStatus, srcsRemains, 0, -1L); | 
|  |             } else { | 
|  |                 throw ssle; | 
|  |             } | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (packetLen > conContext.conSession.getPacketBufferSize()) { | 
|  |             int largestRecordSize = sslContext.isDTLS() ? | 
|  |                     DTLSRecord.maxRecordSize : SSLRecord.maxLargeRecordSize; | 
|  |             if ((packetLen <= largestRecordSize) && !sslContext.isDTLS()) { | 
|  |                 // Expand the expected maximum packet/application buffer | 
|  |                 // sizes. | 
|  |                 // | 
|  |                 // Only apply to SSL/TLS protocols. | 
|  |  | 
|  |                 // Old behavior: shall we honor the System Property | 
|  |                  | 
|  |                 conContext.conSession.expandBufferSizes(); | 
|  |             } | 
|  |  | 
|  |              | 
|  |             largestRecordSize = conContext.conSession.getPacketBufferSize(); | 
|  |             if (packetLen > largestRecordSize) { | 
|  |                 throw new SSLProtocolException( | 
|  |                         "Input record too big: max = " + | 
|  |                         largestRecordSize + " len = " + packetLen); | 
|  |             } | 
|  |         } | 
|  |  | 
|  |          | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |          */ | 
|  |         int dstsRemains = 0; | 
|  |         for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) { | 
|  |             dstsRemains += dsts[i].remaining(); | 
|  |         } | 
|  |  | 
|  |         if (conContext.isNegotiated) { | 
|  |             int FragLen = | 
|  |                     conContext.inputRecord.estimateFragmentSize(packetLen); | 
|  |             if (FragLen > dstsRemains) { | 
|  |                 return new SSLEngineResult( | 
|  |                         Status.BUFFER_OVERFLOW, hsStatus, 0, 0); | 
|  |             } | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if ((packetLen == -1) || (srcsRemains < packetLen)) { | 
|  |             return new SSLEngineResult(Status.BUFFER_UNDERFLOW, hsStatus, 0, 0); | 
|  |         } | 
|  |  | 
|  |          | 
|  |  | 
|  |          */ | 
|  |         Plaintext plainText; | 
|  |         try { | 
|  |             plainText = decode(srcs, srcsOffset, srcsLength, | 
|  |                             dsts, dstsOffset, dstsLength); | 
|  |         } catch (IOException ioe) { | 
|  |             if (ioe instanceof SSLException) { | 
|  |                 throw ioe; | 
|  |             } else { | 
|  |                 throw new SSLException("readRecord", ioe); | 
|  |             } | 
|  |         } | 
|  |  | 
|  |          | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |          */ | 
|  |         Status status = (isInboundDone() ? Status.CLOSED : Status.OK); | 
|  |         if (plainText.handshakeStatus != null) { | 
|  |             hsStatus = plainText.handshakeStatus; | 
|  |         } else { | 
|  |             hsStatus = conContext.getHandshakeStatus(); | 
|  |         } | 
|  |  | 
|  |         int deltaNet = srcsRemains; | 
|  |         for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { | 
|  |             deltaNet -= srcs[i].remaining(); | 
|  |         } | 
|  |  | 
|  |         int deltaApp = dstsRemains; | 
|  |         for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) { | 
|  |             deltaApp -= dsts[i].remaining(); | 
|  |         } | 
|  |  | 
|  |         return new SSLEngineResult( | 
|  |                 status, hsStatus, deltaNet, deltaApp, plainText.recordSN); | 
|  |     } | 
|  |  | 
|  |     private Plaintext decode( | 
|  |         ByteBuffer[] srcs, int srcsOffset, int srcsLength, | 
|  |         ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { | 
|  |  | 
|  |         Plaintext pt = SSLTransport.decode(conContext, | 
|  |                             srcs, srcsOffset, srcsLength, | 
|  |                             dsts, dstsOffset, dstsLength); | 
|  |  | 
|  |          | 
|  |         if (pt != Plaintext.PLAINTEXT_NULL) { | 
|  |             HandshakeStatus hsStatus = tryToFinishHandshake(pt.contentType); | 
|  |             if (hsStatus == null) { | 
|  |                 pt.handshakeStatus = conContext.getHandshakeStatus(); | 
|  |             } else { | 
|  |                 pt.handshakeStatus = hsStatus; | 
|  |             } | 
|  |  | 
|  |              | 
|  |             if (conContext.inputRecord.seqNumIsHuge() || | 
|  |                     conContext.inputRecord.readCipher.atKeyLimit()) { | 
|  |                 pt.handshakeStatus = | 
|  |                         tryKeyUpdate(pt.handshakeStatus); | 
|  |             } | 
|  |         } | 
|  |  | 
|  |         return pt; | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public Runnable getDelegatedTask() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             if (conContext.handshakeContext != null &&  | 
|  |                     !conContext.handshakeContext.taskDelegated && | 
|  |                     !conContext.handshakeContext.delegatedActions.isEmpty()) { | 
|  |                 conContext.handshakeContext.taskDelegated = true; | 
|  |                 return new DelegatedTask(this); | 
|  |             } | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |  | 
|  |         return null; | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void closeInbound() throws SSLException { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             if (isInboundDone()) { | 
|  |                 return; | 
|  |             } | 
|  |  | 
|  |             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | 
|  |                 SSLLogger.finest("Closing inbound of SSLEngine"); | 
|  |             } | 
|  |  | 
|  |             // Is it ready to close inbound? | 
|  |             // | 
|  |              | 
|  |             if (!conContext.isInputCloseNotified && | 
|  |                 (conContext.isNegotiated || | 
|  |                     conContext.handshakeContext != null)) { | 
|  |  | 
|  |                 throw conContext.fatal(Alert.INTERNAL_ERROR, | 
|  |                         "closing inbound before receiving peer's close_notify"); | 
|  |             } | 
|  |  | 
|  |             conContext.closeInbound(); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public boolean isInboundDone() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.isInboundClosed(); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void closeOutbound() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             if (conContext.isOutboundClosed()) { | 
|  |                 return; | 
|  |             } | 
|  |  | 
|  |             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | 
|  |                 SSLLogger.finest("Closing outbound of SSLEngine"); | 
|  |             } | 
|  |  | 
|  |             conContext.closeOutbound(); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public boolean isOutboundDone() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.isOutboundDone(); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public String[] getSupportedCipherSuites() { | 
|  |         return CipherSuite.namesOf(sslContext.getSupportedCipherSuites()); | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public String[] getEnabledCipherSuites() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return CipherSuite.namesOf(conContext.sslConfig.enabledCipherSuites); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void setEnabledCipherSuites(String[] suites) { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             conContext.sslConfig.enabledCipherSuites = | 
|  |                     CipherSuite.validValuesOf(suites); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public String[] getSupportedProtocols() { | 
|  |         return ProtocolVersion.toStringArray( | 
|  |                 sslContext.getSupportedProtocolVersions()); | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public String[] getEnabledProtocols() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return ProtocolVersion.toStringArray( | 
|  |                     conContext.sslConfig.enabledProtocols); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void setEnabledProtocols(String[] protocols) { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             if (protocols == null) { | 
|  |                 throw new IllegalArgumentException("Protocols cannot be null"); | 
|  |             } | 
|  |  | 
|  |             conContext.sslConfig.enabledProtocols = | 
|  |                     ProtocolVersion.namesOf(protocols); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public SSLSession getSession() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.conSession; | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public SSLSession getHandshakeSession() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.handshakeContext == null ? | 
|  |                     null : conContext.handshakeContext.handshakeSession; | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public SSLEngineResult.HandshakeStatus getHandshakeStatus() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.getHandshakeStatus(); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void setUseClientMode(boolean mode) { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             conContext.setUseClientMode(mode); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public boolean getUseClientMode() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.sslConfig.isClientMode; | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void setNeedClientAuth(boolean need) { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             conContext.sslConfig.clientAuthType = | 
|  |                     (need ? ClientAuthType.CLIENT_AUTH_REQUIRED : | 
|  |                             ClientAuthType.CLIENT_AUTH_NONE); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public boolean getNeedClientAuth() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return (conContext.sslConfig.clientAuthType == | 
|  |                             ClientAuthType.CLIENT_AUTH_REQUIRED); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void setWantClientAuth(boolean want) { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             conContext.sslConfig.clientAuthType = | 
|  |                     (want ? ClientAuthType.CLIENT_AUTH_REQUESTED : | 
|  |                             ClientAuthType.CLIENT_AUTH_NONE); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public boolean getWantClientAuth() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return (conContext.sslConfig.clientAuthType == | 
|  |                             ClientAuthType.CLIENT_AUTH_REQUESTED); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void setEnableSessionCreation(boolean flag) { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             conContext.sslConfig.enableSessionCreation = flag; | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public boolean getEnableSessionCreation() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.sslConfig.enableSessionCreation; | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public SSLParameters getSSLParameters() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.sslConfig.getSSLParameters(); | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |    } | 
|  |  | 
|  |     @Override | 
|  |     public void setSSLParameters(SSLParameters params) { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             conContext.sslConfig.setSSLParameters(params); | 
|  |  | 
|  |             if (conContext.sslConfig.maximumPacketSize != 0) { | 
|  |                 conContext.outputRecord.changePacketSize( | 
|  |                         conContext.sslConfig.maximumPacketSize); | 
|  |             } | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |    } | 
|  |  | 
|  |     @Override | 
|  |     public String getApplicationProtocol() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.applicationProtocol; | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public String getHandshakeApplicationProtocol() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.handshakeContext == null ? | 
|  |                     null : conContext.handshakeContext.applicationProtocol; | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void setHandshakeApplicationProtocolSelector( | 
|  |             BiFunction<SSLEngine, List<String>, String> selector) { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             conContext.sslConfig.engineAPSelector = selector; | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public BiFunction<SSLEngine, List<String>, String> | 
|  |             getHandshakeApplicationProtocolSelector() { | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |             return conContext.sslConfig.engineAPSelector; | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public boolean useDelegatedTask() { | 
|  |         return true; | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public String toString() { | 
|  |         return "SSLEngine[" + | 
|  |                 "hostname=" + getPeerHost() + | 
|  |                 ", port=" + getPeerPort() + | 
|  |                 ", " + conContext.conSession +   | 
|  |                 "]"; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private void checkTaskThrown() throws SSLException { | 
|  |  | 
|  |         Exception exc = null; | 
|  |         engineLock.lock(); | 
|  |         try { | 
|  |              | 
|  |             HandshakeContext hc = conContext.handshakeContext; | 
|  |             if ((hc != null) && (hc.delegatedThrown != null)) { | 
|  |                 exc = hc.delegatedThrown; | 
|  |                 hc.delegatedThrown = null; | 
|  |             } | 
|  |  | 
|  |              | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |              */ | 
|  |             if (conContext.delegatedThrown != null) { | 
|  |                 if (exc != null) { | 
|  |                      | 
|  |                     if (conContext.delegatedThrown == exc) { | 
|  |                          | 
|  |                         conContext.delegatedThrown = null; | 
|  |                     } // otherwise report the hc delegatedThrown | 
|  |                 } else { | 
|  |                     // Nothing waiting in HandshakeContext, but one is in the | 
|  |                      | 
|  |                     exc = conContext.delegatedThrown; | 
|  |                     conContext.delegatedThrown = null; | 
|  |                 } | 
|  |             } | 
|  |         } finally { | 
|  |             engineLock.unlock(); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (exc == null) { | 
|  |             return; | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (exc instanceof SSLException) { | 
|  |             throw (SSLException)exc; | 
|  |         } else if (exc instanceof RuntimeException) { | 
|  |             throw (RuntimeException)exc; | 
|  |         } else { | 
|  |             throw getTaskThrown(exc); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private static SSLException getTaskThrown(Exception taskThrown) { | 
|  |         String msg = taskThrown.getMessage(); | 
|  |  | 
|  |         if (msg == null) { | 
|  |             msg = "Delegated task threw Exception or Error"; | 
|  |         } | 
|  |  | 
|  |         if (taskThrown instanceof RuntimeException) { | 
|  |             throw new RuntimeException(msg, taskThrown); | 
|  |         } else if (taskThrown instanceof SSLHandshakeException) { | 
|  |             return (SSLHandshakeException) | 
|  |                 new SSLHandshakeException(msg).initCause(taskThrown); | 
|  |         } else if (taskThrown instanceof SSLKeyException) { | 
|  |             return (SSLKeyException) | 
|  |                 new SSLKeyException(msg).initCause(taskThrown); | 
|  |         } else if (taskThrown instanceof SSLPeerUnverifiedException) { | 
|  |             return (SSLPeerUnverifiedException) | 
|  |                 new SSLPeerUnverifiedException(msg).initCause(taskThrown); | 
|  |         } else if (taskThrown instanceof SSLProtocolException) { | 
|  |             return (SSLProtocolException) | 
|  |                 new SSLProtocolException(msg).initCause(taskThrown); | 
|  |         } else if (taskThrown instanceof SSLException) { | 
|  |             return (SSLException)taskThrown; | 
|  |         } else { | 
|  |             return new SSLException(msg, taskThrown); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private static class DelegatedTask implements Runnable { | 
|  |         private final SSLEngineImpl engine; | 
|  |  | 
|  |         DelegatedTask(SSLEngineImpl engineInstance) { | 
|  |             this.engine = engineInstance; | 
|  |         } | 
|  |  | 
|  |         @Override | 
|  |         public void run() { | 
|  |             engine.engineLock.lock(); | 
|  |             try { | 
|  |                 HandshakeContext hc = engine.conContext.handshakeContext; | 
|  |                 if (hc == null || hc.delegatedActions.isEmpty()) { | 
|  |                     return; | 
|  |                 } | 
|  |  | 
|  |                 try { | 
|  |                     @SuppressWarnings("removal") | 
|  |                     var dummy = AccessController.doPrivileged( | 
|  |                             new DelegatedAction(hc), engine.conContext.acc); | 
|  |                 } catch (PrivilegedActionException pae) { | 
|  |                     // Get the handshake context again in case the | 
|  |                      | 
|  |                     Exception reportedException = pae.getException(); | 
|  |  | 
|  |                      | 
|  |                     if (engine.conContext.delegatedThrown == null) { | 
|  |                         engine.conContext.delegatedThrown = reportedException; | 
|  |                     } | 
|  |  | 
|  |                     // ...and the HandshakeContext in case condition | 
|  |                     // wasn't fatal and the handshakeContext is still | 
|  |                      | 
|  |                     hc = engine.conContext.handshakeContext; | 
|  |                     if (hc != null) { | 
|  |                         hc.delegatedThrown = reportedException; | 
|  |                     } else if (engine.conContext.closeReason != null) { | 
|  |                          | 
|  |                         engine.conContext.closeReason = | 
|  |                                 getTaskThrown(reportedException); | 
|  |                     } | 
|  |                 } catch (RuntimeException rte) { | 
|  |                     // Get the handshake context again in case the | 
|  |                     // handshaking has completed. | 
|  |  | 
|  |                      | 
|  |                     if (engine.conContext.delegatedThrown == null) { | 
|  |                         engine.conContext.delegatedThrown = rte; | 
|  |                     } | 
|  |  | 
|  |                     // ...and the HandshakeContext in case condition | 
|  |                     // wasn't fatal and the handshakeContext is still | 
|  |                      | 
|  |                     hc = engine.conContext.handshakeContext; | 
|  |                     if (hc != null) { | 
|  |                         hc.delegatedThrown = rte; | 
|  |                     } else if (engine.conContext.closeReason != null) { | 
|  |                          | 
|  |                         engine.conContext.closeReason = rte; | 
|  |                     } | 
|  |                 } | 
|  |  | 
|  |                 // Get the handshake context again in case the | 
|  |                  | 
|  |                 hc = engine.conContext.handshakeContext; | 
|  |                 if (hc != null) { | 
|  |                     hc.taskDelegated = false; | 
|  |                 } | 
|  |             } finally { | 
|  |                 engine.engineLock.unlock(); | 
|  |             } | 
|  |         } | 
|  |  | 
|  |         private static class DelegatedAction | 
|  |                 implements PrivilegedExceptionAction<Void> { | 
|  |             final HandshakeContext context; | 
|  |             DelegatedAction(HandshakeContext context) { | 
|  |                 this.context = context; | 
|  |             } | 
|  |  | 
|  |             @Override | 
|  |             public Void run() throws Exception { | 
|  |                 while (!context.delegatedActions.isEmpty()) { | 
|  |                     Map.Entry<Byte, ByteBuffer> me = | 
|  |                             context.delegatedActions.poll(); | 
|  |                     if (me != null) { | 
|  |                         context.dispatch(me.getKey(), me.getValue()); | 
|  |                     } | 
|  |                 } | 
|  |                 return null; | 
|  |             } | 
|  |         } | 
|  |     } | 
|  | } |