From 1dfff197eadcf24823d7915e6eead2a850f679f9 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Tue, 14 Feb 2012 16:09:28 +0000
Subject: [PATCH] Fix OPENDJ-420: Rare SSLExceptions while handling LDAPS connections and big LDAP searches
---
opends/src/server/org/opends/server/extensions/TLSByteChannel.java | 737 ++++++++++++++++++++++++++++++--------------------------
1 files changed, 393 insertions(+), 344 deletions(-)
diff --git a/opends/src/server/org/opends/server/extensions/TLSByteChannel.java b/opends/src/server/org/opends/server/extensions/TLSByteChannel.java
index ad2b96e..9ddb118 100644
--- a/opends/src/server/org/opends/server/extensions/TLSByteChannel.java
+++ b/opends/src/server/org/opends/server/extensions/TLSByteChannel.java
@@ -42,6 +42,7 @@
import java.util.Set;
import javax.net.ssl.*;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import org.opends.server.admin.std.server.LDAPConnectionHandlerCfg;
import org.opends.server.api.ClientConnection;
@@ -53,50 +54,375 @@
/**
* A class that provides a TLS byte channel implementation.
*/
-public class TLSByteChannel implements ByteChannel, ConnectionSecurityProvider
+public final class TLSByteChannel implements ConnectionSecurityProvider
{
- private static final DebugTracer TRACER = getTracer();
+ /**
+ * Private implementation.
+ */
+ private final class ByteChannelImpl implements ByteChannel
+ {
- private final ByteChannel socketChannel;
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ synchronized (readLock)
+ {
+ synchronized (writeLock)
+ {
+ final boolean isInitiator = !sslEngine.isInboundDone();
- private final SSLEngine sslEngine;
+ try
+ {
+ if (!sslEngine.isOutboundDone())
+ {
+ sslEngine.closeOutbound();
+ while (doWrapAndSend(EMPTY_BUFFER) > 0)
+ {
+ // Write out any remaining SSL close notifications.
+ }
+ }
+ }
+ catch (final ClosedChannelException e)
+ {
+ // Ignore this so that close is idempotent.
+ }
+ finally
+ {
+ try
+ {
+ sslEngine.closeInbound();
+ }
+ catch (final SSLException e)
+ {
+ // Not yet received peer's close notification. Ignore this if we
+ // are the initiator.
+ if (!isInitiator)
+ {
+ throw e;
+ }
+ }
+ finally
+ {
+ channel.close();
+ }
+ }
+ }
+ }
+ }
- // read copy to buffer
- private final ByteBuffer appData;
- // read encrypted
- private final ByteBuffer appNetData;
- // Write encrypted
- private final ByteBuffer netData;
- private final ByteBuffer tempData;
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isOpen()
+ {
+ return !sslEngine.isOutboundDone() || !sslEngine.isInboundDone();
+ }
- private final int sslBufferSize;
- private final int appBufSize;
- private boolean reading = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ public int read(final ByteBuffer unwrappedData) throws IOException
+ {
+ synchronized (readLock)
+ {
+ // Repeat until there is some unwrapped data available or all available
+ // data has been read from the underlying socket.
+ if (!recvUnwrappedBuffer.hasRemaining())
+ {
+ final int read = doRecvAndUnwrap();
+ if (read <= 0)
+ {
+ // No data read or end of stream.
+ return read;
+ }
+ }
+
+ // Copy available data.
+ final int startPos = unwrappedData.position();
+ if (recvUnwrappedBuffer.remaining() > unwrappedData.remaining())
+ {
+ // Unwrapped data does not fit in client buffer so copy one by at a
+ // time: it's annoying that there is no easy way to do this with
+ // ByteBuffers.
+ while (unwrappedData.hasRemaining())
+ {
+ unwrappedData.put(recvUnwrappedBuffer.get());
+ }
+ }
+ else
+ {
+ // Unwrapped data fits client buffer so block copy.
+ unwrappedData.put(recvUnwrappedBuffer);
+ }
+ return unwrappedData.position() - startPos;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int write(final ByteBuffer unwrappedData) throws IOException
+ {
+ // This method will block until the entire message is sent.
+ final int bytesWritten = unwrappedData.remaining();
+
+ // Synchronized in order to prevent interleaving and reordering.
+ synchronized (writeLock)
+ {
+ // Repeat until the entire input data is written.
+ while (unwrappedData.hasRemaining())
+ {
+ // Wrap and send the data.
+ doWrapAndSend(unwrappedData);
+
+ // Perform handshake if needed.
+ if (isHandshaking(sslEngine.getHandshakeStatus()))
+ {
+ doHandshake(false /* isReading */);
+ }
+ }
+ }
+
+ return bytesWritten;
+ }
+
+
+
+ private void doHandshake(final boolean isReading) throws IOException
+ {
+ // This lock is probably unnecessary since tasks can be run in parallel,
+ // but it adds no additional overhead so there's little harm in having
+ // it.
+ synchronized (handshakeLock)
+ {
+ while (true)
+ {
+ switch (sslEngine.getHandshakeStatus())
+ {
+ case NEED_TASK:
+ Runnable runnable;
+ while ((runnable = sslEngine.getDelegatedTask()) != null)
+ {
+ runnable.run();
+ }
+ break;
+ case NEED_UNWRAP:
+ // Block for writes, but be non-blocking for reads.
+ if (isReading)
+ {
+ // Let doRecvAndUnwrap() deal with this.
+ return;
+ }
+
+ // Need to do an unwrap (read) while writing.
+ if (doRecvAndUnwrap() < 0)
+ {
+ throw new ClosedChannelException();
+ }
+ break;
+ case NEED_WRAP:
+ doWrapAndSend(EMPTY_BUFFER);
+ break;
+ default: // NOT_HANDSHAKING, FINISHED.
+ return;
+ }
+ }
+ }
+ }
+
+
+
+ // Attempt to read and unwrap the next SSL packet.
+ private int doRecvAndUnwrap() throws IOException
+ {
+ // Synchronize SSL unwrap with channel reads.
+ synchronized (unwrapLock)
+ {
+ // Repeat if there is underflow or overflow.
+ boolean needRead = true;
+ while (true)
+ {
+ // Read wrapped data if needed.
+ if (needRead)
+ {
+ recvWrappedBuffer.compact(); // Prepare for append.
+ final int read = channel.read(recvWrappedBuffer);
+ recvWrappedBuffer.flip(); // Restore for read.
+ if (read < 0)
+ {
+ // Peer abort?
+ sslEngine.closeInbound();
+ return -1;
+ }
+ }
+ else
+ {
+ needRead = true;
+ }
+
+ // Unwrap.
+ recvUnwrappedBuffer.compact(); // Prepare for append.
+ final SSLEngineResult result = sslEngine.unwrap(recvWrappedBuffer,
+ recvUnwrappedBuffer);
+ recvUnwrappedBuffer.flip(); // Restore for read.
+
+ switch (result.getStatus())
+ {
+ case BUFFER_OVERFLOW:
+ // The unwrapped buffer is not big enough: resize and repeat.
+ final int newAppSize = sslEngine.getSession()
+ .getApplicationBufferSize();
+ final ByteBuffer newRecvUnwrappedBuffer = ByteBuffer
+ .allocate(recvUnwrappedBuffer.limit() + newAppSize);
+ newRecvUnwrappedBuffer.put(recvUnwrappedBuffer);
+ newRecvUnwrappedBuffer.flip();
+ recvUnwrappedBuffer = newRecvUnwrappedBuffer;
+ needRead = false;
+ break; // Retry unwrap.
+ case BUFFER_UNDERFLOW:
+ // Not enough data was read. This either means that the inbound
+ // buffer was too small, or not enough data is available.
+ final int newPktSize = sslEngine.getSession().getPacketBufferSize();
+ if (newPktSize > recvWrappedBuffer.capacity())
+ {
+ // Buffer needs resizing.
+ final ByteBuffer newRecvWrappedBuffer = ByteBuffer
+ .allocate(newPktSize);
+ newRecvWrappedBuffer.put(recvWrappedBuffer);
+ newRecvWrappedBuffer.flip();
+ recvWrappedBuffer = newRecvWrappedBuffer;
+ break;
+ }
+ else
+ {
+ // Not enough data is available to read a complete SSL packet.
+ return 0;
+ }
+ case CLOSED:
+ // Peer sent SSL close notification.
+ sslEngine.closeInbound();
+ return -1;
+ default: // OK
+ if (recvUnwrappedBuffer.hasRemaining())
+ {
+ // Some application data was read so return it.
+ return recvUnwrappedBuffer.remaining();
+ }
+ else if (isHandshaking(result.getHandshakeStatus()))
+ {
+ // No application data was read, but if we are handshaking then
+ // try to continue.
+ if (result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP
+ && !recvWrappedBuffer.hasRemaining())
+ {
+ // Not enough data is available to continue handshake.
+ return 0;
+ }
+ else
+ {
+ // Continue handshake.
+ doHandshake(true /* isReading */);
+ }
+ }
+ else
+ {
+ // No data available and not handshaking.
+ return 0;
+ }
+ }
+ }
+ }
+ }
+
+
+
+ // Attempt to wrap and send the next SSL packet.
+ private int doWrapAndSend(final ByteBuffer unwrappedData)
+ throws IOException
+ {
+ // Synchronize SSL wrap with channel writes.
+ synchronized (wrapLock)
+ {
+ // Repeat while there is overflow.
+ while (true)
+ {
+ final SSLEngineResult result = sslEngine.wrap(unwrappedData,
+ sendWrappedBuffer);
+ switch (result.getStatus())
+ {
+ case BUFFER_OVERFLOW:
+ // The wrapped buffer is not big enough: resize and repeat.
+ final int newSize = sslEngine.getSession().getPacketBufferSize();
+ final ByteBuffer newSendWrappedBuffer = ByteBuffer
+ .allocate(sendWrappedBuffer.position() + newSize);
+ sendWrappedBuffer.flip();
+ newSendWrappedBuffer.put(sendWrappedBuffer);
+ sendWrappedBuffer = newSendWrappedBuffer;
+ break; // Retry.
+ case BUFFER_UNDERFLOW:
+ // This should not happen for sends.
+ throw new SSLException("Got unexpected underflow while wrapping");
+ case CLOSED:
+ throw new ClosedChannelException();
+ default: // OK
+ // Write the SSL packet: our IO stack will block until all the
+ // data is written.
+ sendWrappedBuffer.flip();
+ while (sendWrappedBuffer.hasRemaining())
+ {
+ channel.write(sendWrappedBuffer);
+ }
+ final int written = sendWrappedBuffer.position();
+ sendWrappedBuffer.clear();
+ return written;
+ }
+ }
+ }
+ }
+
+
+
+ private boolean isHandshaking(final HandshakeStatus status)
+ {
+ return status != HandshakeStatus.NOT_HANDSHAKING;
+ }
+
+ }
+
+
// Map of cipher phrases to effective key size (bits). Taken from the
// following RFCs: 5289, 4346, 3268,4132 and 4162.
- private static final Map<String, Integer> cipherMap;
+ private static final Map<String, Integer> CIPHER_MAP;
static
{
- cipherMap = new LinkedHashMap<String, Integer>();
- cipherMap.put("_WITH_AES_256_CBC_", new Integer(256));
- cipherMap.put("_WITH_CAMELLIA_256_CBC_", new Integer(256));
- cipherMap.put("_WITH_AES_256_GCM_", new Integer(256));
- cipherMap.put("_WITH_3DES_EDE_CBC_", new Integer(112));
- cipherMap.put("_WITH_AES_128_GCM_", new Integer(128));
- cipherMap.put("_WITH_SEED_CBC_", new Integer(128));
- cipherMap.put("_WITH_CAMELLIA_128_CBC_", new Integer(128));
- cipherMap.put("_WITH_AES_128_CBC_", new Integer(128));
- cipherMap.put("_WITH_IDEA_CBC_", new Integer(128));
- cipherMap.put("_WITH_DES_CBC_", new Integer(56));
- cipherMap.put("_WITH_RC2_CBC_40_", new Integer(40));
- cipherMap.put("_WITH_RC4_40_", new Integer(40));
- cipherMap.put("_WITH_DES40_CBC_", new Integer(40));
- cipherMap.put("_WITH_NULL_", new Integer(0));
- };
+ CIPHER_MAP = new LinkedHashMap<String, Integer>();
+ CIPHER_MAP.put("_WITH_AES_256_CBC_", new Integer(256));
+ CIPHER_MAP.put("_WITH_CAMELLIA_256_CBC_", new Integer(256));
+ CIPHER_MAP.put("_WITH_AES_256_GCM_", new Integer(256));
+ CIPHER_MAP.put("_WITH_3DES_EDE_CBC_", new Integer(112));
+ CIPHER_MAP.put("_WITH_AES_128_GCM_", new Integer(128));
+ CIPHER_MAP.put("_WITH_SEED_CBC_", new Integer(128));
+ CIPHER_MAP.put("_WITH_CAMELLIA_128_CBC_", new Integer(128));
+ CIPHER_MAP.put("_WITH_AES_128_CBC_", new Integer(128));
+ CIPHER_MAP.put("_WITH_IDEA_CBC_", new Integer(128));
+ CIPHER_MAP.put("_WITH_DES_CBC_", new Integer(56));
+ CIPHER_MAP.put("_WITH_RC2_CBC_40_", new Integer(40));
+ CIPHER_MAP.put("_WITH_RC4_40_", new Integer(40));
+ CIPHER_MAP.put("_WITH_DES40_CBC_", new Integer(40));
+ CIPHER_MAP.put("_WITH_NULL_", new Integer(0));
+ }
+
+ private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
+ private static final DebugTracer TRACER = getTracer();
@@ -124,12 +450,28 @@
+ private final ByteChannelImpl pimpl = new ByteChannelImpl();
+ private final ByteChannel channel;
+ private final SSLEngine sslEngine;
+
+ private ByteBuffer recvWrappedBuffer;
+ private ByteBuffer recvUnwrappedBuffer;
+ private ByteBuffer sendWrappedBuffer;
+
+ private final Object handshakeLock = new Object();
+ private final Object unwrapLock = new Object();
+ private final Object wrapLock = new Object();
+ private final Object readLock = new Object();
+ private final Object writeLock = new Object();
+
+
+
private TLSByteChannel(final LDAPConnectionHandlerCfg config,
- final ClientConnection c, final ByteChannel socketChannel,
+ final ClientConnection c, final ByteChannel channel,
final SSLContext sslContext)
{
- this.socketChannel = socketChannel;
+ this.channel = channel;
// getHostName could potentially be very expensive and could block
// the connection handler for several minutes. (See issue 4229)
@@ -137,11 +479,14 @@
// avoid blocking new connections. Just remove for now to prevent
// potential DoS attacks. SSL sessions will not be reused and some
// cipher suites (such as Kerberos) will not work.
+
// String hostName = socketChannel.socket().getInetAddress().getHostName();
// int port = socketChannel.socket().getPort();
// sslEngine = sslContext.createSSLEngine(hostName, port);
+
sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(false);
+
final Set<String> protocols = config.getSSLProtocol();
if (!protocols.isEmpty())
{
@@ -171,15 +516,18 @@
break;
}
- final SSLSession sslSession = sslEngine.getSession();
- sslBufferSize = sslSession.getPacketBufferSize();
- appBufSize = sslSession.getApplicationBufferSize();
+ // Allocate read/write buffers.
+ final SSLSession session = sslEngine.getSession();
+ final int wrappedBufferSize = session.getPacketBufferSize();
+ final int unwrappedBufferSize = session.getApplicationBufferSize();
- appNetData = ByteBuffer.allocate(sslBufferSize);
- netData = ByteBuffer.allocate(sslBufferSize);
+ sendWrappedBuffer = ByteBuffer.allocate(wrappedBufferSize);
+ recvWrappedBuffer = ByteBuffer.allocate(wrappedBufferSize);
+ recvUnwrappedBuffer = ByteBuffer.allocate(unwrappedBufferSize);
- appData = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
- tempData = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
+ // Initially nothing has been received.
+ recvWrappedBuffer.flip();
+ recvUnwrappedBuffer.flip();
}
@@ -187,27 +535,9 @@
/**
* {@inheritDoc}
*/
- public synchronized void close() throws IOException
+ public ByteChannel getChannel()
{
- sslEngine.closeInbound();
- sslEngine.closeOutbound();
- final SSLEngineResult.HandshakeStatus hsStatus = sslEngine
- .getHandshakeStatus();
- if (hsStatus != SSLEngineResult.HandshakeStatus.FINISHED
- && hsStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
- {
- doHandshakeWrite(hsStatus);
- }
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- public int getAppBufSize()
- {
- return appBufSize;
+ return pimpl;
}
@@ -248,31 +578,15 @@
*/
public int getSSF()
{
- int cipherKeySSF = 0;
final String cipherString = sslEngine.getSession().getCipherSuite();
- for (final Map.Entry<String, Integer> mapEntry : cipherMap.entrySet())
+ for (final Map.Entry<String, Integer> mapEntry : CIPHER_MAP.entrySet())
{
if (cipherString.indexOf(mapEntry.getKey()) >= 0)
{
- cipherKeySSF = mapEntry.getValue().intValue();
- break;
+ return mapEntry.getValue().intValue();
}
}
- return cipherKeySSF;
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- public boolean isOpen()
- {
- if (sslEngine.isInboundDone() || sslEngine.isOutboundDone())
- {
- return false;
- }
- return true;
+ return 0;
}
@@ -285,269 +599,4 @@
return true;
}
-
-
- /**
- * {@inheritDoc}
- */
- public synchronized int read(final ByteBuffer clearBuffer) throws IOException
- {
- SSLEngineResult.HandshakeStatus hsStatus;
- if (!reading)
- {
- appNetData.clear();
- }
- else
- {
- reading = false;
- }
-
- if (!socketChannel.isOpen())
- {
- return -1;
- }
-
- if (sslEngine.isInboundDone())
- {
- return -1;
- }
-
- do
- {
- final int wrappedBytes = socketChannel.read(appNetData);
- appNetData.flip();
-
- if (wrappedBytes == -1)
- {
- return -1;
- }
-
- hsStatus = sslEngine.getHandshakeStatus();
-
- if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
- || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP)
- {
- doHandshakeRead(hsStatus);
- }
-
- if (wrappedBytes == 0)
- {
- return 0;
- }
-
- while (appNetData.hasRemaining())
- {
- appData.clear();
- final SSLEngineResult res = sslEngine.unwrap(appNetData, appData);
- appData.flip();
-
- if (res.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW)
- {
- appNetData.compact();
- reading = true;
- break;
- }
- else if (res.getStatus() != SSLEngineResult.Status.OK)
- {
- return -1;
- }
-
- hsStatus = sslEngine.getHandshakeStatus();
- if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
- || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP)
- {
- doHandshakeOp(hsStatus);
- }
- clearBuffer.put(appData);
- }
- hsStatus = sslEngine.getHandshakeStatus();
- }
- while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
- || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP);
-
- return clearBuffer.position();
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- public ByteChannel wrapChannel(final ByteChannel channel)
- {
- return this;
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- public synchronized int write(final ByteBuffer clearData) throws IOException
- {
- if (!socketChannel.isOpen() || sslEngine.isOutboundDone())
- {
- throw new ClosedChannelException();
- }
-
- final int originalPosition = clearData.position();
- final int originalLimit = clearData.limit();
- final int length = originalLimit - originalPosition;
-
- if (length > sslBufferSize)
- {
- int pos = originalPosition;
- int lim = originalPosition + sslBufferSize;
- while (pos < originalLimit)
- {
- clearData.position(pos);
- clearData.limit(lim);
- writeInternal(clearData);
- pos = lim;
- lim = Math.min(originalLimit, pos + sslBufferSize);
- }
- return length;
- }
- else
- {
- return writeInternal(clearData);
- }
- }
-
-
-
- private void doHandshakeOp(SSLEngineResult.HandshakeStatus hsStatus)
- throws IOException
- {
- SSLEngineResult res;
- switch (hsStatus)
- {
- case NEED_TASK:
- hsStatus = doTasks();
- break;
- case NEED_WRAP:
- tempData.clear();
- netData.clear();
- res = sslEngine.wrap(tempData, netData);
- hsStatus = res.getHandshakeStatus();
- netData.flip();
- while (netData.hasRemaining())
- {
- socketChannel.write(netData);
- }
- hsStatus = sslEngine.getHandshakeStatus();
- return;
- default:
- return;
- }
- }
-
-
-
- private void doHandshakeRead(SSLEngineResult.HandshakeStatus hsStatus)
- throws IOException
- {
- do
- {
- doHandshakeOp(hsStatus);
- hsStatus = sslEngine.getHandshakeStatus();
- }
- while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
- || hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK);
- }
-
-
-
- private void doHandshakeUnwrap() throws IOException
- {
- netData.clear();
- tempData.clear();
- final int bytesRead = socketChannel.read(netData);
-
- if (bytesRead <= 0)
- {
- throw new ClosedChannelException();
- }
- else
- {
- sslEngine.unwrap(netData, tempData);
- }
- }
-
-
-
- private void doHandshakeWrite(SSLEngineResult.HandshakeStatus hsStatus)
- throws IOException
- {
- do
- {
- if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
- {
- doHandshakeUnwrap();
- }
- else
- {
- doHandshakeOp(hsStatus);
- }
- hsStatus = sslEngine.getHandshakeStatus();
- }
- while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
- || hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
- || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP);
- }
-
-
-
- private SSLEngineResult.HandshakeStatus doTasks()
- {
- Runnable task;
- while ((task = sslEngine.getDelegatedTask()) != null)
- {
- task.run();
- }
- return sslEngine.getHandshakeStatus();
- }
-
-
-
- private int writeInternal(final ByteBuffer clearData) throws IOException
- {
- int totBytesSent = 0;
- SSLEngineResult.HandshakeStatus hsStatus;
- hsStatus = sslEngine.getHandshakeStatus();
-
- if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
- || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
- || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
- {
- doHandshakeWrite(hsStatus);
- }
-
- while (clearData.hasRemaining())
- {
- netData.clear();
- final SSLEngineResult res = sslEngine.wrap(clearData, netData);
- netData.flip();
- if (netData.remaining() == 0)
- {
- // wrap didn't produce any data from our clear buffer.
- // Throw exception to prevent looping.
- throw new SSLException("SSLEngine.wrap produced 0 bytes");
- }
-
- if (res.getStatus() != SSLEngineResult.Status.OK)
- {
- throw new ClosedChannelException();
- }
-
- if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
- || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
- || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
- {
- doHandshakeWrite(hsStatus);
- }
- totBytesSent += socketChannel.write(netData);
- }
- return totBytesSent;
- }
}
--
Gitblit v1.10.0