Skip to content

Commit c080657

Browse files
authored
Merge pull request TooTallNate#527 from marci4/master
Infrastructure for websocket extension
2 parents 657262f + 283c014 commit c080657

File tree

9 files changed

+568
-32
lines changed

9 files changed

+568
-32
lines changed

src/main/java/org/java_websocket/AbstractWebSocket.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public void setConnectionLostTimeout( int connectionLostTimeout ) {
8787
protected void stopConnectionLostTimer() {
8888
if (connectionLostTimer != null ||connectionLostTimerTask != null) {
8989
if( WebSocketImpl.DEBUG )
90-
System.out.println( "Connection lost timer stoped" );
90+
System.out.println( "Connection lost timer stopped" );
9191
cancelConnectionLostTimer();
9292
}
9393
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ private void write( List<ByteBuffer> bufs ) {
749749

750750
private void open( Handshakedata d ) {
751751
if( DEBUG )
752-
System.out.println( "open using draft: " + draft.getClass().getSimpleName() );
752+
System.out.println( "open using draft: " + draft );
753753
readystate = READYSTATE.OPEN;
754754
try {
755755
wsl.onWebsocketOpen( this, d );

src/main/java/org/java_websocket/drafts/Draft.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,8 @@ public Role getRole() {
263263
return role;
264264
}
265265

266+
public String toString() {
267+
return getClass().getSimpleName();
268+
}
269+
266270
}

src/main/java/org/java_websocket/drafts/Draft_10.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
@Deprecated
4646
public class Draft_10 extends Draft {
4747

48-
private class IncompleteException extends Throwable {
48+
class IncompleteException extends Throwable {
4949

5050
/**
5151
* It's Serializable.
@@ -77,7 +77,7 @@ public static int readVersion( Handshakedata handshakedata ) {
7777
return -1;
7878
}
7979

80-
private ByteBuffer incompleteframe;
80+
ByteBuffer incompleteframe;
8181

8282
private final Random reuseableRandom = new Random();
8383

@@ -232,7 +232,7 @@ private byte[] toByteArray( long val, int bytecount ) {
232232
return buffer;
233233
}
234234

235-
private Opcode toOpcode( byte opcode ) throws InvalidFrameException {
235+
Opcode toOpcode( byte opcode ) throws InvalidFrameException {
236236
switch(opcode) {
237237
case 0:
238238
return Opcode.CONTINUOUS;

src/main/java/org/java_websocket/drafts/Draft_6455.java

Lines changed: 302 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,22 @@
2525

2626
package org.java_websocket.drafts;
2727

28+
import org.java_websocket.WebSocketImpl;
29+
import org.java_websocket.exceptions.InvalidDataException;
30+
import org.java_websocket.exceptions.InvalidFrameException;
2831
import org.java_websocket.exceptions.InvalidHandshakeException;
29-
import org.java_websocket.handshake.ClientHandshake;
30-
import org.java_websocket.handshake.HandshakeBuilder;
31-
import org.java_websocket.handshake.ServerHandshakeBuilder;
32+
import org.java_websocket.exceptions.LimitExedeedException;
33+
import org.java_websocket.extensions.DefaultExtension;
34+
import org.java_websocket.extensions.IExtension;
35+
import org.java_websocket.framing.Framedata;
36+
import org.java_websocket.framing.FramedataImpl1;
37+
import org.java_websocket.handshake.*;
38+
import org.java_websocket.util.Charsetfunctions;
3239

40+
import java.math.BigInteger;
41+
import java.nio.ByteBuffer;
3342
import java.text.SimpleDateFormat;
34-
import java.util.Calendar;
35-
import java.util.Locale;
36-
import java.util.TimeZone;
43+
import java.util.*;
3744

3845
/**
3946
* Implementation for the RFC 6455 websocket protocol
@@ -42,25 +49,293 @@
4249
@SuppressWarnings("deprecation")
4350
public class Draft_6455 extends Draft_17 {
4451

45-
@Override
46-
public HandshakeBuilder postProcessHandshakeResponseAsServer(ClientHandshake request, ServerHandshakeBuilder response) throws InvalidHandshakeException {
47-
super.postProcessHandshakeResponseAsServer(request, response);
48-
response.setHttpStatusMessage("Web Socket Protocol Handshake");
49-
response.put("Server", "TooTallNate Java-WebSocket");
50-
response.put("Date", getServerTime());
51-
return response;
52-
}
53-
54-
@Override
55-
public Draft copyInstance() {
56-
return new Draft_6455();
57-
}
58-
59-
private String getServerTime() {
60-
Calendar calendar = Calendar.getInstance();
61-
SimpleDateFormat dateFormat = new SimpleDateFormat(
62-
"EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
63-
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
64-
return dateFormat.format(calendar.getTime());
65-
}
52+
/**
53+
* Attribute for the used extension in this draft
54+
*/
55+
private IExtension extension;
56+
57+
/**
58+
* Attribute for all available extension in this draft
59+
*/
60+
private List<IExtension> knownExtensions;
61+
62+
/**
63+
* Constructor for the websocket protocol specified by RFC 6455 with default extensions
64+
*/
65+
public Draft_6455() {
66+
this( Collections.<IExtension>emptyList() );
67+
}
68+
69+
/**
70+
* Constructor for the websocket protocol specified by RFC 6455 with custom extensions
71+
*
72+
* @param inputExtension the extension which should be used for this draft
73+
*/
74+
public Draft_6455( IExtension inputExtension ) {
75+
this( Collections.singletonList( inputExtension ) );
76+
}
77+
78+
/**
79+
* Constructor for the websocket protocol specified by RFC 6455 with custom extensions
80+
*
81+
* @param inputExtensions the extensions which should be used for this draft
82+
*/
83+
public Draft_6455( List<IExtension> inputExtensions ) {
84+
knownExtensions = new ArrayList<IExtension>();
85+
boolean hasDefault = false;
86+
for( IExtension inputExtension : inputExtensions ) {
87+
if( inputExtension.getClass().equals( DefaultExtension.class ) ) {
88+
hasDefault = true;
89+
}
90+
}
91+
knownExtensions.addAll( inputExtensions );
92+
//We always add the DefaultExtension to implement the normal RFC 6455 specification
93+
if( !hasDefault ) {
94+
DefaultExtension defaultExtension = new DefaultExtension();
95+
knownExtensions.add( this.knownExtensions.size(), defaultExtension );
96+
}
97+
}
98+
99+
@Override
100+
public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException {
101+
if( super.acceptHandshakeAsServer( handshakedata ) == HandshakeState.NOT_MATCHED ) {
102+
return HandshakeState.NOT_MATCHED;
103+
}
104+
String requestedExtension = handshakedata.getFieldValue( "Sec-WebSocket-Extensions" );
105+
for( IExtension knownExtension : knownExtensions ) {
106+
if( knownExtension.acceptProvidedExtensionAsServer( requestedExtension ) ) {
107+
extension = knownExtension;
108+
return HandshakeState.MATCHED;
109+
}
110+
}
111+
return HandshakeState.NOT_MATCHED;
112+
}
113+
114+
115+
@Override
116+
public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException {
117+
if( super.acceptHandshakeAsClient( request, response ) == HandshakeState.NOT_MATCHED ) {
118+
return HandshakeState.NOT_MATCHED;
119+
}
120+
String requestedExtension = response.getFieldValue( "Sec-WebSocket-Extensions" );
121+
for( IExtension knownExtension : knownExtensions ) {
122+
if( knownExtension.acceptProvidedExtensionAsClient( requestedExtension ) ) {
123+
extension = knownExtension;
124+
return HandshakeState.MATCHED;
125+
}
126+
}
127+
return HandshakeState.NOT_MATCHED;
128+
}
129+
130+
/**
131+
* Getter for the extension which is used by this draft
132+
*
133+
* @return the extension which is used or null, if handshake is not yet done
134+
*/
135+
public IExtension getExtension() {
136+
return extension;
137+
}
138+
139+
@Override
140+
public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) {
141+
super.postProcessHandshakeRequestAsClient( request );
142+
StringBuilder requestedExtensions = new StringBuilder();
143+
for( IExtension knownExtension : knownExtensions ) {
144+
if( knownExtension.getProvidedExtensionAsClient() != null && !knownExtension.getProvidedExtensionAsClient().equals( "" ) ) {
145+
requestedExtensions.append( knownExtension.getProvidedExtensionAsClient() ).append( "; " );
146+
}
147+
}
148+
if( requestedExtensions.length() != 0 ) {
149+
request.put( "Sec-WebSocket-Extensions", requestedExtensions.toString() );
150+
}
151+
return request;
152+
}
153+
154+
@Override
155+
public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder
156+
response ) throws InvalidHandshakeException {
157+
super.postProcessHandshakeResponseAsServer( request, response );
158+
if( getExtension().getProvidedExtensionAsServer().length() != 0 ) {
159+
response.put( "Sec-WebSocket-Extensions", getExtension().getProvidedExtensionAsServer() );
160+
}
161+
response.setHttpStatusMessage( "Web Socket Protocol Handshake" );
162+
response.put( "Server", "TooTallNate Java-WebSocket" );
163+
response.put( "Date", getServerTime() );
164+
return response;
165+
}
166+
167+
168+
@Override
169+
public Draft copyInstance() {
170+
ArrayList<IExtension> newExtensions = new ArrayList<IExtension>();
171+
for( IExtension extension : knownExtensions ) {
172+
newExtensions.add( extension.copyInstance() );
173+
}
174+
return new Draft_6455( newExtensions );
175+
}
176+
177+
@Override
178+
public ByteBuffer createBinaryFrame( Framedata framedata ) {
179+
getExtension().encodeFrame( framedata );
180+
return super.createBinaryFrame( framedata );
181+
}
182+
183+
@Override
184+
public Framedata translateSingleFrame( ByteBuffer buffer ) throws IncompleteException, InvalidDataException {
185+
int maxpacketsize = buffer.remaining();
186+
int realpacketsize = 2;
187+
if( maxpacketsize < realpacketsize )
188+
throw new IncompleteException( realpacketsize );
189+
byte b1 = buffer.get( /*0*/ );
190+
boolean FIN = b1 >> 8 != 0;
191+
boolean rsv1 = false, rsv2 = false, rsv3 = false;
192+
if( ( b1 & 0x40 ) != 0 ) {
193+
rsv1 = true;
194+
}
195+
if( ( b1 & 0x20 ) != 0 ) {
196+
rsv2 = true;
197+
}
198+
if( ( b1 & 0x10 ) != 0 ) {
199+
rsv3 = true;
200+
}
201+
byte b2 = buffer.get( /*1*/ );
202+
boolean MASK = ( b2 & -128 ) != 0;
203+
int payloadlength = ( byte ) ( b2 & ~( byte ) 128 );
204+
Framedata.Opcode optcode = toOpcode( ( byte ) ( b1 & 15 ) );
205+
206+
if( !( payloadlength >= 0 && payloadlength <= 125 ) ) {
207+
if( optcode == Framedata.Opcode.PING || optcode == Framedata.Opcode.PONG || optcode == Framedata.Opcode.CLOSING ) {
208+
throw new InvalidFrameException( "more than 125 octets" );
209+
}
210+
if( payloadlength == 126 ) {
211+
realpacketsize += 2; // additional length bytes
212+
if( maxpacketsize < realpacketsize )
213+
throw new IncompleteException( realpacketsize );
214+
byte[] sizebytes = new byte[3];
215+
sizebytes[1] = buffer.get( /*1 + 1*/ );
216+
sizebytes[2] = buffer.get( /*1 + 2*/ );
217+
payloadlength = new BigInteger( sizebytes ).intValue();
218+
} else {
219+
realpacketsize += 8; // additional length bytes
220+
if( maxpacketsize < realpacketsize )
221+
throw new IncompleteException( realpacketsize );
222+
byte[] bytes = new byte[8];
223+
for( int i = 0; i < 8; i++ ) {
224+
bytes[i] = buffer.get( /*1 + i*/ );
225+
}
226+
long length = new BigInteger( bytes ).longValue();
227+
if( length > Integer.MAX_VALUE ) {
228+
throw new LimitExedeedException( "Payloadsize is to big..." );
229+
} else {
230+
payloadlength = ( int ) length;
231+
}
232+
}
233+
}
234+
235+
// int maskskeystart = foff + realpacketsize;
236+
realpacketsize += ( MASK ? 4 : 0 );
237+
// int payloadstart = foff + realpacketsize;
238+
realpacketsize += payloadlength;
239+
240+
if( maxpacketsize < realpacketsize )
241+
throw new IncompleteException( realpacketsize );
242+
243+
ByteBuffer payload = ByteBuffer.allocate( checkAlloc( payloadlength ) );
244+
if( MASK ) {
245+
byte[] maskskey = new byte[4];
246+
buffer.get( maskskey );
247+
for( int i = 0; i < payloadlength; i++ ) {
248+
payload.put( ( byte ) ( buffer.get( /*payloadstart + i*/ ) ^ maskskey[i % 4] ) );
249+
}
250+
} else {
251+
payload.put( buffer.array(), buffer.position(), payload.limit() );
252+
buffer.position( buffer.position() + payload.limit() );
253+
}
254+
255+
FramedataImpl1 frame = FramedataImpl1.get( optcode );
256+
frame.setFin( FIN );
257+
frame.setRSV1( rsv1 );
258+
frame.setRSV2( rsv2 );
259+
frame.setRSV3( rsv3 );
260+
payload.flip();
261+
frame.setPayload( payload );
262+
getExtension().isFrameValid( frame );
263+
getExtension().decodeFrame( frame );
264+
if( WebSocketImpl.DEBUG )
265+
System.out.println( "Decode Payload after: " + Arrays.toString( Charsetfunctions.utf8Bytes( new String( frame.getPayloadData().array() ) ) ) );
266+
frame.isValid();
267+
return frame;
268+
}
269+
270+
271+
@Override
272+
public List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException {
273+
while( true ) {
274+
List<Framedata> frames = new LinkedList<Framedata>();
275+
Framedata cur;
276+
if( incompleteframe != null ) {
277+
// complete an incomplete frame
278+
try {
279+
buffer.mark();
280+
int available_next_byte_count = buffer.remaining();// The number of bytes received
281+
int expected_next_byte_count = incompleteframe.remaining();// The number of bytes to complete the incomplete frame
282+
283+
if( expected_next_byte_count > available_next_byte_count ) {
284+
// did not receive enough bytes to complete the frame
285+
incompleteframe.put( buffer.array(), buffer.position(), available_next_byte_count );
286+
buffer.position( buffer.position() + available_next_byte_count );
287+
return Collections.emptyList();
288+
}
289+
incompleteframe.put( buffer.array(), buffer.position(), expected_next_byte_count );
290+
buffer.position( buffer.position() + expected_next_byte_count );
291+
cur = translateSingleFrame( ( ByteBuffer ) incompleteframe.duplicate().position( 0 ) );
292+
frames.add( cur );
293+
incompleteframe = null;
294+
} catch ( IncompleteException e ) {
295+
// extending as much as suggested
296+
int oldsize = incompleteframe.limit();
297+
ByteBuffer extendedframe = ByteBuffer.allocate( checkAlloc( e.getPreferedSize() ) );
298+
assert ( extendedframe.limit() > incompleteframe.limit() );
299+
incompleteframe.rewind();
300+
extendedframe.put( incompleteframe );
301+
incompleteframe = extendedframe;
302+
continue;
303+
}
304+
}
305+
306+
while( buffer.hasRemaining() ) {// Read as much as possible full frames
307+
buffer.mark();
308+
try {
309+
cur = translateSingleFrame( buffer );
310+
frames.add( cur );
311+
} catch ( IncompleteException e ) {
312+
// remember the incomplete data
313+
buffer.reset();
314+
int pref = e.getPreferedSize();
315+
incompleteframe = ByteBuffer.allocate( checkAlloc( pref ) );
316+
incompleteframe.put( buffer );
317+
break;
318+
}
319+
}
320+
return frames;
321+
}
322+
}
323+
324+
/**
325+
* Generate a date for for the date-header
326+
*
327+
* @return the server time
328+
*/
329+
private String getServerTime() {
330+
Calendar calendar = Calendar.getInstance();
331+
SimpleDateFormat dateFormat = new SimpleDateFormat(
332+
"EEE, dd MMM yyyy HH:mm:ss z", Locale.US );
333+
dateFormat.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
334+
return dateFormat.format( calendar.getTime() );
335+
}
336+
337+
@Override
338+
public String toString() {
339+
return super.toString() + " extension: " + getExtension().toString();
340+
}
66341
}

0 commit comments

Comments
 (0)