Skip to content

Commit 85390d5

Browse files
committed
Merge pull request TooTallNate#193 from Davidiusdadi/master
fixed critical bugs causing random protocol errors and bug that caused server to hang on stop()
2 parents 53fced8 + 158b9bc commit 85390d5

File tree

3 files changed

+63
-36
lines changed

3 files changed

+63
-36
lines changed

‎src/main/java/org/java_websocket/SSLSocketChannel2.java‎

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
* Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper.
3232
*/
3333
publicclassSSLSocketChannel2implementsByteChannel, WrappedByteChannel{
34+
/**
35+
* This object is used to feed the{@link SSLEngine}'s wrap and unwrap methods during the handshake phase.
36+
**/
3437
protectedstaticByteBufferemptybuffer = ByteBuffer.allocate( 0 );
3538

3639
protectedExecutorServiceexec;
@@ -49,12 +52,9 @@ public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel{
4952
/** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */
5053
protectedSelectionKeyselectionKey;
5154

52-
53-
protectedSSLEngineResultengineResult;
5455
protectedSSLEnginesslEngine;
55-
56-
57-
privateStatusengineStatus = Status.BUFFER_UNDERFLOW;
56+
protectedSSLEngineResultreadEngineResult;
57+
protectedSSLEngineResultwriteEngineResult;
5858

5959
publicSSLSocketChannel2( SocketChannelchannel , SSLEnginesslEngine , ExecutorServiceexec , SelectionKeykey ) throwsIOException{
6060
if( channel == null || sslEngine == null || exec == null )
@@ -64,6 +64,8 @@ public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , Executor
6464
this.sslEngine = sslEngine;
6565
this.exec = exec;
6666

67+
readEngineResult = writeEngineResult = newSSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs
68+
6769
tasks = newArrayList<Future<?>>( 3 );
6870
if( key != null ){
6971
key.interestOps( key.interestOps() | SelectionKey.OP_WRITE );
@@ -93,8 +95,12 @@ private void consumeFutureUninterruptible( Future<?> f ){
9395
}
9496
}
9597

98+
/**
99+
* This method will do whatever necessary to process the sslengine handshake.
100+
* Thats why it's called both from the{@link #read(ByteBuffer)} and{@link #write(ByteBuffer)}
101+
**/
96102
privatesynchronizedvoidprocessHandshake() throwsIOException{
97-
if( engineResult.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING )
103+
if( sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING )
98104
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.
99105
if( !tasks.isEmpty() ){
100106
Iterator<Future<?>> it = tasks.iterator();
@@ -110,8 +116,8 @@ private synchronized void processHandshake() throws IOException{
110116
}
111117
}
112118

113-
if( engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ){
114-
if( !isBlocking() || engineStatus == Status.BUFFER_UNDERFLOW ){
119+
if( sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ){
120+
if( !isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ){
115121
inCrypt.compact();
116122
intread = socketChannel.read( inCrypt );
117123
if( read == -1 ){
@@ -121,36 +127,37 @@ private synchronized void processHandshake() throws IOException{
121127
}
122128
inData.compact();
123129
unwrap();
124-
if( engineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ){
130+
if( sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED ){
125131
createBuffers( sslEngine.getSession() );
126132
return;
127133
}
128134
}
129135
consumeDelegatedTasks();
130-
assert ( engineResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );
131-
if( tasks.isEmpty() || engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ){
136+
assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );
137+
if( tasks.isEmpty() || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ){
132138
socketChannel.write( wrap( emptybuffer ) );
133-
if( engineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ){
139+
if( sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED ){
134140
createBuffers( sslEngine.getSession() );
135141
}
136142
}
137143
}
138144

139145
privatesynchronizedByteBufferwrap( ByteBufferb ) throwsSSLException{
140146
outCrypt.compact();
141-
engineResult = sslEngine.wrap( b, outCrypt );
147+
writeEngineResult = sslEngine.wrap( b, outCrypt );
142148
outCrypt.flip();
143149
returnoutCrypt;
144150
}
145151

152+
/**
153+
* performs the unwrap operation by unwrapping from{@link #inCrypt} to{@link #inData}
154+
**/
146155
privatesynchronizedByteBufferunwrap() throwsSSLException{
147156
intrem;
148157
do{
149158
rem = inData.remaining();
150-
engineResult = sslEngine.unwrap( inCrypt, inData );
151-
engineStatus = engineResult.getStatus();
152-
} while ( engineStatus == SSLEngineResult.Status.OK && ( rem != inData.remaining() || engineResult.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) );
153-
159+
readEngineResult = sslEngine.unwrap( inCrypt, inData );
160+
} while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) );
154161
inData.flip();
155162
returninData;
156163
}
@@ -200,6 +207,8 @@ public int write( ByteBuffer src ) throws IOException{
200207
/**
201208
* Blocks when in blocking mode until at least one byte has been decoded.<br>
202209
* When not in blocking mode 0 may be returned.
210+
*
211+
* @return the number of bytes read.
203212
**/
204213
publicintread( ByteBufferdst ) throwsIOException{
205214
if( !dst.hasRemaining() )
@@ -216,11 +225,16 @@ public int read( ByteBuffer dst ) throws IOException{
216225
}
217226
}
218227
}
219-
228+
/* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call.
229+
* 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining)
230+
*/
220231
intpurged = readRemaining( dst );
221232
if( purged != 0 )
222233
returnpurged;
223234

235+
/* We only continue when we really need more data from the network.
236+
* Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption
237+
*/
224238
assert ( inData.position() == 0 );
225239
inData.clear();
226240

@@ -229,7 +243,7 @@ public int read( ByteBuffer dst ) throws IOException{
229243
else
230244
inCrypt.compact();
231245

232-
if( ( isBlocking() && inCrypt.position() == 0 ) || engineStatus == Status.BUFFER_UNDERFLOW )
246+
if( isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW )
233247
if( socketChannel.read( inCrypt ) == -1 ){
234248
return -1;
235249
}
@@ -243,6 +257,9 @@ public int read( ByteBuffer dst ) throws IOException{
243257
returntransfered;
244258
}
245259

260+
/**
261+
*{@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt)
262+
**/
246263
privateintreadRemaining( ByteBufferdst ) throwsSSLException{
247264
if( inData.hasRemaining() ){
248265
returntransfereTo( inData, dst );
@@ -273,7 +290,7 @@ public void close() throws IOException{
273290
}
274291

275292
privatebooleanisHandShakeComplete(){
276-
HandshakeStatusstatus = engineResult.getHandshakeStatus();
293+
HandshakeStatusstatus = sslEngine.getHandshakeStatus();
277294
returnstatus == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
278295
}
279296

@@ -304,7 +321,7 @@ public boolean isOpen(){
304321

305322
@Override
306323
publicbooleanisNeedWrite(){
307-
returnoutCrypt.hasRemaining() || !isHandShakeComplete();
324+
returnoutCrypt.hasRemaining() || !isHandShakeComplete();// FIXME this condition can cause high cpu load during handshaking when network is slow
308325
}
309326

310327
@Override
@@ -314,8 +331,7 @@ public void writeMore() throws IOException{
314331

315332
@Override
316333
publicbooleanisNeedRead(){
317-
returninData.hasRemaining() || ( inCrypt.hasRemaining() && engineResult.getStatus() != Status.BUFFER_UNDERFLOW &&
318-
engineResult.getStatus() != Status.CLOSED );
334+
returninData.hasRemaining() || ( inCrypt.hasRemaining() && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW && readEngineResult.getStatus() != Status.CLOSED );
319335
}
320336

321337
@Override

‎src/main/java/org/java_websocket/WebSocketImpl.java‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public class WebSocketImpl implements WebSocket{
9393
privateOpcodecurrent_continuous_frame_opcode = null;
9494

9595
/** the bytes of an incomplete received handshake */
96-
privateByteBuffertmpHandshakeBytes;
96+
privateByteBuffertmpHandshakeBytes = ByteBuffer.allocate( 0 );
9797

9898
/** stores the handshake sent by this websocket ( Role.CLIENT only ) */
9999
privateClientHandshakehandshakerequest = null;
@@ -149,8 +149,11 @@ public WebSocketImpl( WebSocketListener listener , List<Draft> drafts , Socket s
149149
*
150150
*/
151151
publicvoiddecode( ByteBuffersocketBuffer ){
152-
if( !socketBuffer.hasRemaining() || flushandclosestate )
152+
assert ( socketBuffer.hasRemaining() );
153+
154+
if( flushandclosestate ){
153155
return;
156+
}
154157

155158
if( DEBUG )
156159
System.out.println( "process(" + socketBuffer.remaining() + "):{" + ( socketBuffer.remaining() > 1000 ? "too big to display" : newString( socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining() ) ) + "}" );
@@ -159,19 +162,24 @@ public void decode( ByteBuffer socketBuffer ){
159162
decodeFrames( socketBuffer );
160163
} else{
161164
if( decodeHandshake( socketBuffer ) ){
162-
decodeFrames( socketBuffer );
165+
assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time
166+
167+
if( socketBuffer.hasRemaining() ){
168+
decodeFrames( socketBuffer );
169+
} elseif( tmpHandshakeBytes.hasRemaining() ){
170+
decodeFrames( tmpHandshakeBytes );
171+
}
163172
}
164173
}
165174
assert ( isClosing() || isFlushAndClose() || !socketBuffer.hasRemaining() );
166175
}
167-
168176
/**
169177
* Returns whether the handshake phase has is completed.
170178
* In case of a broken handshake this will be never the case.
171179
**/
172180
privatebooleandecodeHandshake( ByteBuffersocketBufferNew ){
173181
ByteBuffersocketBuffer;
174-
if( tmpHandshakeBytes == null ){
182+
if( tmpHandshakeBytes.capacity() == 0 ){
175183
socketBuffer = socketBufferNew;
176184
} else{
177185
if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ){
@@ -260,7 +268,7 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ){
260268
draft.setParseMode( role );
261269
Handshakedatatmphandshake = draft.translateHandshake( socketBuffer );
262270
if( tmphandshakeinstanceofServerHandshake == false ){
263-
flushAndClose( CloseFrame.PROTOCOL_ERROR, "Wwrong http function", false );
271+
flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
264272
returnfalse;
265273
}
266274
ServerHandshakehandshake = (ServerHandshake) tmphandshake;
@@ -286,7 +294,7 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ){
286294
close( e );
287295
}
288296
} catch ( IncompleteHandshakeExceptione ){
289-
if( tmpHandshakeBytes == null ){
297+
if( tmpHandshakeBytes.capacity() == 0 ){
290298
socketBuffer.reset();
291299
intnewsize = e.getPreferedSize();
292300
if( newsize == 0 ){

‎src/main/java/org/java_websocket/server/WebSocketServer.java‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
importjava.nio.ByteBuffer;
99
importjava.nio.channels.ByteChannel;
1010
importjava.nio.channels.CancelledKeyException;
11+
importjava.nio.channels.ClosedByInterruptException;
1112
importjava.nio.channels.SelectableChannel;
1213
importjava.nio.channels.SelectionKey;
1314
importjava.nio.channels.Selector;
@@ -325,6 +326,7 @@ public void run(){
325326
ByteBufferbuf = takeBuffer();
326327
try{
327328
if( SocketChannelIOHelper.read( buf, conn, (ByteChannel) conn.channel ) ){
329+
assert ( buf.hasRemaining() );
328330
conn.inQueue.put( buf );
329331
queue( conn );
330332
i.remove();
@@ -339,9 +341,6 @@ public void run(){
339341
} catch ( IOExceptione ){
340342
pushBuffer( buf );
341343
throwe;
342-
} catch ( RuntimeExceptione ){
343-
pushBuffer( buf );
344-
throwe;
345344
}
346345
}
347346
if( key.isWritable() ){
@@ -359,21 +358,25 @@ public void run(){
359358
try{
360359
if( SocketChannelIOHelper.readMore( buf, conn, c ) )
361360
iqueue.add( conn );
361+
assert ( buf.hasRemaining() );
362362
conn.inQueue.put( buf );
363363
queue( conn );
364-
} finally{
364+
} catch ( IOExceptione ){
365365
pushBuffer( buf );
366+
throwe;
366367
}
367368

368369
}
369370
} catch ( CancelledKeyExceptione ){
370371
// an other thread may cancel the key
372+
} catch ( ClosedByInterruptExceptione ){
373+
return; // do the same stuff as when InterruptedException is thrown
371374
} catch ( IOExceptionex ){
372375
if( key != null )
373376
key.cancel();
374377
handleIOException( key, conn, ex );
375378
} catch ( InterruptedExceptione ){
376-
return;// FIXME controlled shutdown
379+
return;// FIXME controlled shutdown (e.g. take care of buffermanagement)
377380
}
378381
}
379382

@@ -418,7 +421,7 @@ private void pushBuffer( ByteBuffer buf ) throws InterruptedException{
418421
}
419422

420423
privatevoidhandleIOException( SelectionKeykey, WebSocketconn, IOExceptionex ){
421-
//onWebsocketError( conn, ex );// conn may be null here
424+
//onWebsocketError( conn, ex );// conn may be null here
422425
if( conn != null ){
423426
conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, ex.getMessage() );
424427
} elseif( key != null ){

0 commit comments

Comments
(0)