/**
* This file is part of SecureNIO. Copyright (C) 2014 K. Dermitzakis
* <dermitza@gmail.com>
*
* SecureNIO is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* SecureNIO is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with SecureNIO. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.dermitza.securenio.socket.secure;
import ch.dermitza.securenio.AbstractSelector;
import ch.dermitza.securenio.socket.SocketIF;
import ch.dermitza.securenio.socket.timeout.TimeoutListener;
import ch.dermitza.securenio.socket.timeout.worker.Timeout;
import ch.dermitza.securenio.socket.timeout.worker.TimeoutWorker;
import ch.dermitza.securenio.util.PropertiesReader;
import ch.dermitza.securenio.util.logging.LoggerHandler;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import static javax.net.ssl.SSLEngineResult.Status.BUFFER_OVERFLOW;
import static javax.net.ssl.SSLEngineResult.Status.BUFFER_UNDERFLOW;
import static javax.net.ssl.SSLEngineResult.Status.CLOSED;
import static javax.net.ssl.SSLEngineResult.Status.OK;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
/**
* A secure socket implementation of {@link SocketIF}. This class implements all
* logic required to process SSL/TLS handshaking and encrypt/decrypt data being
* sent and received through the underlying {@link SocketChannel}. <p> Note that
* this class is declared as final as it should NOT be extended.
*
* @author K. Dermitzakis
* @version 0.19
* @since 0.18
*/
public final class SecureSocket implements SocketIF {
private static final Logger logger = LoggerHandler.getLogger(SecureSocket.class.getName());
private final SocketChannel sc;
private final SSLEngine engine;
// BUFFERS
private final ByteBuffer encryptedIn;
private final ByteBuffer encryptedOut;
private final ByteBuffer decryptedIn;
private final ByteBuffer decryptedOut;
private volatile boolean handshakePending = true;
private volatile boolean taskPending = false;
private boolean singleThreaded = false;
private SSLEngineResult result = null;
private final HandshakeListener hsListener;
private final TimeoutListener toListener;
private final TaskWorker taskWorker;
private final TimeoutWorker toWorker;
private final Timeout timeout;
/**
* Create a new instance of a {@link SecureSocket}. This instance has all
* the necessary logic to perform an SSL/TLS handshake and to encrypt and
* decrypt data being sent and received through the underlying
* {@link SocketChannel}.
*
* @param channel The underlying SocketChannel
* @param engine The underlying SSLEngine
* @param singleThreaded Whether or not this socket should perform the
* {@link SSLEngineResult#HandshakeStatus} NEED_TASK in the same thread
* (true) or in the {@link TaskWorker} thread (false). If set to true, null
* can be passed for the {@link TaskWorker} instance.
* @param taskWorker The {@link TaskWorker} instance associated with this
* SecureSocket. Can be null if this socket performs the
* {@link SSLEngineResult#HandshakeStatus} NEED_TASK in the same thread
* @param toWorker The {@link TimeoutWorker} instance associated with this
* SecureSocket.
* @param hsListener The {@link HandshakeListener} associated with this
* SecureSocket. Only one handshake listener is associated per socket, which
* is usually the {@link AbstractSelector} implementation.
* @param toListener The {@link TimeoutListener} associated with this
* SecureSocket. Only one timeout listener is associated per socket, which
* is usually the {@link AbstractSelector} implementation.
*/
public SecureSocket(SocketChannel channel, SSLEngine engine,
boolean singleThreaded, TaskWorker taskWorker,
TimeoutWorker toWorker, HandshakeListener hsListener,
TimeoutListener toListener) {
this.sc = channel;
this.engine = engine;
this.singleThreaded = singleThreaded;
this.taskWorker = taskWorker;
this.hsListener = hsListener;
// Timeouts
this.toWorker = toWorker;
this.toListener = toListener;
timeout = new Timeout(this, toListener, PropertiesReader.getTimeoutMS());
int appBufSize = engine.getSession().getApplicationBufferSize();
int netBufSize = engine.getSession().getPacketBufferSize();
decryptedIn = ByteBuffer.allocate(appBufSize);
decryptedOut = ByteBuffer.allocate(appBufSize);
encryptedIn = ByteBuffer.allocate(netBufSize);
encryptedOut = ByteBuffer.allocate(netBufSize);
}
/**
* Get the associated underlying {@link SSLEngine}. This method is called
* from the {@link TaskWorker} when there are pending SSLEngine tasks to be
* run.
*
* @return the underlying {@link SSLEngine} associated with this socket.
*/
public SSLEngine getEngine() {
return this.engine;
}
/**
* Invalidate the current {@link SSLSession}. This method could be
* periodically used via a {@link Timeout} to perform SSL/TLS session
* rotation if needed.
*/
@Override
public void invalidateSession() {
engine.getSession().invalidate();
handshakePending = true;
taskPending = false;
}
/**
* Initialize SSL/TLS handshaking. This method is called from the
* {@link AbstractSelector} thread in two cases: (1) when
* {@link #finishConnect()} is called or (2) when the {@link SSLSession} has
* been previously invalidated.
*
* @throws IOException propagated exceptions from
* {@link #processHandshake()}
* @throws SSLException if there is an SSL/TLS problem with the call to the
* underlying {@link SSLEngine#beginHandshake()}.
*/
@Override
public void initHandshake() throws IOException {
engine.beginHandshake();
processHandshake();
}
/**
* Returns the underlying {@link SocketChannel}. This is done in order to
* register the current socket with a {@link Selector}, as only the
* {@link SocketChannel} implementation is allowed to be associated with a
* {@link Selector}.
*
* @return the underlying SocketChannel
*/
@Override
public SocketChannel getSocket() {
return this.sc;
}
/**
* Pass-through implementation of
* {@link SocketChannel#connect(SocketAddress remote)}
*
* @param remote The remote address to which this channel is to be connected
* @return true if a connection was established, false if this channel is in
* non-blocking mode and the connection operation is in progress
* @throws IOException Propagated exceptions from the underlying
* {@link SocketChannel#connect(SocketAddress remote)} implementation.
*/
@Override
public boolean connect(SocketAddress remote) throws IOException {
return sc.connect(remote);
}
/**
* Pass-through implementation of {@link SocketChannel#finishConnect()} In
* the {@link SecureSocket} implementation, once the connection of the
* underlying {@link SocketChannel} is finished, the SSL/TLS handshake is
* manually initiated here. <p> Note that this is not strictly necessary but
* rather a convenience, as subsequent handshakes can be initiated from the
* {@link SecureSocket#read(ByteBuffer buffer)} and
* {@link SecureSocket#write(ByteBuffer buffer)} methods.
*
* @return true if, and only if, this channel's socket is now connected
* @throws IOException Propagated exceptions from the underlying
* {@link SocketChannel#finishConnect()} implementation.
*/
@Override
public boolean finishConnect() throws IOException {
boolean ret = sc.finishConnect();
if (ret) {// sanity check
initHandshake();
}
return ret;
}
/**
* Pass-through implementation of
* {@link SocketChannel#register(Selector sel, int ops)}
*
* @param sel The selector with which this channel is to be registered
* @param ops The interest set for the resulting key
* @return A key representing the registration of this channel with the
* given selector
* @throws ClosedChannelException Propagated exceptions from the underlying
* {@link SocketChannel#register(Selector sel, int ops)} implementation.
*/
@Override
public SelectionKey register(Selector sel, int ops) throws ClosedChannelException {
return sc.register(sel, ops);
}
/**
* Pass-through implementation of
* {@link SocketChannel#configureBlocking(boolean block)}
*
* @param block If true then this channel will be placed in blocking mode;
* if false then it will be placed in non-blocking mode
* @return This selectable channel
* @throws IOException Propagated exceptions from the underlying
* {@link SocketChannel#configureBlocking(boolean block)} implementation.
*/
@Override
public SelectableChannel configureBlocking(boolean block) throws IOException {
return sc.configureBlocking(block);
}
/**
* Performs SSL/TLS handshaking. Note that it can perform NEED_TASK either
* on the same thread or on the {@link TaskWorker} thread, based on how the
* {@link SecureSocket} has been initialized.
*
* @throws IOException If there is an underlying IOException while
* performing the handshake
* @throws SSLException If there is an exception thrown by the underlying
* {@link SSLEngine} while performing the handshake
*/
@Override
public void processHandshake() throws IOException {
int count;
SSLEngineResult.HandshakeStatus status;
// At first call of processHandshake(), there is no SSLEngineResult
// yet, use the handshakeStatus from the SSLEngine instead
if (result == null) {
status = engine.getHandshakeStatus();
} else {
status = result.getHandshakeStatus();
}
// process the handshake status
switch (status) {
case NEED_TASK:
logger.log(Level.FINEST, "{0} NEED_TASK",
sc.socket().getRemoteSocketAddress());
// Run the delegated SSL/TLS tasks
runDelegatedTasks();
if (!singleThreaded) {
// Return as handshaking cannot continue
return;
}
case NEED_UNWRAP:
logger.log(Level.FINEST, "{0} NEED_UNWRAP",
sc.socket().getRemoteSocketAddress());
// Don’t read if inbound is already closed
count = engine.isInboundDone()
? -1
: sc.read(encryptedIn);
logger.log(Level.FINEST, "Read {0} bytes", count);
encryptedIn.flip();
try {
result = engine.unwrap(encryptedIn, decryptedIn);
encryptedIn.compact();
} catch (IllegalStateException ise) {
// SSLEngine may fail with IllegalStateException in some
// cases when receiving unexpected kinds of SSL records
// after being closed, see:
// https://github.com/spray/spray/issues/743
// https://github.com/spray/spray/commit/cd1cfb8
// https://groups.google.com/forum/#!msg/spray-user/v3x3ZfyVVF0/9DqHCGa9M38J
// https://groups.google.com/forum/#%21msg/spray-user/qxntq3aRc_I/TPj4r0XGobgJ
logger.log(Level.FINEST, "Ignoring exception in close()");
}
break;
case NEED_WRAP:
logger.log(Level.FINEST, "{0} NEED_WRAP",
sc.socket().getRemoteSocketAddress());
decryptedOut.flip();
result = engine.wrap(decryptedOut, encryptedOut);
decryptedOut.compact();
if (result.getStatus() == SSLEngineResult.Status.CLOSED) {
// RFC 2246 #7.2.1 requires us to respond to an
// incoming close_notify with an outgoing
// close_notify. The engine takes care of this, so we
// are now trying to send a close_notify, which can
// only happen if we have just received a
// close_notify.
// Try to flush the close_notify.
try {
count = flush();
} catch (SocketException exc) {
// failed to send the close_notify, this can happen if
// the peer has already sent its close_notify and then
// close the socket, which is permitted by RFC_2246.
}
} else {
// flush without the try/catch,
// letting any exceptions propagate.
count = flush();
}
break;
case FINISHED:
logger.log(Level.FINEST, "{0} FINISHED",
sc.socket().getRemoteSocketAddress());
handshakePending = false;
// Indicate to the associated handshake listener that the
// handshake is complete
hsListener.handshakeComplete(this);
case NOT_HANDSHAKING:
logger.log(Level.FINEST, "{0} NOT_HANDSHAKING",
sc.socket().getRemoteSocketAddress());
// handshake has been completed at this point, no need to
// check the status of the SSLEngineResult;
return;
}
// Check the result of the preceding wrap or unwrap.
switch (result.getStatus()) {
case BUFFER_UNDERFLOW:
// Return as we do not have enough data to continue processing
// the handshake
logger.log(Level.FINEST, "{0} BUFFER_UNDERFLOW",
sc.socket().getRemoteSocketAddress());
return;
case BUFFER_OVERFLOW:
logger.log(Level.FINEST, "{0} BUFFER_OVERFLOW",
sc.socket().getRemoteSocketAddress());
// Return as the encrypted buffer has not been cleared yet
return;
case CLOSED:
logger.log(Level.FINEST, "{0} CLOSED",
sc.socket().getRemoteSocketAddress());
if (engine.isOutboundDone()) {
sc.socket().shutdownOutput();// stop sending
}
return;
case OK:
logger.log(Level.FINEST, "{0} OK",
sc.socket().getRemoteSocketAddress());
// handshaking can continue.
break;
}
processHandshake();
}
/**
* Runs the {@link SSLEngine} delegated tasks. The tasks can either run in
* the same thread (single threaded implementation) or via the
* {@link TaskWorker} thread (multithreaded implementation). Note that in
* the single threaded implementation, the {@link AbstractSelector} thread
* will block until all tasks are completed. Additionally, in this case, the
* taskPending variable is not needed.
*/
private void runDelegatedTasks() {
if (singleThreaded) {
// Run the delegated tasks in the same thread
Runnable task;
while ((task = engine.getDelegatedTask()) != null) {
task.run();
}
// Update the SSLEngineResult
updateResult();
} else {
// Run the delegated tasks in the TaskWorker thread
// An existing task might already be pending for completion with
// the TaskWorker, and we could have arrived here from a read or
// write. If this is the case, we should NOT requeue the task
// with the TaskWorker, otherwise there will be subsequent
// problems. E.g. the TaskWorker finishes a (multi-queued) task
// which by the second time of processing is now null. This can
// then trigger processHandshake() to run on a potentially
// closed socket.
if (!taskPending) {
taskWorker.addSocket(this);
setTaskPending(true);
}
}
}
/**
* Update the {@link SSLEngineResult} and {@link #setTaskPending(boolean)}
* to false. <p> This method is called once the {@link SSLEngine} has no
* more pending tasks. The updated result is fused from both the previous
* {@link SSLEngineResult} and the {@link SSLEngine#getHandshakeStatus()}.
* <p> This is done such that we can receive the FINISHED
* {@link SSLEngineResult#handshakeStatus} that would otherwise not be
* available by just updating the existing {@link SSLEngineResult}. <p> We
* also {@link #setTaskPending(boolean)} to false as there are no more tasks
* needed to be completed for the underlying {@link SSLEngine}.
*/
@Override
public void updateResult() {
result = new SSLEngineResult(
result.getStatus(),
engine.getHandshakeStatus(),
result.bytesProduced(),
result.bytesConsumed());
// SSLEngine task was completed, set its taskPending
// to false, in case there are more tasks to be run
// in the future. TODO: does this ever happen?
setTaskPending(false);
}
/**
* Reads a sequence of bytes from this channel into the given buffer. This
* is a pass-through implementation of the underlying
* {@link SecureSocket#read(ByteBuffer buffer)}, with additional logic to
* handle the SSL/TLS encrypted stream. <p> An attempt is made to read up to
* r bytes from the encrypted channel, where r is the number of bytes
* remaining in the buffer. If handshaking has not been previously
* completed, handshaking also occurs at this stage. This method will also
* respond appropriately with returning -1 (EOF) when the underlying
* {@link SSLEngine#isInboundDone()}, or when reading from the encrypted
* channel returns -1 (EOF);
*
*
* @param buffer The buffer into which bytes are to be transferred
* @return The number of bytes read, possibly zero, or -1 if the channel has
* reached end-of-stream
* @throws IOException Propagated exceptions from the underlying
* {@link SocketChannel#read(ByteBuffer buffer)} implementation.
* @throws BufferOverflowException If the underlying {@link SSLEngineResult}
* has a status of BUFFER_OVERFLOW. As this should not happen in this
* implementation, it can be considered serious and should be handled
* @throws BufferUnderflowException If the underlying
* {@link SSLEngineResult} has a status of BUFFER_UNDERFLOW. As this should
* not happen in this implementation, it can be considered serious and
* should be handled
*/
@Override
public int read(ByteBuffer buffer) throws IOException {
if (engine.isInboundDone()) {
// We can skip the read operation as the SSLEngine is closed,
// instead, propagate EOF one level up
return -1;
}
decryptedIn.clear();
int pos = decryptedIn.position();
// Read from the channel
int count = sc.read(encryptedIn);
logger.log(Level.FINEST, "{0} Read {1} bytes encrypted",
new Object[]{sc.socket().getRemoteSocketAddress(), count});
if (count == -1) {
// We have reached EOF, propagate one level up
// At this point, we may not have received close_notify from peer,
// and as such we cannot SSLEngine.closeInbound(), this will happen
// in a subsequent step
//engine.closeInbound();
return count;
}
// Unwrap the data just read
encryptedIn.flip();
result = engine.unwrap(encryptedIn, decryptedIn);
encryptedIn.compact();
// Process the engineResult.Status
switch (result.getStatus()) {
case BUFFER_UNDERFLOW:
// nothing was read. This is the entry point for initiating a
// short timeout in the underlying socket if we are still in the
// handshaking phase. The reason is to rule out DDOS attacks
// where a high number of idle connections are created with the
// sole purpose of either exhausting the ports of the host
// machine or depleting the host machine's memory.
// Schedule a new timeout
toWorker.insert(timeout);
logger.log(Level.FINEST, "{0} BUFFER_UNDERFLOW",
sc.socket().getRemoteSocketAddress());
return 0;
case BUFFER_OVERFLOW:
if (!timeout.hasExpired()) {
// cancel any previous timeout
toWorker.cancel(timeout);
}
logger.log(Level.FINEST, "{0} BUFFER_OVERFLOW",
sc.socket().getRemoteSocketAddress());
// This should never happen (ideally). If this happens, the
// thread responsible for emptying the decryptedIn buffer has
// not done so in time. Throw an exception to be handled at the
// application layer.
throw new BufferOverflowException();
case CLOSED:
logger.log(Level.FINEST, "{0} CLOSED",
sc.socket().getRemoteSocketAddress());
// The SSLEngine was inbound closed, there is and will be no
// more input from the engine, so setup the socket appropriately
// too. An outbound close_notify will be send by the SSLEngine
sc.socket().shutdownInput();
break;
case OK:
if (!timeout.hasExpired()) {
// cancel any previous timeout
toWorker.cancel(timeout);
}
logger.log(Level.FINEST, "{0} OK",
sc.socket().getRemoteSocketAddress());
break;
}
// process any handshaking now required
processHandshake();
// put the data to the buffer given to us
for (int i = pos; i < decryptedIn.position(); i++) {
buffer.put(decryptedIn.get(i));
}
// return count of application data read
count = decryptedIn.position() - pos;
return count;
}
@Override
public int write(ByteBuffer buffer) throws IOException {
// Make shallow copy, assumes the entire buffer needs to be written
//System.out.println("buffer position" + buffer.position());
//System.out.println("buffer capacity " + buffer.capacity());
while (buffer.hasRemaining()) {
decryptedOut.put(buffer.get());
}
//System.out.println("decryptedOut position " + decryptedOut.position());
//System.out.println("decryptedOut capacity " + decryptedOut.capacity());
int pos = decryptedOut.position();
encryptedOut.clear();
// Wrap the data to be written
decryptedOut.flip();
result = engine.wrap(decryptedOut, encryptedOut);
decryptedOut.compact();
// Process the engineResult.Status
switch (result.getStatus()) {
case BUFFER_UNDERFLOW:
logger.log(Level.FINEST, "{0} BUFFER_UNDERFLOW",
sc.socket().getRemoteSocketAddress());
// This shouldn't happen as we only call write() when there is
// data to be written, throw an exception that will be handled
// in the application layer.
throw new BufferUnderflowException();
case BUFFER_OVERFLOW:
logger.log(Level.FINEST, "{0} BUFFER_OVERFLOW",
sc.socket().getRemoteSocketAddress());
// This shouldn't happen if we flush data that has been wrapped
// as we do in this implementation, throw an exception that will
// be handled in the application layer.
throw new BufferOverflowException();
case CLOSED:
logger.log(Level.FINEST, "{0} CLOSED",
sc.socket().getRemoteSocketAddress());
// Trying to write on a closed SSLEngine, throw an exception
// that will be handled in the application layer.
throw new SSLException("SSLEngine is CLOSED");
case OK:
logger.log(Level.FINEST, "{0} OK",
sc.socket().getRemoteSocketAddress());
// Everything is good, everything is fine.
break;
}
// Process any pending handshake
processHandshake();
// Flush any pending data to the network
flush();
// return count of application bytes written.
return pos - decryptedOut.position();
}
/**
* Flush encrypted output data to the underlying {@link SocketChannel}. This
* method will block until all data is written to the channel. TODO:
* implement a selector pool instead of looping until all data is written.
* Another option would be increasing the SO_SNDBUF size to twice the size,
* it can be done on the fly and could circumvent the temporary selector
* issue.
*
* @return The number of bytes written, possibly zero
* @throws IOException Propagated exceptions from the underlying
* {@link SocketChannel#write(ByteBuffer buffer)} implementation.
*/
private int flush() throws IOException {
// Selector temp = null;
encryptedOut.flip();
int remaining = encryptedOut.remaining();
//System.out.println("Encrypted out remaining: " + remaining);
int countOut = 0;
int count;
int retries = 0;
while(encryptedOut.hasRemaining()){
count = sc.write(encryptedOut);
countOut += count;
retries++;
}
/*
while (encryptedOut.hasRemaining()) {
count = sc.write(encryptedOut);
countOut += count;
retries++;
//System.out.println(sc.socket().getLocalPort()
// + ":" + sc.socket().getPort() + " Flushed " + count + " bytes");
if (count < 0) {
throw new IOException("EOF during flush");
}
if (count == 0) {
// write channel full. Try letting other threads have a go.
Thread.yield();
count = sc.write(encryptedOut);
countOut += count;
retries++;
if (count < 0) {
throw new IOException("EOF during flush");
}
if (count == 0) {
// still full. need to block until it is writable.
// TODO: implement a selector pull to handle this write
while (encryptedOut.hasRemaining()) {
count = sc.write(encryptedOut);
countOut += count;
retries++;
}
temp = Selector.open();
getSocket().register(temp, SelectionKey.OP_WRITE);
temp.select();
}
}
}
if (temp != null) {
temp.close();
temp = null;
}
*/
encryptedOut.compact();
logger.log(Level.FINEST, "{0} Flushed {1} bytes, {2} retries.",
new Object[]{sc.socket().getRemoteSocketAddress(),
countOut, retries <= 1 ? 0 : retries});
return countOut;
}
/**
* Pass-through implementation of {@link SocketChannel#close()}. <p> Before
* closing the underlying {@link SocketChannel}, attempts are made to
* {@link #flush} any encrypted data remaining in the encrypted buffer and
* cleanly close the associated {@link SSLEngine}. <p> Finally, we process
* what we can via {@link #processHandshake()} before closing the underlying
* {@link SocketChannel}.
*
* @throws IOException Propagated exceptions from the underlying
* SocketChannel.close() implementation.
* @throws SSLException If we have not received a close_notify when
* {@link SSLEngine#closeInbound()} is invoked.
*/
@Override
public void close() throws IOException {
//if (timeout.hasExpired()) {
// This causes a threadlock, WHY? TODO
// cancel any previous timeout
// toWorker.cancel(timeout);
//}
try {
// Flush any pending encrypted output data
flush();
if (!engine.isOutboundDone()) {
engine.closeOutbound();
processHandshake();
/*
* RFC 2246 #7.2.1: if we are initiating this
* close, we may send the close_notify without
* waiting for an incoming close_notify.
* If we weren't the initiator we would have already
* received the inbound close_notify in read(),
* and therefore already have done closeOutbound(),
* so, we are initiating the close,
* so we can skip the closeInbound().
*/
} else if (!engine.isInboundDone()) {
// Closing inbound will throw an SSLException if we have not
// received a close_notify.
engine.closeInbound();
// Process what we can before we close the channel.
processHandshake();
}
} finally {
// Clear all buffers TODO
// Close the channel.
sc.close();
}
}
/**
* Used to identify whether the handshaking performed from the underlying
* {@link SSLEngine} is still pending. <p> This is called from the
* application layer that invokes
* {@link AbstractSelector#send(SocketIF, ByteBuffer)} to correctly queue
* data to be written.
*
* @return whether the SSL/TLS handshaking is still pending
*/
@Override
public boolean handshakePending() {
return this.handshakePending;
}
/**
* Sets whether or not there is an {@link SSLEngine} task pending, during an
* SSL/TLS handshake. <p> If the current socket implementation is processing
* the pending tasks in the same thread, this method has no effect.
* Otherwise in a multi-threaded implementation, this allows the
* {@link AbstractSelector} thread to know the status of the task, and
* subsequently correctly process incoming requests (e.g. queueing data to
* be written).
*
* @param taskPending Set whether or not there is a {@link SSLEngine} task
* pending for the {@link SSLEngine} associated with the underlying
* {@link SocketChannel}. Has no effect in a single threaded implementation.
*/
@Override
public void setTaskPending(boolean taskPending) {
this.taskPending = taskPending;
}
}