From ef134b5da3f26c449aa26367008d8028f109e8f1 Mon Sep 17 00:00:00 2001 From: marci4 Date: Sun, 28 May 2017 21:58:41 +0200 Subject: [PATCH] New SSLSocketChannel New implementation working for android as well --- .../org/java_websocket/SSLSocketChannel.java | 482 ++++++++++++++++++ .../org/java_websocket/SSLSocketChannel2.java | 395 -------------- .../DefaultSSLWebSocketServerFactory.java | 5 +- .../java_websocket/util/ByteBufferUtils.java | 8 +- 4 files changed, 489 insertions(+), 401 deletions(-) create mode 100644 src/main/java/org/java_websocket/SSLSocketChannel.java delete mode 100644 src/main/java/org/java_websocket/SSLSocketChannel2.java diff --git a/src/main/java/org/java_websocket/SSLSocketChannel.java b/src/main/java/org/java_websocket/SSLSocketChannel.java new file mode 100644 index 000000000..fa1f57815 --- /dev/null +++ b/src/main/java/org/java_websocket/SSLSocketChannel.java @@ -0,0 +1,482 @@ +package org.java_websocket; + +import org.java_websocket.util.ByteBufferUtils; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import java.io.IOException; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.concurrent.ExecutorService; + + +/** + * A class that represents an SSL/TLS peer, and can be extended to create a client or a server. + *

+ * It makes use of the JSSE framework, and specifically the {@link SSLEngine} logic, which + * is described by Oracle as "an advanced API, not appropriate for casual use", since + * it requires the user to implement much of the communication establishment procedure himself. + * More information about it can be found here: http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLEngine + *

+ * {@link SSLSocketChannel} implements the handshake protocol, required to establish a connection between two peers, + * which is common for both client and server and provides the abstract {@link SSLSocketChannel#read(ByteBuffer)} and + * {@link SSLSocketChannel#write(ByteBuffer)} (String)} methods, that need to be implemented by the specific SSL/TLS peer + * that is going to extend this class. + * + * @author Alex Karnezis + *

+ * Modified by marci4 to allow the usage as a ByteChannel + *

+ * Permission for usage recieved at May 25, 2017 by Alex Karnezis + */ +public class SSLSocketChannel implements WrappedByteChannel, ByteChannel { + + /** + * The underlaying socket channel + */ + private final SocketChannel socketChannel; + + /** + * The engine which will be used for un-/wrapping of buffers + */ + private final SSLEngine engine; + + /** + * The selection key for this socket channel + * Used to set interestOP SelectionKey.OP_WRITE for the underlying channel + * */ + private SelectionKey selectionKey; + + + /** + * Will contain this peer's application data in plaintext, that will be later encrypted + * using {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)} and sent to the other peer. This buffer can typically + * be of any size, as long as it is large enough to contain this peer's outgoing messages. + * If this peer tries to send a message bigger than buffer's capacity a {@link BufferOverflowException} + * will be thrown. + */ + private ByteBuffer myAppData; + + /** + * Will contain this peer's encrypted data, that will be generated after {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)} + * is applied on {@link SSLSocketChannel#myAppData}. It should be initialized using {@link SSLSession#getPacketBufferSize()}, + * which returns the size up to which, SSL/TLS packets will be generated from the engine under a session. + * All SSLEngine network buffers should be sized at least this large to avoid insufficient space problems when performing wrap and unwrap calls. + */ + private ByteBuffer myNetData; + + /** + * Will contain the other peer's (decrypted) application data. It must be large enough to hold the application data + * from any peer. Can be initialized with {@link SSLSession#getApplicationBufferSize()} for an estimation + * of the other peer's application data and should be enlarged if this size is not enough. + */ + private ByteBuffer peerAppData; + + /** + * Will contain the other peer's encrypted data. The SSL/TLS protocols specify that implementations should produce packets containing at most 16 KB of plaintext, + * so a buffer sized to this value should normally cause no capacity problems. However, some implementations violate the specification and generate large records up to 32 KB. + * If the {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer)} detects large inbound packets, the buffer sizes returned by SSLSession will be updated dynamically, so the this peer + * should check for overflow conditions and enlarge the buffer using the session's (updated) buffer size. + */ + private ByteBuffer peerNetData; + + /** + * Will be used to execute tasks that may emerge during handshake in parallel with the server's main thread. + */ + private ExecutorService executor; + + + public SSLSocketChannel( SocketChannel inputSocketChannel, SSLEngine inputEngine, ExecutorService inputExecutor, SelectionKey key ) throws IOException { + if( inputSocketChannel == null || inputEngine == null || executor == inputExecutor ) + throw new IllegalArgumentException( "parameter must not be null" ); + + this.socketChannel = inputSocketChannel; + this.engine = inputEngine; + this.executor = inputExecutor; + myNetData = ByteBuffer.allocate( engine.getSession().getPacketBufferSize() ); + peerNetData = ByteBuffer.allocate( engine.getSession().getPacketBufferSize() ); + this.engine.beginHandshake(); + if( doHandshake() ) { + if( key != null ) { + key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); + this.selectionKey = key; + } + } else { + try { + socketChannel.close(); + } catch ( IOException e ) { + e.printStackTrace(); + } + } + } + + @Override + public synchronized int read( ByteBuffer dst ) throws IOException { + if( !dst.hasRemaining() ) { + return 0; + } + if( peerAppData.hasRemaining() ) { + peerAppData.flip(); + return ByteBufferUtils.transferByteBuffer( peerAppData, dst ); + } + peerNetData.compact(); + + int bytesRead = socketChannel.read( peerNetData ); + /** + * If bytesRead are 0 put we still have some data in peerNetData still to an unwrap (for testcase 1.1.6) + */ + if( bytesRead > 0 || peerNetData.hasRemaining() ) { + peerNetData.flip(); + while( peerNetData.hasRemaining() ) { + peerAppData.compact(); + SSLEngineResult result; + try { + result = engine.unwrap( peerNetData, peerAppData ); + } catch ( SSLException e ) { + e.printStackTrace(); + throw e; + } + switch(result.getStatus()) { + case OK: + peerAppData.flip(); + return ByteBufferUtils.transferByteBuffer( peerAppData, dst ); + case BUFFER_UNDERFLOW: + peerAppData.flip(); + return ByteBufferUtils.transferByteBuffer( peerAppData, dst ); + case BUFFER_OVERFLOW: + peerAppData = enlargeApplicationBuffer( peerAppData ); + break; + case CLOSED: + closeConnection(); + dst.clear(); + return -1; + default: + throw new IllegalStateException( "Invalid SSL status: " + result.getStatus() ); + } + } + } else if( bytesRead < 0 ) { + handleEndOfStream(); + } + ByteBufferUtils.transferByteBuffer( peerAppData, dst ); + return bytesRead; + } + + @Override + public synchronized int write( ByteBuffer output ) throws IOException { + int num = 0; + while( output.hasRemaining() ) { + // The loop has a meaning for (outgoing) messages larger than 16KB. + // Every wrap call will remove 16KB from the original message and send it to the remote peer. + myNetData.clear(); + SSLEngineResult result = engine.wrap( output, myNetData ); + switch(result.getStatus()) { + case OK: + myNetData.flip(); + while( myNetData.hasRemaining() ) { + num += socketChannel.write( myNetData ); + } + break; + case BUFFER_OVERFLOW: + myNetData = enlargePacketBuffer( myNetData ); + break; + case BUFFER_UNDERFLOW: + throw new SSLException( "Buffer underflow occured after a wrap. I don't think we should ever get here." ); + case CLOSED: + closeConnection(); + return 0; + default: + throw new IllegalStateException( "Invalid SSL status: " + result.getStatus() ); + } + } + return num; + } + + /** + * Implements the handshake protocol between two peers, required for the establishment of the SSL/TLS connection. + * During the handshake, encryption configuration information - such as the list of available cipher suites - will be exchanged + * and if the handshake is successful will lead to an established SSL/TLS session. + *

+ *

+ * A typical handshake will usually contain the following steps: + *

+ *

+ *

+ * Handshake is also used during the end of the session, in order to properly close the connection between the two peers. + * A proper connection close will typically include the one peer sending a CLOSE message to another, and then wait for + * the other's CLOSE message to close the transport link. The other peer from his perspective would read a CLOSE message + * from his peer and then enter the handshake procedure to send his own CLOSE message as well. + * + * @return True if the connection handshake was successful or false if an error occurred. + * @throws IOException - if an error occurs during read/write to the socket channel. + */ + private boolean doHandshake() throws IOException { + SSLEngineResult result; + HandshakeStatus handshakeStatus; + + // NioSslPeer's fields myAppData and peerAppData are supposed to be large enough to hold all message data the peer + // will send and expects to receive from the other peer respectively. Since the messages to be exchanged will usually be less + // than 16KB long the capacity of these fields should also be smaller. Here we initialize these two local buffers + // to be used for the handshake, while keeping client's buffers at the same size. + int appBufferSize = engine.getSession().getApplicationBufferSize(); + myAppData = ByteBuffer.allocate( appBufferSize ); + peerAppData = ByteBuffer.allocate( appBufferSize ); + myNetData.clear(); + peerNetData.clear(); + + handshakeStatus = engine.getHandshakeStatus(); + while( handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING ) { + switch(handshakeStatus) { + case NEED_UNWRAP: + if( socketChannel.read( peerNetData ) < 0 ) { + if( engine.isInboundDone() && engine.isOutboundDone() ) { + return false; + } + try { + engine.closeInbound(); + } catch ( SSLException e ) { + //Ignore, cant do anything against this exception + } + engine.closeOutbound(); + // After closeOutbound the engine will be set to WRAP state, in order to try to send a close message to the client. + handshakeStatus = engine.getHandshakeStatus(); + break; + } + peerNetData.flip(); + try { + result = engine.unwrap( peerNetData, peerAppData ); + peerNetData.compact(); + handshakeStatus = result.getHandshakeStatus(); + } catch ( SSLException sslException ) { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + switch(result.getStatus()) { + case OK: + break; + case BUFFER_OVERFLOW: + // Will occur when peerAppData's capacity is smaller than the data derived from peerNetData's unwrap. + peerAppData = enlargeApplicationBuffer( peerAppData ); + break; + case BUFFER_UNDERFLOW: + // Will occur either when no data was read from the peer or when the peerNetData buffer was too small to hold all peer's data. + peerNetData = handleBufferUnderflow( peerNetData ); + break; + case CLOSED: + if( engine.isOutboundDone() ) { + return false; + } else { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + default: + throw new IllegalStateException( "Invalid SSL status: " + result.getStatus() ); + } + break; + case NEED_WRAP: + myNetData.clear(); + try { + result = engine.wrap( myAppData, myNetData ); + handshakeStatus = result.getHandshakeStatus(); + } catch ( SSLException sslException ) { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + switch(result.getStatus()) { + case OK: + myNetData.flip(); + while( myNetData.hasRemaining() ) { + socketChannel.write( myNetData ); + } + break; + case BUFFER_OVERFLOW: + // Will occur if there is not enough space in myNetData buffer to write all the data that would be generated by the method wrap. + // Since myNetData is set to session's packet size we should not get to this point because SSLEngine is supposed + // to produce messages smaller or equal to that, but a general handling would be the following: + myNetData = enlargePacketBuffer( myNetData ); + break; + case BUFFER_UNDERFLOW: + throw new SSLException( "Buffer underflow occured after a wrap. I don't think we should ever get here." ); + case CLOSED: + try { + myNetData.flip(); + while( myNetData.hasRemaining() ) { + socketChannel.write( myNetData ); + } + // At this point the handshake status will probably be NEED_UNWRAP so we make sure that peerNetData is clear to read. + peerNetData.clear(); + } catch ( Exception e ) { + handshakeStatus = engine.getHandshakeStatus(); + } + break; + default: + throw new IllegalStateException( "Invalid SSL status: " + result.getStatus() ); + } + break; + case NEED_TASK: + Runnable task; + while( ( task = engine.getDelegatedTask() ) != null ) { + executor.execute( task ); + } + handshakeStatus = engine.getHandshakeStatus(); + break; + case FINISHED: + break; + case NOT_HANDSHAKING: + break; + default: + throw new IllegalStateException( "Invalid SSL status: " + handshakeStatus ); + } + } + + return true; + + } + + /** + * Enlarging a packet buffer (peerNetData or myNetData) + * + * @param buffer the buffer to enlarge + * @return the enlarged buffer + */ + private ByteBuffer enlargePacketBuffer( ByteBuffer buffer ) { + return enlargeBuffer( buffer, engine.getSession().getPacketBufferSize() ); + } + + /** + * Enlarging a packet buffer (peerAppData or myAppData) + * + * @param buffer the buffer to enlarge + * @return the enlarged buffer + */ + private ByteBuffer enlargeApplicationBuffer( ByteBuffer buffer ) { + return enlargeBuffer( buffer, engine.getSession().getApplicationBufferSize() ); + } + + /** + * Compares sessionProposedCapacity with buffer's capacity. If buffer's capacity is smaller, + * returns a buffer with the proposed capacity. If it's equal or larger, returns a buffer + * with capacity twice the size of the initial one. + * + * @param buffer - the buffer to be enlarged. + * @param sessionProposedCapacity - the minimum size of the new buffer, proposed by {@link SSLSession}. + * @return A new buffer with a larger capacity. + */ + private ByteBuffer enlargeBuffer( ByteBuffer buffer, int sessionProposedCapacity ) { + if( sessionProposedCapacity > buffer.capacity() ) { + buffer = ByteBuffer.allocate( sessionProposedCapacity ); + } else { + buffer = ByteBuffer.allocate( buffer.capacity() * 2 ); + } + return buffer; + } + + /** + * Handles {@link SSLEngineResult.Status#BUFFER_UNDERFLOW}. Will check if the buffer is already filled, and if there is no space problem + * will return the same buffer, so the client tries to read again. If the buffer is already filled will try to enlarge the buffer either to + * session's proposed size or to a larger capacity. A buffer underflow can happen only after an unwrap, so the buffer will always be a + * peerNetData buffer. + * + * @param buffer - will always be peerNetData buffer. + * @return The same buffer if there is no space problem or a new buffer with the same data but more space. + */ + private ByteBuffer handleBufferUnderflow( ByteBuffer buffer ) { + if( engine.getSession().getPacketBufferSize() < buffer.limit() ) { + return buffer; + } else { + ByteBuffer replaceBuffer = enlargePacketBuffer( buffer ); + buffer.flip(); + replaceBuffer.put( buffer ); + return replaceBuffer; + } + } + + /** + * This method should be called when this peer wants to explicitly close the connection + * or when a close message has arrived from the other peer, in order to provide an orderly shutdown. + *

+ * It first calls {@link SSLEngine#closeOutbound()} which prepares this peer to send its own close message and + * sets {@link SSLEngine} to the NEED_WRAP state. Then, it delegates the exchange of close messages + * to the handshake method and finally, it closes socket channel. + * + * @throws IOException if an I/O error occurs to the socket channel. + */ + private void closeConnection() throws IOException { + engine.closeOutbound(); + try { + doHandshake(); + } catch ( IOException e ) { + //Just ignore this exception since we are closing the connection already + } + socketChannel.close(); + } + + /** + * In addition to orderly shutdowns, an unorderly shutdown may occur, when the transport link (socket channel) + * is severed before close messages are exchanged. This may happen by getting an -1 or {@link IOException} + * when trying to read from the socket channel, or an {@link IOException} when trying to write to it. + * In both cases {@link SSLEngine#closeInbound()} should be called and then try to follow the standard procedure. + * + * @throws IOException if an I/O error occurs to the socket channel. + */ + private void handleEndOfStream() throws IOException { + try { + engine.closeInbound(); + } catch ( Exception e ) { + System.err.println( "This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream." ); + } + closeConnection(); + } + + @Override + public boolean isNeedWrite() { + return false; + } + + @Override + public void writeMore() throws IOException { + //Nothing to do since we write out all the data in a while loop + } + + @Override + public boolean isNeedRead() { + return peerNetData.hasRemaining() || peerAppData.hasRemaining(); + } + + @Override + public int readMore( ByteBuffer dst ) throws IOException { + return read( dst ); + } + + @Override + public boolean isBlocking() { + return socketChannel.isBlocking(); + } + + + @Override + public boolean isOpen() { + return socketChannel.isOpen(); + } + + @Override + public void close() throws IOException { + closeConnection(); + } +} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java deleted file mode 100644 index 6b1990ee5..000000000 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - Copyright (C) 2003 Alexander Kout - Originally from the jFxp project (http://jfxp.sourceforge.net/). - Copied with permission June 11, 2012 by Femi Omojola (fomojola@ideasynthesis.com). - */ -package org.java_websocket; - -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.SSLSession; -import java.io.EOFException; -import java.io.IOException; -import java.net.Socket; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.ByteChannel; -import java.nio.channels.SelectableChannel; -import java.nio.channels.SelectionKey; -import java.nio.channels.SocketChannel; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; - -/** - * Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper. - */ -public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { - /** - * This object is used to feed the {@link SSLEngine}'s wrap and unwrap methods during the handshake phase. - **/ - protected static ByteBuffer emptybuffer = ByteBuffer.allocate( 0 ); - - protected ExecutorService exec; - - protected List> tasks; - - /** raw payload incomming */ - protected ByteBuffer inData; - /** encrypted data outgoing */ - protected ByteBuffer outCrypt; - /** encrypted data incoming */ - protected ByteBuffer inCrypt; - - /** the underlying channel */ - protected SocketChannel socketChannel; - /** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */ - protected SelectionKey selectionKey; - - protected SSLEngine sslEngine; - protected SSLEngineResult readEngineResult; - protected SSLEngineResult writeEngineResult; - - /** - * Should be used to count the buffer allocations. - * But because of #190 where HandshakeStatus.FINISHED is not properly returned by nio wrap/unwrap this variable is used to check whether {@link #createBuffers(SSLSession)} needs to be called. - **/ - protected int bufferallocations = 0; - - public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { - if( channel == null || sslEngine == null || exec == null ) - throw new IllegalArgumentException( "parameter must not be null" ); - - this.socketChannel = channel; - this.sslEngine = sslEngine; - this.exec = exec; - - readEngineResult = writeEngineResult = new SSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs - - tasks = new ArrayList>( 3 ); - if( key != null ) { - key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); - this.selectionKey = key; - } - createBuffers( sslEngine.getSession() ); - // kick off handshake - socketChannel.write( wrap( emptybuffer ) );// initializes res - processHandshake(); - } - - private void consumeFutureUninterruptible( Future f ) { - try { - boolean interrupted = false; - while ( true ) { - try { - f.get(); - break; - } catch ( InterruptedException e ) { - interrupted = true; - } - } - if( interrupted ) - Thread.currentThread().interrupt(); - } catch ( ExecutionException e ) { - throw new RuntimeException( e ); - } - } - - /** - * This method will do whatever necessary to process the sslengine handshake. - * Thats why it's called both from the {@link #read(ByteBuffer)} and {@link #write(ByteBuffer)} - **/ - private synchronized void processHandshake() throws IOException { - if( sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) - return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking. - if( !tasks.isEmpty() ) { - Iterator> it = tasks.iterator(); - while ( it.hasNext() ) { - Future f = it.next(); - if( f.isDone() ) { - it.remove(); - } else { - if( isBlocking() ) - consumeFutureUninterruptible( f ); - return; - } - } - } - - if( sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) { - if( !isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) { - inCrypt.compact(); - int read = socketChannel.read( inCrypt ); - if( read == -1 ) { - throw new IOException( "connection closed unexpectedly by peer" ); - } - inCrypt.flip(); - } - inData.compact(); - unwrap(); - if( readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { - createBuffers( sslEngine.getSession() ); - return; - } - } - consumeDelegatedTasks(); - if( tasks.isEmpty() || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) { - socketChannel.write( wrap( emptybuffer ) ); - if( writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { - createBuffers( sslEngine.getSession() ); - return; - } - } - assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );// this function could only leave NOT_HANDSHAKING after createBuffers was called unless #190 occurs which means that nio wrap/unwrap never return HandshakeStatus.FINISHED - - bufferallocations = 1; // look at variable declaration why this line exists and #190. Without this line buffers would not be be recreated when #190 AND a rehandshake occur. - } - private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException { - outCrypt.compact(); - writeEngineResult = sslEngine.wrap( b, outCrypt ); - outCrypt.flip(); - return outCrypt; - } - - /** - * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData} - **/ - private synchronized ByteBuffer unwrap() throws SSLException { - int rem; - //There are some ssl test suites, which get around the selector.select() call, which cause an infinite unwrap and 100% cpu usage (see #459 and #458) - if(readEngineResult.getStatus() == SSLEngineResult.Status.CLOSED && sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING){ - try { - close(); - } catch (IOException e) { - //Not really interesting - } - } - do { - rem = inData.remaining(); - readEngineResult = sslEngine.unwrap( inCrypt, inData ); - } while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); - inData.flip(); - return inData; - } - - protected void consumeDelegatedTasks() { - Runnable task; - while ( ( task = sslEngine.getDelegatedTask() ) != null ) { - tasks.add( exec.submit( task ) ); - // task.run(); - } - } - - protected void createBuffers( SSLSession session ) { - int netBufferMax = session.getPacketBufferSize(); - int appBufferMax = Math.max(session.getApplicationBufferSize(), netBufferMax); - - if( inData == null ) { - inData = ByteBuffer.allocate( appBufferMax ); - outCrypt = ByteBuffer.allocate( netBufferMax ); - inCrypt = ByteBuffer.allocate( netBufferMax ); - } else { - if( inData.capacity() != appBufferMax ) - inData = ByteBuffer.allocate( appBufferMax ); - if( outCrypt.capacity() != netBufferMax ) - outCrypt = ByteBuffer.allocate( netBufferMax ); - if( inCrypt.capacity() != netBufferMax ) - inCrypt = ByteBuffer.allocate( netBufferMax ); - } - inData.rewind(); - inData.flip(); - inCrypt.rewind(); - inCrypt.flip(); - outCrypt.rewind(); - outCrypt.flip(); - bufferallocations++; - } - - public int write( ByteBuffer src ) throws IOException { - if( !isHandShakeComplete() ) { - processHandshake(); - return 0; - } - // assert ( bufferallocations > 1 ); //see #190 - //if( bufferallocations <= 1 ) { - // createBuffers( sslEngine.getSession() ); - //} - int num = socketChannel.write( wrap( src ) ); - if (writeEngineResult.getStatus() == SSLEngineResult.Status.CLOSED) { - throw new EOFException("Connection is closed"); - } - return num; - - } - - /** - * Blocks when in blocking mode until at least one byte has been decoded.
- * When not in blocking mode 0 may be returned. - * - * @return the number of bytes read. - **/ - public int read(ByteBuffer dst) throws IOException { - while (true) { - if (!dst.hasRemaining()) - return 0; - if (!isHandShakeComplete()) { - if (isBlocking()) { - while (!isHandShakeComplete()) { - processHandshake(); - } - } else { - processHandshake(); - if (!isHandShakeComplete()) { - return 0; - } - } - } - // assert ( bufferallocations > 1 ); //see #190 - //if( bufferallocations <= 1 ) { - // createBuffers( sslEngine.getSession() ); - //} - /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call. - * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining) - */ - int purged = readRemaining(dst); - if (purged != 0) - return purged; - - /* We only continue when we really need more data from the network. - * Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption - */ - assert (inData.position() == 0); - inData.clear(); - - if (!inCrypt.hasRemaining()) - inCrypt.clear(); - else - inCrypt.compact(); - - if (isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW) - if (socketChannel.read(inCrypt) == -1) { - return -1; - } - inCrypt.flip(); - unwrap(); - - int transfered = transfereTo(inData, dst); - if (transfered == 0 && isBlocking()) { - continue; - } - return transfered; - } - } - /** - * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt) - **/ - private int readRemaining( ByteBuffer dst ) throws SSLException { - if( inData.hasRemaining() ) { - return transfereTo( inData, dst ); - } - if( !inData.hasRemaining() ) - inData.clear(); - // test if some bytes left from last read (e.g. BUFFER_UNDERFLOW) - if( inCrypt.hasRemaining() ) { - unwrap(); - int amount = transfereTo( inData, dst ); - if (readEngineResult.getStatus() == SSLEngineResult.Status.CLOSED) { - return -1; - } - if( amount > 0 ) - return amount; - } - return 0; - } - - public boolean isConnected() { - return socketChannel.isConnected(); - } - - public void close() throws IOException { - sslEngine.closeOutbound(); - sslEngine.getSession().invalidate(); - if( socketChannel.isOpen() ) - socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written - socketChannel.close(); - } - - private boolean isHandShakeComplete() { - HandshakeStatus status = sslEngine.getHandshakeStatus(); - return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; - } - - public SelectableChannel configureBlocking( boolean b ) throws IOException { - return socketChannel.configureBlocking( b ); - } - - public boolean connect( SocketAddress remote ) throws IOException { - return socketChannel.connect( remote ); - } - - public boolean finishConnect() throws IOException { - return socketChannel.finishConnect(); - } - - public Socket socket() { - return socketChannel.socket(); - } - - public boolean isInboundDone() { - return sslEngine.isInboundDone(); - } - - @Override - public boolean isOpen() { - return socketChannel.isOpen(); - } - - @Override - public boolean isNeedWrite() { - return outCrypt.hasRemaining() || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow - } - - @Override - public void writeMore() throws IOException { - write( outCrypt ); - } - - @Override - public boolean isNeedRead() { - return inData.hasRemaining() || ( inCrypt.hasRemaining() && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW && readEngineResult.getStatus() != Status.CLOSED ); - } - - @Override - public int readMore( ByteBuffer dst ) throws SSLException { - return readRemaining( dst ); - } - - private int transfereTo( ByteBuffer from, ByteBuffer to ) { - int fremain = from.remaining(); - int toremain = to.remaining(); - if( fremain > toremain ) { - // FIXME there should be a more efficient transfer method - int limit = Math.min( fremain, toremain ); - for( int i = 0 ; i < limit ; i++ ) { - to.put( from.get() ); - } - return limit; - } else { - to.put( from ); - return fremain; - } - - } - - @Override - public boolean isBlocking() { - return socketChannel.isBlocking(); - } - -} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java index 011951e30..f3124d082 100644 --- a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java +++ b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java @@ -1,6 +1,5 @@ package org.java_websocket.server; import java.io.IOException; -import java.net.Socket; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; @@ -13,7 +12,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; -import org.java_websocket.SSLSocketChannel2; +import org.java_websocket.SSLSocketChannel; import org.java_websocket.WebSocketAdapter; import org.java_websocket.WebSocketImpl; import org.java_websocket.drafts.Draft; @@ -47,7 +46,7 @@ public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); e.setEnabledCipherSuites( ciphers.toArray(new String[]{})); e.setUseClientMode( false ); - return new SSLSocketChannel2( channel, e, exec, key ); + return new SSLSocketChannel( channel, e, exec, key ); } @Override diff --git a/src/main/java/org/java_websocket/util/ByteBufferUtils.java b/src/main/java/org/java_websocket/util/ByteBufferUtils.java index 171905b88..17297be39 100644 --- a/src/main/java/org/java_websocket/util/ByteBufferUtils.java +++ b/src/main/java/org/java_websocket/util/ByteBufferUtils.java @@ -19,19 +19,21 @@ private ByteBufferUtils() { * @param source the ByteBuffer to copy from * @param dest the ByteBuffer to copy to */ - public static void transferByteBuffer( ByteBuffer source, ByteBuffer dest ) { + public static int transferByteBuffer( ByteBuffer source, ByteBuffer dest ) { if( source == null || dest == null ) { throw new IllegalArgumentException(); } int fremain = source.remaining(); int toremain = dest.remaining(); if( fremain > toremain ) { - source.limit( Math.min( fremain, toremain ) ); + int limit = Math.min( fremain, toremain ); + source.limit( limit ); dest.put( source ); + return limit; } else { dest.put( source ); + return fremain; } - } /**