headerMap;
226 | }
227 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/http/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * package-info.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | /**
20 | * Provides parsing of headers in HTTP protocol requests.
21 | */
22 | package com.pmeade.websocket.http;
23 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/io/LineInputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * LineInputStream.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.io;
20 |
21 | import java.io.ByteArrayOutputStream;
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.nio.charset.StandardCharsets;
25 |
26 | import static com.google.common.base.Preconditions.checkNotNull;
27 |
28 | /**
29 | * LineInputStream decorates an InputStream for line-by-line reading.
30 | * It implements a readLine()
method, similar to
31 | * BufferedReader
, but does not need an intermediate
32 | * InputStreamReader
to translate.
33 | *
34 | * The reason we avoid an InputStreamReader
is that it
35 | * will consume as much of the input as it possibly can in order
36 | * to optimize character encoding from the input bytes.
37 | *
38 | *
In our case, the input will be switching protocols from an HTTP
39 | * Request to WebSocket frames. We want to consume the HTTP Request
40 | * line by line and process it for a WebSocket handshake, but the
41 | * data following that must be handled in a completely different way.
42 | *
43 | *
The ability to handle the data line-by-line like
44 | * BufferedReader
is able to do, is very useful for
45 | * parsing the HTTP Reqeust. Therefore we have ListInputStream to
46 | * provide this functionality as an InputStream decorator.
47 | *
48 | *
Unlike BufferedReader
, which handles three kinds of
49 | * line endings (CR, LF, CRLF), the HTTP protocol has defined the line
50 | * ending to be CRLF. Therefore, the readLine()
method of
51 | * this decorator requires a CRLF (or EOF) sequence to terminate a line.
52 | *
53 | * @author blinkdog
54 | */
55 | public class LineInputStream extends InputStream {
56 | /**
57 | * Constant defining Carriage Return (CR). Octet 13, Hex 0x0c.
58 | */
59 | public static final int CR = 13;
60 |
61 | /**
62 | * Constant defining the end of the stream (EOF). This is derived
63 | * from the InputStream API. Calls to read()
return -1
64 | * when the end of the stream is reached.
65 | */
66 | public static final int EOF = -1;
67 |
68 | /**
69 | * Constant defining Line Feed (LF). Octet 10, Hex 0x0a
70 | */
71 | public static final int LF = 10;
72 |
73 | /**
74 | * Constant defining the canonical name of the UTF-8 character encoding.
75 | */
76 | public static final String UTF_8 = StandardCharsets.UTF_8.name();
77 |
78 | /**
79 | * Construct a LineInputStream to decorate the provided InputStream.
80 | * A NullPointerException
will be thrown if the provided
81 | * InputStream is null.
82 | * @param in InputStream to be decorated by this LineInputStream
83 | */
84 | public LineInputStream(final InputStream in) {
85 | checkNotNull(in);
86 | this.inputStream = in;
87 | }
88 |
89 | /**
90 | * {@inheritDoc}
91 | * @return the next byte of data, or -1
if the end of the
92 | * stream is reached.
93 | * @throws IOException if an I/O error occurs
94 | */
95 | @Override
96 | public final int read() throws IOException {
97 | return inputStream.read();
98 | }
99 |
100 | /**
101 | * Reads a line of text. A line is considered to be terminated by a
102 | * carriage return ('\r') followed immediately by a linefeed ('\n').
103 | * This is per the HTTP specification.
104 | * @return String containing the contents of the line, not including
105 | * any line-termination characters, or null if the end of the
106 | * stream has been reached
107 | * @throws IOException if an I/O error occurs
108 | */
109 | public final String readLine() throws IOException {
110 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
111 | boolean inputTaken = false;
112 | while (true) {
113 | int data = inputStream.read();
114 | // if this is the end of the stream
115 | if (data == EOF) {
116 | // if we've taken some input already
117 | if (inputTaken) {
118 | // return that input
119 | return baos.toString(UTF_8);
120 | } else {
121 | // otherwise return null
122 | return null;
123 | }
124 | // otherwise, if this is a CR
125 | } else if (data == CR) {
126 | // it may be the end of a line
127 | lastWasCarriageReturn = true;
128 | // otherwise, if this is a LF
129 | } else if (data == LF) {
130 | // if we did follow a CR
131 | if (lastWasCarriageReturn) {
132 | // then this is the end of a line
133 | lastWasCarriageReturn = false;
134 | return baos.toString(UTF_8);
135 | } else {
136 | inputTaken = true;
137 | lastWasCarriageReturn = false;
138 | baos.write(LF);
139 | }
140 | // otherwise...
141 | } else {
142 | // if the last thing was a carriage return
143 | if (lastWasCarriageReturn) {
144 | // write that CR to our line
145 | baos.write(CR);
146 | }
147 | // add the data we just read to the line
148 | inputTaken = true;
149 | lastWasCarriageReturn = false;
150 | baos.write(data);
151 | }
152 | }
153 | }
154 |
155 | /**
156 | * InputStream to be decorated by this LineInputStream. This reference
157 | * is provided at construction time.
158 | */
159 | private final InputStream inputStream;
160 |
161 | /**
162 | * Flag: Is the last character we processed a Carriage Return (Octet 13)?
163 | * true: Yes, the last character was a CR
164 | * false: No, the last character was not a CR
165 | */
166 | private boolean lastWasCarriageReturn = false;
167 | }
168 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/io/WebSocketServerInputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketServerInputStream.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.io;
20 |
21 | import com.google.common.hash.HashCode;
22 | import com.google.common.hash.HashFunction;
23 | import com.google.common.hash.Hashing;
24 | import com.google.common.io.BaseEncoding;
25 | import com.pmeade.websocket.http.HttpRequest;
26 | import java.io.IOException;
27 | import java.io.InputStream;
28 | import java.nio.charset.StandardCharsets;
29 |
30 | import static com.google.common.base.Preconditions.checkNotNull;
31 |
32 | /**
33 | * WebSocketServerInputStream decorates an InputStream to handle WebSocket
34 | * frames as specified in RFC 6455.
35 | * @author pmeade
36 | */
37 | public class WebSocketServerInputStream extends InputStream {
38 | /**
39 | * Constant indicating end of stream.
40 | */
41 | public static final int EOF = -1;
42 |
43 | /**
44 | * Number of bytes in the WebSocket handshake nonce.
45 | */
46 | public static final int HANDSHAKE_NONCE_LENGTH = 16;
47 |
48 | /**
49 | * Payload length indicating that the payload's true length is a
50 | * yet-to-be-provided unsigned 16-bit integer.
51 | */
52 | public static final int LENGTH_16 = 0x7E;
53 |
54 | /**
55 | * A payload specified with 16 bits must have at least this
56 | * length in order to be considered valid.
57 | */
58 | public static final int LENGTH_16_MIN = 126;
59 |
60 | /**
61 | * Payload length indicating that the payload's true length is a
62 | * yet-to-be-provided unsigned 64-bit integer (MSB = 0).
63 | */
64 | public static final int LENGTH_64 = 0x7F;
65 |
66 | /**
67 | * A payload specified with 64 bits must have at least this
68 | * length in order to be considered valid.
69 | */
70 | public static final int LENGTH_64_MIN = 0x10000;
71 |
72 | /**
73 | * Binary mask to limit an int to 8 bits (an unsigned byte).
74 | */
75 | public static final int MASK_BYTE = 0x000000FF;
76 |
77 | /**
78 | * Binary mask to extract the final fragment flag bit of a WebSocket frame.
79 | */
80 | public static final int MASK_FINAL = 0x80;
81 |
82 | /**
83 | * Binary mask to extract the masking flag bit of a WebSocket frame.
84 | */
85 | public static final int MASK_MASK = 0x80;
86 |
87 | /**
88 | * Binary mask to limit a value in the range [0-3] (inclusive).
89 | */
90 | public static final int MASK_MASKING_INDEX = 0x03;
91 |
92 | /**
93 | * Binary mask to extract the opcode bits of a WebSocket frame.
94 | */
95 | public static final int MASK_OPCODE = 0x0F;
96 |
97 | /**
98 | * Binary mask to extract the control bit of an opcode.
99 | */
100 | public static final int MASK_CONTROL_OPCODE = 0x08;
101 |
102 | /**
103 | * Binary mask to extract the payload size of a WebSocket frame.
104 | */
105 | public static final int MASK_PAYLOAD_SIZE = 0x7F;
106 |
107 | /**
108 | * Binary mask to extract the reserved flag bits of a WebSocket frame.
109 | */
110 | public static final int MASK_RESERVED = 0x70;
111 |
112 | /**
113 | * Number of masking bytes provided by the client.
114 | */
115 | public static final int NUM_MASKING_BYTES = 4;
116 |
117 | /**
118 | * Number of octets (bytes) in a 64-bit number.
119 | */
120 | public static final int NUM_OCTET_64 = 8;
121 |
122 | /**
123 | * Number of bits in an octet.
124 | */
125 | public static final int OCTET = 8;
126 |
127 | /**
128 | * WebSocket Opcode for a Continuation frame.
129 | */
130 | public static final int OPCODE_CONTINUATION = 0x00;
131 |
132 | /**
133 | * WebSocket Opcode for a Text frame.
134 | */
135 | public static final int OPCODE_TEXT = 0x01;
136 |
137 | /**
138 | * WebSocket Opcode for a Binary frame.
139 | */
140 | public static final int OPCODE_BINARY = 0x02;
141 |
142 | /**
143 | * Lowest WebSocket Opcode for reserved non-control frames. That is
144 | * these data frames are yet reserved (undefined).
145 | */
146 | public static final int OPCODE_RESERVED_NON_CONTROL_LOW = 0x03;
147 |
148 | /**
149 | * Highest WebSocket Opcode for reserved non-control frames. That is
150 | * these data frames are yet reserved (undefined).
151 | */
152 | public static final int OPCODE_RESERVED_NON_CONTROL_HIGH = 0x07;
153 |
154 | /**
155 | * WebSocket Opcode for a Close control frame.
156 | */
157 | public static final int OPCODE_CLOSE = 0x08;
158 |
159 | /**
160 | * WebSocket Opcode for a Ping control frame.
161 | */
162 | public static final int OPCODE_PING = 0x09;
163 |
164 | /**
165 | * WebSocket Opcode for a Pong control frame.
166 | */
167 | public static final int OPCODE_PONG = 0x0A;
168 |
169 | /**
170 | * Lowest WebSocket Opcode for reserved control frames. That is
171 | * these control frames are yet reserved (undefined).
172 | */
173 | public static final int OPCODE_RESERVED_CONTROL_LOW = 0x0B;
174 |
175 | /**
176 | * Highest WebSocket Opcode for reserved control frames. That is
177 | * these control frames are yet reserved (undefined).
178 | */
179 | public static final int OPCODE_RESERVED_CONTROL_HIGH = 0x0F;
180 |
181 | /**
182 | * Lowest WebSocket Opcode that defines a control frame.
183 | */
184 | public static final int OPCODE_CONTROL_LOW = 0x08;
185 |
186 | /**
187 | * Highest WebSocket Opcode that defines a control frame.
188 | */
189 | public static final int OPCODE_CONTROL_HIGH = 0x0F;
190 |
191 | /**
192 | * WebSocket Accept UUID. The UUID to be appended to the client-provided
193 | * security nonce, in order to complete the WebSocket handshake.
194 | */
195 | public static final String WEBSOCKET_ACCEPT_UUID =
196 | "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
197 |
198 | /**
199 | * Convert the provided byte to an unsigned int.
200 | * @param b byte to be converted
201 | * @return unsigned int (8-bit) representation of the provided byte
202 | */
203 | public static final int asUnsignedInt(final byte b) {
204 | int x = b;
205 | x &= MASK_BYTE;
206 | return x;
207 | }
208 |
209 | /**
210 | * Convert the provided String to UTF-8 encoded bytes.
211 | * @param s String to be converted to a UTF-8 representation
212 | * @return byte array containing the UTF-8 representation of the
213 | * provided String data
214 | */
215 | public static final byte[] asUTF8(final String s) {
216 | return s.getBytes(StandardCharsets.UTF_8);
217 | }
218 |
219 | /**
220 | * Check if the first String contains() the second String. This method
221 | * returns false if the first string is null.
222 | * @param s1 String to be checked if it contains
223 | * @param s2 String to check for
224 | * @return true, iff s1.contains(s2), otherwise false
225 | */
226 | public static boolean checkContains(final String s1, final String s2) {
227 | if (s1 == null) {
228 | return false;
229 | }
230 | return s1.contains(s2);
231 | }
232 |
233 | /**
234 | * Check if the first String startsWith() the second String. This method
235 | * returns false if the first String is null.
236 | * @param s1 String to be checked if it starts with
237 | * @param s2 String to check for
238 | * @return true, iff s1.startsWith(s2), otherwise false
239 | */
240 | public static boolean checkStartsWith(final String s1, final String s2) {
241 | if (s1 == null) {
242 | return false;
243 | }
244 | return s1.startsWith(s2);
245 | }
246 |
247 | /**
248 | * Create a WebSocket-speaking InputStream from the provided InputStream.
249 | * Note that an output peer is still required.
250 | * @param is InputStream to be decorated as a WebSocket-speaking InputStream
251 | */
252 | public WebSocketServerInputStream(final InputStream is) {
253 | checkNotNull(is, "is == null");
254 | this.inputStream = is;
255 | }
256 |
257 | /**
258 | * Create a WebSocket-speaking InputStream from the provided InputStream
259 | * and output peer WebSocketOutputStream.
260 | * @param is InputStream to be decorated as a WebSocket-speaking InputStream
261 | * @param wsos WebSocketOutputStream to be the output peer
262 | */
263 | public WebSocketServerInputStream(final InputStream is,
264 | final WebSocketServerOutputStream wsos) {
265 | checkNotNull(is, "is == null");
266 | checkNotNull(wsos, "wsos == null");
267 | this.inputStream = is;
268 | this.outputPeer = wsos;
269 | }
270 |
271 | /**
272 | * Obtain a reference to the peer object used for output on this stream.
273 | * @return WebSocketServerOutputStream used for output on this stream
274 | */
275 | public final WebSocketServerOutputStream getOutputPeer() {
276 | return outputPeer;
277 | }
278 |
279 | /**
280 | * Determine if this connection has sent a WebSocket Close frame.
281 | * @return true, if this connection has sent a WebSocket Close frame,
282 | * otherwise false.
283 | */
284 | private boolean isCloseSent() {
285 | return outputPeer.isCloseSent();
286 | }
287 |
288 | /**
289 | * Determine if the WebSocket connection is closed.
290 | * @return true, if the WebSocket connection is closed, otherwise false.
291 | */
292 | public final boolean isClosed() {
293 | return closeReceived && isCloseSent();
294 | }
295 |
296 | /**
297 | * Determine if the WebSocket connection has failed.
298 | * @return true, if the WebSocket connection has failed, otherwise false.
299 | */
300 | public final boolean isFailed() {
301 | return failed;
302 | }
303 |
304 | /**
305 | * Determine if the WebSocket handshake has completed successfully.
306 | * @return true, if the WebSocket handshake has completed successfully,
307 | * otherwise false.
308 | */
309 | public final boolean isHandshakeComplete() {
310 | return handshakeComplete;
311 | }
312 |
313 | /**
314 | * Reads the next byte of data from the input stream. The value byte is
315 | * returned as an int in the range 0 to 255. If no byte is available
316 | * because the end of the stream has been reached, the value -1 is
317 | * returned. This method blocks until input data is available, the end
318 | * of the stream is detected, or an exception is thrown.
319 | * @return the next byte of data, or -1 if the end of the stream is
320 | * reached.
321 | * @throws IOException if an I/O error occurs.
322 | */
323 | @Override
324 | public final int read() throws IOException {
325 | if (isClosed() || isFailed()) {
326 | return EOF;
327 | }
328 | if (!handshakeComplete) {
329 | shakeHands();
330 | if (!handshakeComplete) {
331 | failTheWebSocketConnection();
332 | return EOF;
333 | }
334 | }
335 | return nextWebSocketByte();
336 | }
337 |
338 | /**
339 | * Set the output peer for this InputStream. A WebSocketServerOutputStream
340 | * object is used to communicate back to the source of this InputStream.
341 | * This method allows a client to specify the object that does this. This
342 | * is especially useful if the client did not do so during construction.
343 | * @param op WebSocketServerOutputStream object used to communicate back
344 | * to the source of this InputStream
345 | */
346 | public final void setOutputPeer(final WebSocketServerOutputStream op) {
347 | this.outputPeer = op;
348 | }
349 |
350 | //-----------------------------------------------------------------------
351 |
352 | /**
353 | * Obtain the next byte of data from the WebSocket.
354 | * @return the next byte of data from the WebSocket
355 | * @throws IOException if anything goes wrong with the underlying stream
356 | */
357 | private int nextWebSocketByte() throws IOException {
358 | while (payloadLength == 0L) {
359 | nextWebSocketFrame();
360 | if (isClosed() || isFailed()) {
361 | return EOF;
362 | }
363 | }
364 | int data = inputStream.read() ^ maskingBytes[maskingIndex];
365 | payloadLength--;
366 | maskingIndex++;
367 | maskingIndex &= MASK_MASKING_INDEX;
368 | return data;
369 | }
370 |
371 | /**
372 | * Process the next WebSocket frame. This method reads the header
373 | * information about the frame. It sets up non-control frames to
374 | * provide their data, and handles WebSocket protocol-specifics for
375 | * the control frames.
376 | * @throws IOException if anything goes wrong with the underlying stream
377 | */
378 | private void nextWebSocketFrame() throws IOException {
379 | // byte 0: flags and opcode
380 | int flagOps = inputStream.read();
381 | if ((flagOps & MASK_RESERVED) != 0x00) {
382 | failTheWebSocketConnection();
383 | return;
384 | }
385 | int opcode = flagOps & MASK_OPCODE;
386 | if (opcode >= OPCODE_RESERVED_NON_CONTROL_LOW
387 | && opcode <= OPCODE_RESERVED_NON_CONTROL_HIGH) {
388 | failTheWebSocketConnection();
389 | return;
390 | }
391 | if (opcode >= OPCODE_RESERVED_CONTROL_LOW) {
392 | failTheWebSocketConnection();
393 | return;
394 | }
395 | boolean finalFragment = (flagOps & MASK_FINAL) == MASK_FINAL;
396 | boolean controlOpcode =
397 | (flagOps & MASK_CONTROL_OPCODE) == MASK_CONTROL_OPCODE;
398 | if (controlOpcode && !finalFragment) {
399 | failTheWebSocketConnection();
400 | return;
401 | }
402 | // byte 1: masking and payload length
403 | int maskPayload = inputStream.read();
404 | boolean masked = (maskPayload & MASK_MASK) == MASK_MASK;
405 | if (!masked) {
406 | failTheWebSocketConnection();
407 | return;
408 | }
409 | int payloadSize = maskPayload & MASK_PAYLOAD_SIZE;
410 | // byte 2-9: extended payload length, if specified
411 | if (payloadSize == LENGTH_16) {
412 | if (controlOpcode) {
413 | failTheWebSocketConnection();
414 | return;
415 | }
416 | payloadLength = (inputStream.read() << OCTET)
417 | | (inputStream.read());
418 | if (payloadLength < LENGTH_16_MIN) {
419 | failTheWebSocketConnection();
420 | return;
421 | }
422 | } else if (payloadSize == LENGTH_64) {
423 | if (controlOpcode) {
424 | failTheWebSocketConnection();
425 | return;
426 | }
427 | payloadLength = 0L;
428 | for (int i = 0; i < NUM_OCTET_64; i++) {
429 | payloadLength |=
430 | inputStream.read() << (NUM_OCTET_64 - 1 - i) * OCTET;
431 | }
432 | if (payloadLength < LENGTH_64_MIN) {
433 | failTheWebSocketConnection();
434 | return;
435 | }
436 | } else {
437 | payloadLength = payloadSize;
438 | }
439 | // byte 10-13: masking key
440 | for (int i = 0; i < NUM_MASKING_BYTES; i++) {
441 | maskingBytes[i] = inputStream.read();
442 | }
443 | maskingIndex = 0;
444 | // if this is a control opcode; handle the control frame
445 | if (opcode == OPCODE_CLOSE) {
446 | handleCloseFrame();
447 | }
448 | if (opcode == OPCODE_PING) {
449 | handlePingFrame();
450 | }
451 | if (opcode == OPCODE_PONG) {
452 | handlePongFrame();
453 | }
454 | }
455 |
456 | /**
457 | * Perform the initial WebSocket handshake. WebSockets connect with an
458 | * HTTP Request to upgrade the connection to a WebSocket connection. This
459 | * method ensures that the request is correctly formed, and provides
460 | * the appropriate response to the client. After this method is called,
461 | * further communication is performed solely with WebSocket frames.
462 | * @throws IOException if anything goes wrong with the underlying stream
463 | */
464 | private void shakeHands() throws IOException {
465 | HttpRequest req = new HttpRequest(inputStream);
466 | String requestLine = req.get(HttpRequest.REQUEST_LINE);
467 | handshakeComplete = checkStartsWith(requestLine, "GET /")
468 | && checkContains(requestLine, "HTTP/")
469 | && req.get("Host") != null
470 | && checkContains(req.get("Upgrade"), "websocket")
471 | && checkContains(req.get("Connection"), "Upgrade")
472 | && "13".equals(req.get("Sec-WebSocket-Version"))
473 | && req.get("Sec-WebSocket-Key") != null;
474 | String nonce = req.get("Sec-WebSocket-Key");
475 | if (handshakeComplete) {
476 | byte[] nonceBytes = BaseEncoding.base64().decode(nonce);
477 | if (nonceBytes.length != HANDSHAKE_NONCE_LENGTH) {
478 | handshakeComplete = false;
479 | }
480 | }
481 | // if we have met all the requirements
482 | if (handshakeComplete) {
483 | outputPeer.write(asUTF8("HTTP/1.1 101 Switching Protocols\r\n"));
484 | outputPeer.write(asUTF8("Upgrade: websocket\r\n"));
485 | outputPeer.write(asUTF8("Connection: upgrade\r\n"));
486 | outputPeer.write(asUTF8("Sec-WebSocket-Accept: "));
487 | HashFunction hf = Hashing.sha1();
488 | HashCode hc = hf.newHasher()
489 | .putString(nonce, StandardCharsets.UTF_8)
490 | .putString(WEBSOCKET_ACCEPT_UUID, StandardCharsets.UTF_8)
491 | .hash();
492 | String acceptKey = BaseEncoding.base64().encode(hc.asBytes());
493 | outputPeer.write(asUTF8(acceptKey));
494 | outputPeer.write(asUTF8("\r\n\r\n"));
495 | }
496 | outputPeer.setHandshakeComplete(handshakeComplete);
497 | }
498 |
499 | /**
500 | * Sets the WebSocketServerInputStream to a FAILED state. In this state,
501 | * no further processing of data takes place. Mostly it is used to
502 | * prevent actions that rely upon faulty WebSocket implementations.
503 | */
504 | private void failTheWebSocketConnection() {
505 | failed = true;
506 | }
507 |
508 | /**
509 | * Handle an incoming Close control frame. If we haven't sent a Close
510 | * frame to the client, we do so. We then close the underlying socket.
511 | * @throws IOException if anything goes wrong with the underlying stream
512 | */
513 | private void handleCloseFrame() throws IOException {
514 | // the client has sent us a close frame
515 | closeReceived = true;
516 | // if we already sent a close frame before
517 | if (isCloseSent()) {
518 | // then we received an acknowledgement close frame
519 | // from the client, so we need to close the underlying
520 | // TCP socket now
521 | this.close();
522 | return;
523 | }
524 | // otherwise, the client has sent us a close frame
525 | // and we will acknowledge that close frame now
526 | byte[] closePayload = consumePayload();
527 | if (closePayload.length >= 2) {
528 | int highByte = asUnsignedInt(closePayload[0]);
529 | int lowByte = asUnsignedInt(closePayload[1]);
530 | int closeStatusCode = (highByte << OCTET) | lowByte;
531 | outputPeer.writeClose(closeStatusCode);
532 | } else {
533 | outputPeer.writeClose();
534 | }
535 | // we need to close the underlying TCP socket now
536 | this.close();
537 | }
538 |
539 | /**
540 | * Handle an incoming Ping control frame. The WebSocket standard indicates
541 | * echoing a Pong frame back with an identical payload. That is what this
542 | * method does.
543 | * @throws IOException if anything goes wrong with the underlying stream
544 | */
545 | private void handlePingFrame() throws IOException {
546 | // read all of the ping payload
547 | byte[] pingPayload = consumePayload();
548 | outputPeer.writePong(pingPayload);
549 | }
550 |
551 | /**
552 | * Handle an incoming Pong control frame. This method simply consumes
553 | * the payload (if any) and disregards the frame.
554 | * @throws IOException if anything goes wrong with the underlying stream
555 | */
556 | private void handlePongFrame() throws IOException {
557 | // read all of the pong payload
558 | consumePayload();
559 | }
560 |
561 | /**
562 | * Consume the entire payload of the frame. Note that the state of the
563 | * field payloadLength
is used (and altered) in this utility
564 | * method.
565 | * @return byte[] containing the bytes of the frame payload
566 | * @throws IOException if anything goes wrong with the underlying stream
567 | */
568 | private byte[] consumePayload() throws IOException {
569 | byte[] payload = new byte[(int) payloadLength];
570 | int count = 0;
571 | while (payloadLength > 0L) {
572 | payload[count] = (byte) this.read();
573 | count++;
574 | }
575 | return payload;
576 | }
577 |
578 | /**
579 | * Flag: Has the WebSocket connection received a CLOSE frame?
580 | */
581 | private boolean closeReceived = false;
582 |
583 | /**
584 | * Flag: Has the WebSocket connection failed for any reason?
585 | */
586 | private boolean failed = false;
587 |
588 | /**
589 | * Flag: Is the WebSocket handshake complete?
590 | */
591 | private boolean handshakeComplete = false;
592 |
593 | /**
594 | * InputStream to be decorated as a WebSocket-speaking InputStream.
595 | */
596 | private InputStream inputStream = null;
597 |
598 | /**
599 | * Bytes of the latest masking key provided by the client.
600 | */
601 | private final int[] maskingBytes = new int[NUM_MASKING_BYTES];
602 |
603 | /**
604 | * Index of the next maskingByte to be used on payload data.
605 | */
606 | private int maskingIndex = 0;
607 |
608 | /**
609 | * The companion OutputStream for this WebSocketInputStream.
610 | */
611 | private WebSocketServerOutputStream outputPeer = null;
612 |
613 | /**
614 | * Number of payload bytes that we still expecting before the next
615 | * WebSocket frame.
616 | */
617 | private long payloadLength = 0L;
618 | }
619 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/io/WebSocketServerOutputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketServerOutputStream.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.io;
20 |
21 | import java.io.IOException;
22 | import java.io.OutputStream;
23 |
24 | import static com.google.common.base.Preconditions.checkNotNull;
25 | import java.nio.charset.StandardCharsets;
26 |
27 | /**
28 | * WebSocketServerOutputStream decorates an OutputStream to handle WebSocket
29 | * frames as specified in RFC 6455.
30 | * @author pmeade
31 | */
32 | public class WebSocketServerOutputStream extends OutputStream {
33 | /**
34 | * Payload length indicating that the payload's true length is a
35 | * yet-to-be-provided unsigned 16-bit integer.
36 | */
37 | public static final int LENGTH_16 = 0x7E;
38 |
39 | /**
40 | * A payload specified with 16 bits must have at least this
41 | * length in order to be considered valid.
42 | */
43 | public static final int LENGTH_16_MIN = 126;
44 |
45 | /**
46 | * Payload length indicating that the payload's true length is a
47 | * yet-to-be-provided unsigned 64-bit integer (MSB = 0).
48 | */
49 | public static final int LENGTH_64 = 0x7F;
50 |
51 | /**
52 | * A payload specified with 64 bits must have at least this
53 | * length in order to be considered valid.
54 | */
55 | public static final int LENGTH_64_MIN = 0x10000;
56 |
57 | /**
58 | * Binary mask to remove all but the bits of octet 3. We also remove the
59 | * sign bit (0x80000000) if any, to prevent it from being shifted down.
60 | */
61 | public static final int MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN = 0x7f000000;
62 |
63 | /**
64 | * Binary mask to remove all but the bits of octet 2.
65 | */
66 | public static final int MASK_HIGH_WORD_LOW_BYTE = 0x00ff0000;
67 |
68 | /**
69 | * Binary mask to remove all but the bits of octet 1.
70 | */
71 | public static final int MASK_LOW_WORD_HIGH_BYTE = 0x0000ff00;
72 |
73 | /**
74 | * Binary mask to remove all but the lowest 8 bits (octet 0).
75 | */
76 | public static final int MASK_LOW_WORD_LOW_BYTE = 0x000000ff;
77 |
78 | /**
79 | * Number of bits required to shift octet 1 into the lowest 8 bits.
80 | */
81 | public static final int OCTET_ONE = 8;
82 |
83 | /**
84 | * Number of bits required to shift octet 2 into the lowest 8 bits.
85 | */
86 | public static final int OCTET_TWO = 16;
87 |
88 | /**
89 | * Number of bits required to shift octet 3 into the lowest 8 bits.
90 | */
91 | public static final int OCTET_THREE = 24;
92 |
93 | /**
94 | * WebSocket defined opcode for a Binary frame. Includes high bit (0x80)
95 | * to indicate that the frame is the final/complete frame.
96 | */
97 | public static final int OPCODE_FRAME_BINARY = 0x82;
98 |
99 | /**
100 | * WebSocket defined opcode for a Close frame. Includes high bit (0x80)
101 | * to indicate that the frame is the final/complete frame.
102 | */
103 | public static final int OPCODE_FRAME_CLOSE = 0x88;
104 |
105 | /**
106 | * WebSocket defined opcode for a Pong frame. Includes high bit (0x80)
107 | * to indicate that the frame is the final/complete frame.
108 | */
109 | public static final int OPCODE_FRAME_PONG = 0x8A;
110 |
111 | /**
112 | * WebSocket defined opcode for a Text frame. Includes high bit (0x80)
113 | * to indicate that the frame is the final/complete frame.
114 | */
115 | public static final int OPCODE_FRAME_TEXT = 0x81;
116 |
117 | /**
118 | * Create a WebSocket-speaking OutputStream from the provided OutputStream.
119 | * @param os OutputStream to be decorated as a WebSocketServerOutputStream
120 | */
121 | public WebSocketServerOutputStream(final OutputStream os) {
122 | checkNotNull(os);
123 | this.outputStream = os;
124 | }
125 |
126 | /**
127 | * Writes the specified byte to this output stream. The general contract
128 | * for write is that one byte is written to the output stream. The byte to
129 | * be written is the eight low-order bits of the argument b. The 24
130 | * high-order bits of b are ignored.
131 | *
Subclasses of OutputStream must provide an implementation for this
132 | * method.
133 | * @param b the byte.
134 | * @throws IOException if an I/O error occurs. In particular, an
135 | * IOException may be thrown if the output stream has
136 | * been closed.
137 | */
138 | @Override
139 | public final void write(final int b) throws IOException {
140 | if (handshakeComplete) {
141 | byte[] ba = new byte[] {(byte) b };
142 | writeBinary(ba);
143 | } else {
144 | outputStream.write(b);
145 | }
146 | }
147 |
148 | /**
149 | * Writes len bytes from the specified byte array starting at offset off to
150 | * this output stream. The general contract for write(b, off, len) is that
151 | * some of the bytes in the array b are written to the output stream in
152 | * order; element b[off] is the first byte written and b[off+len-1] is the
153 | * last byte written by this operation.
154 | *
The write method of OutputStream calls the write method of one
155 | * argument on each of the bytes to be written out. Subclasses are
156 | * encouraged to override this method and provide a more efficient
157 | * implementation.
158 | *
If b is null, a NullPointerException is thrown.
159 | *
If off is negative, or len is negative, or off+len is greater than
160 | * the length of the array b, then an IndexOutOfBoundsException is thrown.
161 | * @param b the data.
162 | * @param off the start offset in the data.
163 | * @param len the number of bytes to write.
164 | * @throws IOException if an I/O error occurs. In particular, an
165 | * IOException is thrown if the output stream is
166 | * closed.
167 | */
168 | @Override
169 | public final void write(final byte[] b, final int off, final int len)
170 | throws IOException {
171 | if (handshakeComplete) {
172 | byte[] dst = new byte[len];
173 | System.arraycopy(b, off, dst, 0, len);
174 | writeBinary(dst);
175 | } else {
176 | super.write(b, off, len);
177 | }
178 | }
179 |
180 | /**
181 | * Writes b.length bytes from the specified byte array to this output
182 | * stream. The general contract for write(b) is that it should have
183 | * exactly the same effect as the call write(b, 0, b.length).
184 | * @param b the data.
185 | * @throws IOException if an I/O error occurs.
186 | */
187 | @Override
188 | public final void write(final byte[] b) throws IOException {
189 | if (handshakeComplete) {
190 | writeBinary(b);
191 | } else {
192 | super.write(b);
193 | }
194 | }
195 |
196 | /**
197 | * Determine if a Close control frame has been sent over the WebSocket.
198 | * @return true, iff a Close control frame has been sent,
199 | * otherwise fasle
200 | */
201 | public final boolean isCloseSent() {
202 | return closeSent;
203 | }
204 |
205 | /**
206 | * Determine if the WebSocket handshake has completed successfully.
207 | * @return true, if the WebSocket handshake has completed successfully,
208 | * otherwise false
209 | */
210 | public final boolean isHandshakeComplete() {
211 | return handshakeComplete;
212 | }
213 |
214 | /**
215 | * Tell this WebSocketServerOutputStream if the WebSocket handshake
216 | * has completed successfully.
217 | * @param complete true, if the WebSocket handshake has completed
218 | * successfully, otherwise false
219 | */
220 | public final void setHandshakeComplete(final boolean complete) {
221 | this.handshakeComplete = complete;
222 | }
223 |
224 | /**
225 | * Write the provided binary data to the WebSocket.
226 | * @param bytes byte array containing the binary data to be writen
227 | * @throws IOException if an I/O error occurs
228 | */
229 | public final void writeBinary(final byte[] bytes) throws IOException {
230 | int binLength = bytes.length;
231 | outputStream.write(OPCODE_FRAME_BINARY); // final binary-frame
232 | if (binLength < LENGTH_16_MIN) {
233 | outputStream.write(binLength); // small payload length
234 | } else if (binLength < LENGTH_64_MIN) {
235 | outputStream.write(LENGTH_16); // medium payload flag
236 | outputStream.write(
237 | (binLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE);
238 | outputStream.write(binLength & MASK_LOW_WORD_LOW_BYTE);
239 | } else {
240 | outputStream.write(LENGTH_64); // large payload flag
241 | outputStream.write(0x00); // upper bytes
242 | outputStream.write(0x00); // upper bytes
243 | outputStream.write(0x00); // upper bytes
244 | outputStream.write(0x00); // upper bytes
245 | outputStream.write(
246 | (binLength & MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN) >> OCTET_THREE);
247 | outputStream.write(
248 | (binLength & MASK_HIGH_WORD_LOW_BYTE) >> OCTET_TWO);
249 | outputStream.write(
250 | (binLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE);
251 | outputStream.write(binLength & MASK_LOW_WORD_LOW_BYTE);
252 | }
253 | outputStream.write(bytes); // binary payload
254 | }
255 |
256 | /**
257 | * Write a Close control frame to the WebSocket.
258 | * @throws IOException if an I/O error occurs
259 | */
260 | public final void writeClose() throws IOException {
261 | if (!closeSent) {
262 | closeSent = true;
263 | outputStream.write(new byte[] {
264 | (byte) OPCODE_FRAME_CLOSE, (byte) 0x00
265 | });
266 | }
267 | }
268 |
269 | /**
270 | * Write a Close control frame to the WebSocket.
271 | * @param statusCode status code indicating the reason for the closure
272 | * of the WebSocket; constants defined in RFC 6455
273 | * @throws IOException if an I/O error occurs
274 | */
275 | public final void writeClose(final int statusCode) throws IOException {
276 | if (!closeSent) {
277 | closeSent = true;
278 | outputStream.write(new byte[] {
279 | (byte) OPCODE_FRAME_CLOSE, (byte) 0x02,
280 | (byte) ((statusCode & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE),
281 | (byte) (statusCode & MASK_LOW_WORD_LOW_BYTE)
282 | });
283 | }
284 | }
285 |
286 | /**
287 | * Write a Pong control frame to the WebSocket. Uses the provided data
288 | * as the payload data of the control frame.
289 | * @param pongPayload byte array containing payload data for the pong frame
290 | * @throws IOException if an I/O error occurs
291 | */
292 | public final void writePong(final byte[] pongPayload) throws IOException {
293 | outputStream.write(new byte[] {
294 | (byte) OPCODE_FRAME_PONG, (byte) (pongPayload.length)
295 | });
296 | outputStream.write(pongPayload);
297 | }
298 |
299 | /**
300 | * Write the provided String to the WebSocket in UTF-8 format.
301 | * @param string String to be written to the WebSocket
302 | * @throws IOException if an I/O error occurs
303 | */
304 | public final void writeString(final String string) throws IOException {
305 | byte[] utfBytes = string.getBytes(StandardCharsets.UTF_8);
306 | int utfLength = utfBytes.length;
307 | outputStream.write(OPCODE_FRAME_TEXT); // final text-frame
308 | if (utfLength < LENGTH_16_MIN) {
309 | outputStream.write(utfLength); // small payload length
310 | } else if (utfLength < LENGTH_64_MIN) {
311 | outputStream.write(LENGTH_16); // medium payload flag
312 | outputStream.write(
313 | (utfLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE);
314 | outputStream.write(utfLength & MASK_LOW_WORD_LOW_BYTE);
315 | } else {
316 | outputStream.write(LENGTH_64); // large payload flag
317 | outputStream.write(0x00); // upper bytes
318 | outputStream.write(0x00); // upper bytes
319 | outputStream.write(0x00); // upper bytes
320 | outputStream.write(0x00); // upper bytes
321 | outputStream.write(
322 | (utfLength & MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN) >> OCTET_THREE);
323 | outputStream.write(
324 | (utfLength & MASK_HIGH_WORD_LOW_BYTE) >> OCTET_TWO);
325 | outputStream.write(
326 | (utfLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE);
327 | outputStream.write(utfLength & MASK_LOW_WORD_LOW_BYTE);
328 | }
329 | outputStream.write(utfBytes); // text payload
330 | }
331 |
332 | /**
333 | * Flag: Has the WebSocket connection sent a CLOSE frame?
334 | */
335 | private boolean closeSent = false;
336 |
337 | /**
338 | * Flag: Is the WebSocket handshake complete?
339 | */
340 | private boolean handshakeComplete = false;
341 |
342 | /**
343 | * OutputStream to be decorated as a WebSocket-speaking OutputStream.
344 | */
345 | private final OutputStream outputStream;
346 | }
347 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/io/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * package-info.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | /**
20 | * Provides for input and output through WebSocket protocol streams.
21 | */
22 | package com.pmeade.websocket.io;
23 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/net/WebSocket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocket.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.net;
20 |
21 | import com.pmeade.websocket.io.WebSocketServerInputStream;
22 | import com.pmeade.websocket.io.WebSocketServerOutputStream;
23 | import java.io.IOException;
24 | import java.net.InetAddress;
25 | import java.net.Socket;
26 | import java.net.SocketAddress;
27 | import java.net.SocketException;
28 | import java.nio.channels.SocketChannel;
29 |
30 | /**
31 | * WebSocket decorates Socket to provide the server side of a Socket that
32 | * speaks the WebSocket (RFC 6455) protocol.
33 | * @author veloxi
34 | */
35 | public class WebSocket extends Socket {
36 | /**
37 | * Construct a WebSocket. WebSocket decorates a Socket for WebSocket
38 | * (RFC 6455) behavior.
39 | * @param s Socket to be decorated with WebSocket behavior.
40 | */
41 | public WebSocket(final Socket s) {
42 | this.socket = s;
43 | }
44 |
45 | @Override
46 | public final void connect(final SocketAddress endpoint) throws IOException {
47 | socket.connect(endpoint);
48 | }
49 |
50 | @Override
51 | public final void connect(final SocketAddress endpoint, final int timeout)
52 | throws IOException {
53 | socket.connect(endpoint, timeout);
54 | }
55 |
56 | @Override
57 | public final void bind(final SocketAddress bindpoint) throws IOException {
58 | socket.bind(bindpoint);
59 | }
60 |
61 | @Override
62 | public final InetAddress getInetAddress() {
63 | return socket.getInetAddress();
64 | }
65 |
66 | @Override
67 | public final InetAddress getLocalAddress() {
68 | return socket.getLocalAddress();
69 | }
70 |
71 | @Override
72 | public final int getPort() {
73 | return socket.getPort();
74 | }
75 |
76 | @Override
77 | public final int getLocalPort() {
78 | return socket.getLocalPort();
79 | }
80 |
81 | @Override
82 | public final SocketAddress getRemoteSocketAddress() {
83 | return socket.getRemoteSocketAddress();
84 | }
85 |
86 | @Override
87 | public final SocketAddress getLocalSocketAddress() {
88 | return socket.getLocalSocketAddress();
89 | }
90 |
91 | @Override
92 | public final SocketChannel getChannel() {
93 | throw new UnsupportedOperationException();
94 | }
95 |
96 | @Override
97 | public final WebSocketServerInputStream getInputStream()
98 | throws IOException {
99 | if (wssos == null) {
100 | this.getOutputStream();
101 | }
102 | if (wssis == null) {
103 | wssis = new WebSocketServerInputStream(
104 | socket.getInputStream(), wssos);
105 | }
106 | return wssis;
107 | }
108 |
109 | @Override
110 | public final WebSocketServerOutputStream getOutputStream()
111 | throws IOException {
112 | if (wssos == null) {
113 | wssos = new WebSocketServerOutputStream(socket.getOutputStream());
114 | }
115 | return wssos;
116 | }
117 |
118 | @Override
119 | public final void setTcpNoDelay(final boolean on) throws SocketException {
120 | socket.setTcpNoDelay(on);
121 | }
122 |
123 | @Override
124 | public final boolean getTcpNoDelay() throws SocketException {
125 | return socket.getTcpNoDelay();
126 | }
127 |
128 | @Override
129 | public final void setSoLinger(final boolean on, final int linger)
130 | throws SocketException {
131 | socket.setSoLinger(on, linger);
132 | }
133 |
134 | @Override
135 | public final int getSoLinger() throws SocketException {
136 | return socket.getSoLinger();
137 | }
138 |
139 | @Override
140 | public final void sendUrgentData(final int data)
141 | throws IOException {
142 | socket.sendUrgentData(data);
143 | }
144 |
145 | @Override
146 | public final void setOOBInline(final boolean on)
147 | throws SocketException {
148 | socket.setOOBInline(on);
149 | }
150 |
151 | @Override
152 | public final boolean getOOBInline() throws SocketException {
153 | return socket.getOOBInline();
154 | }
155 |
156 | @Override
157 | public final synchronized void setSoTimeout(final int timeout)
158 | throws SocketException {
159 | socket.setSoTimeout(timeout);
160 | }
161 |
162 | @Override
163 | public final synchronized int getSoTimeout() throws SocketException {
164 | return socket.getSoTimeout();
165 | }
166 |
167 | @Override
168 | public final synchronized void setSendBufferSize(final int size)
169 | throws SocketException {
170 | socket.setSendBufferSize(size);
171 | }
172 |
173 | @Override
174 | public final synchronized int getSendBufferSize() throws SocketException {
175 | return socket.getSendBufferSize();
176 | }
177 |
178 | @Override
179 | public final synchronized void setReceiveBufferSize(final int size)
180 | throws SocketException {
181 | socket.setReceiveBufferSize(size);
182 | }
183 |
184 | @Override
185 | public final synchronized int getReceiveBufferSize()
186 | throws SocketException {
187 | return socket.getReceiveBufferSize();
188 | }
189 |
190 | @Override
191 | public final void setKeepAlive(final boolean on) throws SocketException {
192 | socket.setKeepAlive(on);
193 | }
194 |
195 | @Override
196 | public final boolean getKeepAlive() throws SocketException {
197 | return socket.getKeepAlive();
198 | }
199 |
200 | @Override
201 | public final void setTrafficClass(final int tc) throws SocketException {
202 | socket.setTrafficClass(tc);
203 | }
204 |
205 | @Override
206 | public final int getTrafficClass() throws SocketException {
207 | return socket.getTrafficClass();
208 | }
209 |
210 | @Override
211 | public final void setReuseAddress(final boolean on) throws SocketException {
212 | socket.setReuseAddress(on);
213 | }
214 |
215 | @Override
216 | public final boolean getReuseAddress() throws SocketException {
217 | return socket.getReuseAddress();
218 | }
219 |
220 | @Override
221 | public final synchronized void close() throws IOException {
222 | socket.close();
223 | }
224 |
225 | @Override
226 | public final void shutdownInput() throws IOException {
227 | socket.shutdownInput();
228 | }
229 |
230 | @Override
231 | public final void shutdownOutput() throws IOException {
232 | socket.shutdownOutput();
233 | }
234 |
235 | @Override
236 | public final String toString() {
237 | return socket.toString();
238 | }
239 |
240 | @Override
241 | public final boolean isConnected() {
242 | return socket.isConnected();
243 | }
244 |
245 | @Override
246 | public final boolean isBound() {
247 | return socket.isBound();
248 | }
249 |
250 | @Override
251 | public final boolean isClosed() {
252 | return socket.isClosed();
253 | }
254 |
255 | @Override
256 | public final boolean isInputShutdown() {
257 | return socket.isInputShutdown();
258 | }
259 |
260 | @Override
261 | public final boolean isOutputShutdown() {
262 | return socket.isOutputShutdown();
263 | }
264 |
265 | @Override
266 | public final void setPerformancePreferences(
267 | final int connectionTime,
268 | final int latency,
269 | final int bandwidth) {
270 | socket.setPerformancePreferences(connectionTime, latency, bandwidth);
271 | }
272 |
273 | /**
274 | * Socket to be decorated for WebSocket behavior.
275 | */
276 | private final Socket socket;
277 |
278 | /**
279 | * WebSocketServerInputStream that decorates the InputStream for
280 | * this Socket. Created on the first call to getInputStream()
.
281 | */
282 | private WebSocketServerInputStream wssis = null;
283 |
284 | /**
285 | * WebSocketServerOutputStream that decorates the OutputStream for
286 | * this Socket. Created on the first call to getInputStream()
287 | * or getOutputStream()
.
288 | */
289 | private WebSocketServerOutputStream wssos = null;
290 | }
291 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/net/WebSocketServerSocket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketServerSocket.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.net;
20 |
21 | import java.io.IOException;
22 | import java.net.InetAddress;
23 | import java.net.ServerSocket;
24 | import java.net.SocketAddress;
25 | import java.net.SocketException;
26 | import java.nio.channels.ServerSocketChannel;
27 |
28 | /**
29 | * WebSocketServerSocket decorates a ServerSocket to accept connections
30 | * that use the WebSocket protocol as specified in RFC 6455.
31 | * @author veloxi
32 | */
33 | public class WebSocketServerSocket extends ServerSocket {
34 | /**
35 | * Construct a WebSocketServerSocket. WebSocketServerSocket provides a
36 | * ServerSocket with WebSocket (RFC 6455) behavior.
37 | * @param ss ServerSocket to be decorated with WebSocket behavior
38 | * @throws IOException if an I/O error occurs
39 | */
40 | public WebSocketServerSocket(final ServerSocket ss) throws IOException {
41 | this.serverSocket = ss;
42 | }
43 |
44 | @Override
45 | public final void bind(final SocketAddress endpoint) throws IOException {
46 | serverSocket.bind(endpoint);
47 | }
48 |
49 | @Override
50 | public final void bind(final SocketAddress endpoint, final int backlog)
51 | throws IOException {
52 | serverSocket.bind(endpoint, backlog);
53 | }
54 |
55 | @Override
56 | public final InetAddress getInetAddress() {
57 | return serverSocket.getInetAddress();
58 | }
59 |
60 | @Override
61 | public final int getLocalPort() {
62 | return serverSocket.getLocalPort();
63 | }
64 |
65 | @Override
66 | public final SocketAddress getLocalSocketAddress() {
67 | return serverSocket.getLocalSocketAddress();
68 | }
69 |
70 | @Override
71 | public final WebSocket accept() throws IOException {
72 | return new WebSocket(serverSocket.accept());
73 | }
74 |
75 | @Override
76 | public final void close() throws IOException {
77 | serverSocket.close();
78 | }
79 |
80 | @Override
81 | public final ServerSocketChannel getChannel() {
82 | throw new UnsupportedOperationException();
83 | }
84 |
85 | @Override
86 | public final boolean isBound() {
87 | return serverSocket.isBound();
88 | }
89 |
90 | @Override
91 | public final boolean isClosed() {
92 | return serverSocket.isClosed();
93 | }
94 |
95 | @Override
96 | public final synchronized void setSoTimeout(final int timeout)
97 | throws SocketException {
98 | serverSocket.setSoTimeout(timeout);
99 | }
100 |
101 | @Override
102 | public final synchronized int getSoTimeout() throws IOException {
103 | return serverSocket.getSoTimeout();
104 | }
105 |
106 | @Override
107 | public final void setReuseAddress(final boolean on)
108 | throws SocketException {
109 | serverSocket.setReuseAddress(on);
110 | }
111 |
112 | @Override
113 | public final boolean getReuseAddress() throws SocketException {
114 | return serverSocket.getReuseAddress();
115 | }
116 |
117 | @Override
118 | public final String toString() {
119 | return serverSocket.toString();
120 | }
121 |
122 | @Override
123 | public final synchronized void setReceiveBufferSize(final int size)
124 | throws SocketException {
125 | serverSocket.setReceiveBufferSize(size);
126 | }
127 |
128 | @Override
129 | public final synchronized int getReceiveBufferSize()
130 | throws SocketException {
131 | return serverSocket.getReceiveBufferSize();
132 | }
133 |
134 | @Override
135 | public final void setPerformancePreferences(
136 | final int connectionTime,
137 | final int latency,
138 | final int bandwidth) {
139 | serverSocket.setPerformancePreferences(
140 | connectionTime, latency, bandwidth);
141 | }
142 |
143 | /**
144 | * ServerSocket to be decorated with WebSocket behavior.
145 | */
146 | private final ServerSocket serverSocket;
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/net/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * package-info.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | /**
20 | * Provides a ServerSocket that understands the WebSocket protocol.
21 | */
22 | package com.pmeade.websocket.net;
23 |
--------------------------------------------------------------------------------
/src/test/java/com/pmeade/websocket/http/HttpRequestTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * HttpRequestTest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.http;
20 |
21 | import com.google.common.base.Joiner;
22 | import com.google.common.collect.ImmutableMap;
23 | import java.io.ByteArrayInputStream;
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 | import java.util.Arrays;
27 | import java.util.Collection;
28 | import java.util.Map;
29 | import java.util.Set;
30 | import org.junit.After;
31 | import org.junit.AfterClass;
32 | import org.junit.Before;
33 | import org.junit.BeforeClass;
34 | import org.junit.Test;
35 | import static org.junit.Assert.*;
36 |
37 | /**
38 | * @author blinkdog
39 | */
40 | public class HttpRequestTest
41 | {
42 | private static final String CLIENT_HANDSHAKE = (Joiner.on("\r\n").join(Arrays.asList(
43 | "GET / HTTP/1.1",
44 | "Host: localhost:8080",
45 | "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0",
46 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
47 | "Accept-Language: en-US,en;q=0.5",
48 | "Accept-Encoding: gzip, deflate",
49 | "Sec-WebSocket-Version: 13",
50 | "Origin: null",
51 | "Sec-WebSocket-Key: V76L7ym8nB/U/K96iWDjKg==",
52 | "Connection: keep-alive, Upgrade",
53 | "Pragma: no-cache",
54 | "Cache-Control: no-cache",
55 | "Upgrade: websocket",
56 | ""
57 | ))) + "\r\n";
58 |
59 | private static final String MESSED_UP_HEADERS = (Joiner.on("\r\n").join(Arrays.asList(
60 | "GET / HTTP/1.1",
61 | "Host: localhost:8080",
62 | "User-Agent Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0",
63 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
64 | "Accept-Language en-US,en;q=0.5",
65 | "Accept-Encoding: gzip, deflate",
66 | "Sec-WebSocket-Version 13",
67 | "Origin: null",
68 | "Sec-WebSocket-Key V76L7ym8nB/U/K96iWDjKg==",
69 | "Connection: keep-alive, Upgrade",
70 | "Pragma no-cache",
71 | "Cache-Control: no-cache",
72 | "Upgrade websocket",
73 | ""
74 | ))) + "\r\n";
75 |
76 | private static final String INCOMPLETE_REQUEST = (Joiner.on("\r\n").join(Arrays.asList(
77 | "GET / HTTP/1.1",
78 | "Host: localhost:8080",
79 | "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0",
80 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
81 | "Accept-Language: en-US,en;q=0.5",
82 | "Accept-Encoding: gzip, deflate",
83 | "Sec-WebSocket-Version: 13"
84 | ))) + "\r\n";
85 |
86 | private static final String BROKEN_HEADERS = (Joiner.on("\r\n").join(Arrays.asList(
87 | "GET / HTTP/1.1",
88 | ": localhost:8080",
89 | ":::: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0",
90 | " : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
91 | "JustABunchOfStuff:",
92 | ":JustABunchOfStuff",
93 | ":::JustABunchOfStuff:::",
94 | " : : : JustABunchOfStuff : : : ",
95 | "dflkjdlk j d lkjdlkjflkdjf lkj dl kfjdlkjfdlkjflkdjflkdjlkfjdlkjfdlkjflkdjflkdjlfkjdlkfjdlkfjdlk:",
96 | ""
97 | ))) + "\r\n";
98 |
99 | public HttpRequestTest() {
100 | }
101 |
102 | @BeforeClass
103 | public static void setUpClass() {
104 | }
105 |
106 | @AfterClass
107 | public static void tearDownClass() {
108 | }
109 |
110 | @Before
111 | public void setUp() {
112 | }
113 |
114 | @After
115 | public void tearDown() {
116 | }
117 |
118 | @Test
119 | public void testAlwaysSucceed() {
120 | assertTrue(true);
121 | }
122 |
123 | @Test
124 | public void testConstructNullReader() {
125 | try {
126 | HttpRequest hr = new HttpRequest(null);
127 | fail();
128 | } catch(NullPointerException e) {
129 | // expected
130 | }
131 | }
132 |
133 | @Test
134 | public void testRequestLineConstantDefined() {
135 | assertNotNull(HttpRequest.REQUEST_LINE);
136 | }
137 |
138 | @Test
139 | public void testConstructWithEmptyReader() {
140 | ByteArrayInputStream bais = new ByteArrayInputStream("".getBytes());
141 | HttpRequest hr = new HttpRequest(bais);
142 | assertEquals(1, hr.size());
143 | assertEquals(null, hr.get(HttpRequest.REQUEST_LINE));
144 | }
145 |
146 | @Test
147 | public void testWithClientHandshake() {
148 | ByteArrayInputStream bais = new ByteArrayInputStream(CLIENT_HANDSHAKE.getBytes());
149 | HttpRequest hr = new HttpRequest(bais);
150 | assertEquals(25, hr.size());
151 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
152 | assertEquals("localhost:8080", hr.get("Host"));
153 | assertEquals("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", hr.get("User-Agent"));
154 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept"));
155 | assertEquals("en-US,en;q=0.5", hr.get("Accept-Language"));
156 | assertEquals("gzip, deflate", hr.get("Accept-Encoding"));
157 | assertEquals("13", hr.get("Sec-WebSocket-Version"));
158 | assertEquals("null", hr.get("Origin"));
159 | assertEquals("V76L7ym8nB/U/K96iWDjKg==", hr.get("Sec-WebSocket-Key"));
160 | assertEquals("keep-alive, Upgrade", hr.get("Connection"));
161 | assertEquals("no-cache", hr.get("Pragma"));
162 | assertEquals("no-cache", hr.get("Cache-Control"));
163 | assertEquals("websocket", hr.get("Upgrade"));
164 | }
165 |
166 | @Test
167 | public void testConstructWithBlankLines() {
168 | ByteArrayInputStream bais = new ByteArrayInputStream("\r\n\r\n\r\n".getBytes());
169 | HttpRequest hr = new HttpRequest(bais);
170 | assertEquals(1, hr.size());
171 | assertEquals(null, hr.get(HttpRequest.REQUEST_LINE));
172 | }
173 |
174 | @Test
175 | public void testWithMessedUpHeaders() {
176 | ByteArrayInputStream bais = new ByteArrayInputStream(MESSED_UP_HEADERS.getBytes());
177 | HttpRequest hr = new HttpRequest(bais);
178 | assertEquals(15, hr.size());
179 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
180 | assertEquals("localhost:8080", hr.get("Host"));
181 | assertEquals(null, hr.get("User-Agent"));
182 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept"));
183 | assertEquals(null, hr.get("Accept-Language"));
184 | assertEquals("gzip, deflate", hr.get("Accept-Encoding"));
185 | assertEquals(null, hr.get("Sec-WebSocket-Version"));
186 | assertEquals("null", hr.get("Origin"));
187 | assertEquals(null, hr.get("Sec-WebSocket-Key"));
188 | assertEquals("keep-alive, Upgrade", hr.get("Connection"));
189 | assertEquals(null, hr.get("Pragma"));
190 | assertEquals("no-cache", hr.get("Cache-Control"));
191 | assertEquals(null, hr.get("Upgrade"));
192 | }
193 |
194 | @Test
195 | public void testWithIncompleteRequest() {
196 | ByteArrayInputStream bais = new ByteArrayInputStream(INCOMPLETE_REQUEST.getBytes());
197 | HttpRequest hr = new HttpRequest(bais);
198 | assertEquals(13, hr.size());
199 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
200 | assertEquals("localhost:8080", hr.get("Host"));
201 | assertEquals("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", hr.get("User-Agent"));
202 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept"));
203 | assertEquals("en-US,en;q=0.5", hr.get("Accept-Language"));
204 | assertEquals("gzip, deflate", hr.get("Accept-Encoding"));
205 | assertEquals("13", hr.get("Sec-WebSocket-Version"));
206 | }
207 |
208 | @Test
209 | public void testWithIncompleteRequestThatThrows() {
210 | InputStream bytesThenThrow = new InputStream() {
211 | final byte[] bytes = INCOMPLETE_REQUEST.getBytes();
212 | final ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
213 | int count = 0;
214 | @Override
215 | public int read() throws IOException {
216 | if(count < bytes.length) {
217 | int data = (int)bytes[count];
218 | count++;
219 | return data;
220 | } else {
221 | throw new IOException("Oh frell...");
222 | }
223 | }
224 | };
225 | HttpRequest hr = new HttpRequest(bytesThenThrow);
226 | assertEquals(13, hr.size());
227 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
228 | assertEquals("localhost:8080", hr.get("Host"));
229 | assertEquals("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", hr.get("User-Agent"));
230 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept"));
231 | assertEquals("en-US,en;q=0.5", hr.get("Accept-Language"));
232 | assertEquals("gzip, deflate", hr.get("Accept-Encoding"));
233 | assertEquals("13", hr.get("Sec-WebSocket-Version"));
234 | }
235 |
236 | @Test
237 | public void testMapMutatorsFailWithException() {
238 | ByteArrayInputStream bais = new ByteArrayInputStream(CLIENT_HANDSHAKE.getBytes());
239 | HttpRequest hr = new HttpRequest(bais);
240 | assertEquals(25, hr.size());
241 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
242 | assertEquals("localhost:8080", hr.get("Host"));
243 |
244 | try {
245 | hr.put("Custom-Header", "Custom Data For Header");
246 | fail();
247 | } catch(UnsupportedOperationException e) {
248 | // expected
249 | }
250 |
251 | try {
252 | hr.remove("Host");
253 | fail();
254 | } catch(UnsupportedOperationException e) {
255 | // expected
256 | }
257 |
258 | try {
259 | ImmutableMap myMap = ImmutableMap.builder()
260 | .put("Header-One", "Data One")
261 | .put("Header-Two", "Data Two")
262 | .put("Header-Three", "Data Three")
263 | .build();
264 | hr.putAll(myMap);
265 | fail();
266 | } catch(UnsupportedOperationException e) {
267 | // expected
268 | }
269 |
270 | try {
271 | hr.clear();
272 | fail();
273 | } catch(UnsupportedOperationException e) {
274 | // expected
275 | }
276 | }
277 |
278 | @Test
279 | public void testMapAccessorsWork() {
280 | ByteArrayInputStream bais = new ByteArrayInputStream(CLIENT_HANDSHAKE.getBytes());
281 | HttpRequest hr = new HttpRequest(bais);
282 | assertEquals(25, hr.size());
283 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
284 | assertEquals("localhost:8080", hr.get("Host"));
285 |
286 | assertFalse(hr.isEmpty());
287 |
288 | assertTrue(hr.containsKey("Host"));
289 | assertFalse(hr.containsKey("Dalek-Malware"));
290 |
291 | assertTrue(hr.containsValue("localhost:8080"));
292 | assertFalse(hr.containsValue("Exterminate!"));
293 |
294 | Set keys = hr.keySet();
295 | assertNotNull(keys);
296 | assertEquals(25, keys.size());
297 |
298 | Collection values = hr.values();
299 | assertNotNull(values);
300 | assertEquals(25, values.size());
301 |
302 | Set> entrySet = hr.entrySet();
303 | assertNotNull(entrySet);
304 | assertEquals(25, entrySet.size());
305 | }
306 |
307 | @Test
308 | public void testWithBrokenHeaders() {
309 | ByteArrayInputStream bais = new ByteArrayInputStream(BROKEN_HEADERS.getBytes());
310 | HttpRequest hr = new HttpRequest(bais);
311 | assertEquals(1, hr.size());
312 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/src/test/java/com/pmeade/websocket/io/LineInputStreamTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * LineInputStreamTest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.io;
20 |
21 | import java.io.BufferedReader;
22 | import java.io.ByteArrayInputStream;
23 | import java.io.IOException;
24 | import java.io.InputStream;
25 | import org.junit.After;
26 | import org.junit.AfterClass;
27 | import org.junit.Before;
28 | import org.junit.BeforeClass;
29 | import org.junit.Test;
30 |
31 | import static org.junit.Assert.*;
32 |
33 | /**
34 | * @author blinkdog
35 | */
36 | public class LineInputStreamTest
37 | {
38 | public LineInputStreamTest() {
39 | }
40 |
41 | @BeforeClass
42 | public static void setUpClass() {
43 | }
44 |
45 | @AfterClass
46 | public static void tearDownClass() {
47 | }
48 |
49 | @Before
50 | public void setUp() {
51 | }
52 |
53 | @After
54 | public void tearDown() {
55 | }
56 |
57 | @Test
58 | public void testAlwaysSucceed() {
59 | assertTrue(true);
60 | }
61 |
62 | @Test
63 | public void testRequireDecoratee() {
64 | try {
65 | LineInputStream lis = new LineInputStream(null);
66 | fail();
67 | } catch (NullPointerException e) {
68 | // expected
69 | }
70 | }
71 |
72 | @Test
73 | public void testConstructWithInputStream() throws Exception {
74 | ByteArrayInputStream bais = new ByteArrayInputStream("".getBytes());
75 | LineInputStream lis = new LineInputStream(bais);
76 | assertNotNull(lis);
77 | assertNull(lis.readLine());
78 | }
79 |
80 | @Test
81 | public void testOneLineNoLineEndings() throws Exception {
82 | ByteArrayInputStream bais = new ByteArrayInputStream("Dalek".getBytes());
83 | LineInputStream lis = new LineInputStream(bais);
84 | assertNotNull(lis);
85 | assertEquals("Dalek", lis.readLine());
86 | assertEquals(null, lis.readLine());
87 | }
88 |
89 | @Test
90 | public void testTwoLinesOneLineEnding() throws Exception {
91 | String[] inputs = {
92 | "Cyberman\r\n",
93 | "Dalek"
94 | };
95 | StringBuilder sb = new StringBuilder();
96 | for(String input : inputs) {
97 | sb.append(input);
98 | }
99 | byte[] bytes = sb.toString().getBytes();
100 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
101 | LineInputStream lis = new LineInputStream(bais);
102 | assertNotNull(lis);
103 | assertEquals("Cyberman", lis.readLine());
104 | assertEquals("Dalek", lis.readLine());
105 | assertEquals(null, lis.readLine());
106 | }
107 |
108 | @Test
109 | public void testSeveralLinesAllEndings() throws Exception {
110 | String[] inputs = {
111 | "Alice\r\n",
112 | "Bob\r\n",
113 | "Carol\r\n",
114 | "Dave\r\n",
115 | "Eve\r\n",
116 | "Mallory\r\n",
117 | "Trent\r\n"
118 | };
119 | StringBuilder sb = new StringBuilder();
120 | for(String input : inputs) {
121 | sb.append(input);
122 | }
123 | byte[] bytes = sb.toString().getBytes();
124 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
125 | LineInputStream lis = new LineInputStream(bais);
126 | assertNotNull(lis);
127 | assertEquals("Alice", lis.readLine());
128 | assertEquals("Bob", lis.readLine());
129 | assertEquals("Carol", lis.readLine());
130 | assertEquals("Dave", lis.readLine());
131 | assertEquals("Eve", lis.readLine());
132 | assertEquals("Mallory", lis.readLine());
133 | assertEquals("Trent", lis.readLine());
134 | assertEquals(null, lis.readLine());
135 | }
136 |
137 | @Test
138 | public void testSeveralLinesMixedEndings() throws Exception {
139 | String[] inputs = {
140 | "Alice\r",
141 | "Bob\n",
142 | "Carol\r\n",
143 | "Dave\r",
144 | "Eve\n",
145 | "Mallory\r\n",
146 | "Trent\r\n",
147 | "Eve\n",
148 | "Dave\r",
149 | "Carol\r\n",
150 | "Eve\n",
151 | };
152 | StringBuilder sb = new StringBuilder();
153 | for(String input : inputs) {
154 | sb.append(input);
155 | }
156 | byte[] bytes = sb.toString().getBytes();
157 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
158 | LineInputStream lis = new LineInputStream(bais);
159 | assertNotNull(lis);
160 | assertEquals("Alice\rBob\nCarol", lis.readLine());
161 | assertEquals("Dave\rEve\nMallory", lis.readLine());
162 | assertEquals("Trent", lis.readLine());
163 | assertEquals("Eve\nDave\rCarol", lis.readLine());
164 | assertEquals("Eve\n", lis.readLine());
165 | assertEquals(null, lis.readLine());
166 | }
167 |
168 | @Test
169 | public void testPlainRead() throws Exception {
170 | ByteArrayInputStream bais = new ByteArrayInputStream("Dalek".getBytes());
171 | LineInputStream lis = new LineInputStream(bais);
172 | assertNotNull(lis);
173 | assertEquals((int)'D', lis.read());
174 | assertEquals((int)'a', lis.read());
175 | assertEquals((int)'l', lis.read());
176 | assertEquals((int)'e', lis.read());
177 | assertEquals((int)'k', lis.read());
178 | assertEquals(-1, lis.read());
179 | }
180 |
181 | @Test
182 | public void testDecoratedIOExceptionNotSuppressed() throws Exception {
183 | InputStream is = new InputStream() {
184 | @Override
185 | public int read() throws IOException {
186 | throw new IOException("Exterminate!");
187 | }
188 | };
189 | LineInputStream lis = new LineInputStream(is);
190 | assertNotNull(lis);
191 | try {
192 | lis.read();
193 | fail();
194 | } catch (IOException e) {
195 | // expected
196 | }
197 | }
198 |
199 | @Test
200 | public void testSeveralLinesSomeEmpty() throws Exception {
201 | String[] inputs = {
202 | "Alice\r\n",
203 | "Bob\r\n",
204 | "\r\n",
205 | "Dave\r\n",
206 | "Eve\r\n",
207 | "\r\n",
208 | "Trent\r\n"
209 | };
210 | StringBuilder sb = new StringBuilder();
211 | for(String input : inputs) {
212 | sb.append(input);
213 | }
214 | byte[] bytes = sb.toString().getBytes();
215 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
216 | LineInputStream lis = new LineInputStream(bais);
217 | assertNotNull(lis);
218 | assertEquals("Alice", lis.readLine());
219 | assertEquals("Bob", lis.readLine());
220 | assertEquals("", lis.readLine());
221 | assertEquals("Dave", lis.readLine());
222 | assertEquals("Eve", lis.readLine());
223 | assertEquals("", lis.readLine());
224 | assertEquals("Trent", lis.readLine());
225 | assertEquals(null, lis.readLine());
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/src/test/java/com/pmeade/websocket/io/WebSocketServerOutputStreamTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketServerOutputStreamTest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.io;
20 |
21 | import java.io.ByteArrayOutputStream;
22 | import java.io.OutputStream;
23 | import org.junit.After;
24 | import org.junit.AfterClass;
25 | import org.junit.Before;
26 | import org.junit.BeforeClass;
27 | import org.junit.Test;
28 | import static org.junit.Assert.*;
29 |
30 | /**
31 | * @author pmeade
32 | */
33 | public class WebSocketServerOutputStreamTest
34 | {
35 | public WebSocketServerOutputStreamTest() {
36 | }
37 |
38 | @BeforeClass
39 | public static void setUpClass() {
40 | }
41 |
42 | @AfterClass
43 | public static void tearDownClass() {
44 | }
45 |
46 | @Before
47 | public void setUp() {
48 | }
49 |
50 | @After
51 | public void tearDown() {
52 | }
53 |
54 | @Test
55 | public void testAlwaysSucceed() {
56 | assertTrue(true);
57 | }
58 |
59 | @Test
60 | public void testExtendsOutputStream() {
61 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
62 | WebSocketServerOutputStream wsos = new WebSocketServerOutputStream(baos);
63 | assertTrue(wsos instanceof OutputStream);
64 | }
65 |
66 | @Test
67 | public void testRequiresDecorableOutputStream() {
68 | try {
69 | WebSocketServerOutputStream webSocketOutputStream = new WebSocketServerOutputStream(null);
70 | fail();
71 | } catch(NullPointerException e) {
72 | // expected
73 | }
74 | }
75 |
76 | @Test
77 | public void testSetHandshakeComplete() {
78 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
79 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
80 | assertFalse(wssos.isHandshakeComplete());
81 | wssos.setHandshakeComplete(true);
82 | assertTrue(wssos.isHandshakeComplete());
83 | }
84 |
85 | @Test
86 | public void testWriteArrayTransparentBeforeHandshake() throws Exception {
87 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
88 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
89 | assertFalse(wssos.isHandshakeComplete());
90 | wssos.write(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
91 | byte[] result = baos.toByteArray();
92 | assertEquals(0x01, result[0]);
93 | assertEquals(0x02, result[1]);
94 | assertEquals(0x03, result[2]);
95 | assertEquals(0x04, result[3]);
96 | assertEquals(0x05, result[4]);
97 | }
98 |
99 | @Test
100 | public void testWriteArrayNotTransparentAfterHandshake() throws Exception {
101 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
102 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
103 | assertFalse(wssos.isHandshakeComplete());
104 | wssos.setHandshakeComplete(true);
105 | assertTrue(wssos.isHandshakeComplete());
106 | wssos.write(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
107 | byte[] result = baos.toByteArray();
108 | assertEquals((byte)0x82, result[0]);
109 | assertEquals(0x05, result[1]);
110 | assertEquals(0x01, result[2]);
111 | assertEquals(0x02, result[3]);
112 | assertEquals(0x03, result[4]);
113 | assertEquals(0x04, result[5]);
114 | assertEquals(0x05, result[6]);
115 | }
116 |
117 | @Test
118 | public void testWriteByteTransparentBeforeHandshake() throws Exception {
119 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
120 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
121 | assertFalse(wssos.isHandshakeComplete());
122 | wssos.write(0x05);
123 | byte[] result = baos.toByteArray();
124 | assertEquals(0x05, result[0]);
125 | }
126 |
127 | @Test
128 | public void testWriteByteNotTransparentAfterHandshake() throws Exception {
129 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
130 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
131 | assertFalse(wssos.isHandshakeComplete());
132 | wssos.setHandshakeComplete(true);
133 | assertTrue(wssos.isHandshakeComplete());
134 | wssos.write(0x05);
135 | byte[] result = baos.toByteArray();
136 | assertEquals((byte)0x82, result[0]);
137 | assertEquals(0x01, result[1]);
138 | assertEquals(0x05, result[2]);
139 | }
140 |
141 | @Test
142 | public void testWriteArrayOffsetTransparentBeforeHandshake() throws Exception {
143 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
144 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
145 | assertFalse(wssos.isHandshakeComplete());
146 | final byte[] DATA = new byte[] {
147 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
148 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
149 | };
150 | wssos.write(DATA, 2, 4);
151 | byte[] result = baos.toByteArray();
152 | assertEquals(0x02, result[0]);
153 | assertEquals(0x03, result[1]);
154 | assertEquals(0x04, result[2]);
155 | assertEquals(0x05, result[3]);
156 | }
157 |
158 | @Test
159 | public void testWriteArrayOffsetNotTransparentAfterHandshake() throws Exception {
160 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
161 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
162 | assertFalse(wssos.isHandshakeComplete());
163 | wssos.setHandshakeComplete(true);
164 | assertTrue(wssos.isHandshakeComplete());
165 | final byte[] DATA = new byte[] {
166 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
167 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
168 | };
169 | wssos.write(DATA, 2, 4);
170 | byte[] result = baos.toByteArray();
171 | assertEquals((byte)0x82, result[0]);
172 | assertEquals(0x04, result[1]);
173 | assertEquals(0x02, result[2]);
174 | assertEquals(0x03, result[3]);
175 | assertEquals(0x04, result[4]);
176 | assertEquals(0x05, result[5]);
177 | }
178 |
179 | @Test
180 | public void testWriteCloseTwiceOutputsOneFrame() throws Exception {
181 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
182 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
183 | assertFalse(wssos.isHandshakeComplete());
184 | wssos.setHandshakeComplete(true);
185 | assertTrue(wssos.isHandshakeComplete());
186 | wssos.writeClose();
187 | wssos.writeClose();
188 | byte[] result = baos.toByteArray();
189 | assertEquals(2, result.length);
190 | assertEquals((byte)0x88, result[0]);
191 | assertEquals(0x00, result[1]);
192 | }
193 |
194 | @Test
195 | public void testWriteCloseStatusCodeTwiceOutputsOneFrame() throws Exception {
196 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
197 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
198 | assertFalse(wssos.isHandshakeComplete());
199 | wssos.setHandshakeComplete(true);
200 | assertTrue(wssos.isHandshakeComplete());
201 | wssos.writeClose(0x1000);
202 | wssos.writeClose(0x1000);
203 | byte[] result = baos.toByteArray();
204 | assertEquals(4, result.length);
205 | assertEquals((byte)0x88, result[0]);
206 | assertEquals(0x02, result[1]);
207 | assertEquals(0x10, result[2]);
208 | assertEquals(0x00, result[3]);
209 | }
210 |
211 | @Test
212 | public void testWriteStringSmallPayload() throws Exception {
213 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
214 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
215 | assertFalse(wssos.isHandshakeComplete());
216 | wssos.setHandshakeComplete(true);
217 | assertTrue(wssos.isHandshakeComplete());
218 | wssos.writeString("\u00a9");
219 | byte[] result = baos.toByteArray();
220 | assertEquals(4, result.length);
221 | assertEquals((byte)0x81, result[0]);
222 | assertEquals(0x02, result[1]);
223 | assertEquals((byte)0xc2, result[2]);
224 | assertEquals((byte)0xa9, result[3]);
225 | }
226 |
227 | @Test
228 | public void testWriteStringMediumPayload() throws Exception {
229 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
230 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
231 | assertFalse(wssos.isHandshakeComplete());
232 | wssos.setHandshakeComplete(true);
233 | assertTrue(wssos.isHandshakeComplete());
234 | StringBuilder sb = new StringBuilder();
235 | for(int i=0; i<0x100; i++) {
236 | sb.append("\u00a9");
237 | }
238 | wssos.writeString(sb.toString());
239 | byte[] result = baos.toByteArray();
240 | assertEquals(516, result.length);
241 | assertEquals((byte)0x81, result[0]);
242 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_16, result[1]);
243 | assertEquals((byte)0x02, result[2]);
244 | assertEquals((byte)0x00, result[3]);
245 | assertEquals((byte)0xc2, result[4]);
246 | assertEquals((byte)0xa9, result[5]);
247 | assertEquals((byte)0xc2, result[6]);
248 | assertEquals((byte)0xa9, result[7]);
249 | }
250 |
251 | @Test
252 | public void testWriteStringLargePayload() throws Exception {
253 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
254 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
255 | assertFalse(wssos.isHandshakeComplete());
256 | wssos.setHandshakeComplete(true);
257 | assertTrue(wssos.isHandshakeComplete());
258 | StringBuilder sb = new StringBuilder();
259 | for(int i=0; i<0x10000; i++) {
260 | sb.append("\u00a9");
261 | }
262 | wssos.writeString(sb.toString());
263 | byte[] result = baos.toByteArray();
264 | assertEquals(0x20000 + 10, result.length);
265 | assertEquals((byte)0x81, result[0]);
266 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_64, result[1]);
267 | assertEquals((byte)0x00, result[2]);
268 | assertEquals((byte)0x00, result[3]);
269 | assertEquals((byte)0x00, result[4]);
270 | assertEquals((byte)0x00, result[5]);
271 | assertEquals((byte)0x00, result[6]);
272 | assertEquals((byte)0x02, result[7]);
273 | assertEquals((byte)0x00, result[8]);
274 | assertEquals((byte)0x00, result[9]);
275 | assertEquals((byte)0xc2, result[10]);
276 | assertEquals((byte)0xa9, result[11]);
277 | assertEquals((byte)0xc2, result[12]);
278 | assertEquals((byte)0xa9, result[13]);
279 | assertEquals((byte)0xc2, result[14]);
280 | assertEquals((byte)0xa9, result[15]);
281 | }
282 |
283 | @Test
284 | public void testWriteBinaryMediumPayload() throws Exception {
285 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
286 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
287 | assertFalse(wssos.isHandshakeComplete());
288 | wssos.setHandshakeComplete(true);
289 | assertTrue(wssos.isHandshakeComplete());
290 | byte[] DATA = new byte[0x200];
291 | for(int i=0; i<0x200; i++) {
292 | DATA[i] = (byte)(i & 0xff);
293 | }
294 | wssos.write(DATA);
295 | byte[] result = baos.toByteArray();
296 | assertEquals(0x200 + 4, result.length);
297 | assertEquals((byte)0x82, result[0]);
298 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_16, result[1]);
299 | assertEquals((byte)0x02, result[2]);
300 | assertEquals((byte)0x00, result[3]);
301 | assertEquals((byte)0x00, result[4]);
302 | assertEquals((byte)0x01, result[5]);
303 | assertEquals((byte)0x02, result[6]);
304 | assertEquals((byte)0x03, result[7]);
305 | }
306 |
307 | @Test
308 | public void testWriteBinaryLargePayload() throws Exception {
309 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
310 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
311 | assertFalse(wssos.isHandshakeComplete());
312 | wssos.setHandshakeComplete(true);
313 | assertTrue(wssos.isHandshakeComplete());
314 | byte[] DATA = new byte[0x20000];
315 | for(int i=0; i<0x20000; i++) {
316 | DATA[i] = (byte)(i & 0xff);
317 | }
318 | wssos.write(DATA);
319 | byte[] result = baos.toByteArray();
320 | assertEquals(0x20000 + 10, result.length);
321 | assertEquals((byte)0x82, result[0]);
322 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_64, result[1]);
323 | assertEquals((byte)0x00, result[2]);
324 | assertEquals((byte)0x00, result[3]);
325 | assertEquals((byte)0x00, result[4]);
326 | assertEquals((byte)0x00, result[5]);
327 | assertEquals((byte)0x00, result[6]);
328 | assertEquals((byte)0x02, result[7]);
329 | assertEquals((byte)0x00, result[8]);
330 | assertEquals((byte)0x00, result[9]);
331 | assertEquals((byte)0x00, result[10]);
332 | assertEquals((byte)0x01, result[11]);
333 | assertEquals((byte)0x02, result[12]);
334 | assertEquals((byte)0x03, result[13]);
335 | assertEquals((byte)0x04, result[14]);
336 | assertEquals((byte)0x05, result[15]);
337 | }
338 | }
339 |
--------------------------------------------------------------------------------
/src/test/java/com/pmeade/websocket/net/WebSocketServerSocketTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketServerSocketTest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.net;
20 |
21 | import java.net.InetAddress;
22 | import java.net.ServerSocket;
23 | import java.net.SocketAddress;
24 | import java.nio.channels.ServerSocketChannel;
25 | import org.junit.After;
26 | import org.junit.AfterClass;
27 | import org.junit.Before;
28 | import org.junit.BeforeClass;
29 | import org.junit.Test;
30 | import static org.junit.Assert.*;
31 | import static org.mockito.Mockito.*;
32 |
33 | /**
34 | * @author veloxi
35 | */
36 | public class WebSocketServerSocketTest
37 | {
38 | private ServerSocket mockSS;
39 | private WebSocketServerSocket wsss;
40 |
41 | public WebSocketServerSocketTest() {
42 | }
43 |
44 | @BeforeClass
45 | public static void setUpClass() {
46 | }
47 |
48 | @AfterClass
49 | public static void tearDownClass() {
50 | }
51 |
52 | @Before
53 | public void setUp() throws Exception {
54 | mockSS = mock(ServerSocket.class);
55 | wsss = new WebSocketServerSocket(mockSS);
56 | }
57 |
58 | @After
59 | public void tearDown() throws Exception {
60 | }
61 |
62 | @Test
63 | public void testAlwaysSucceed() {
64 | assertTrue(true);
65 | verifyZeroInteractions(mockSS);
66 | }
67 |
68 | @Test
69 | public void testExtendsServerSocket() throws Exception {
70 | assertTrue(wsss instanceof ServerSocket);
71 | verifyZeroInteractions(mockSS);
72 | }
73 |
74 | @Test
75 | public void testBindDelegatesToServerSocket() throws Exception {
76 | SocketAddress sa = mock(SocketAddress.class);
77 | wsss.bind(sa);
78 | verify(mockSS).bind(sa);
79 | verifyZeroInteractions(sa);
80 | }
81 |
82 | @Test
83 | public void testBindWithBacklogDelegatesToServerSocket() throws Exception {
84 | SocketAddress sa = mock(SocketAddress.class);
85 | wsss.bind(sa, 12345);
86 | verify(mockSS).bind(sa, 12345);
87 | verifyZeroInteractions(sa);
88 | }
89 |
90 | @Test
91 | public void testGetInetAddressDelegatesToServerSocket() throws Exception {
92 | InetAddress ia = mock(InetAddress.class);
93 | when(mockSS.getInetAddress()).thenReturn(ia);
94 | InetAddress result = wsss.getInetAddress();
95 | assertEquals(ia, result);
96 | verify(mockSS).getInetAddress();
97 | verifyZeroInteractions(ia);
98 | }
99 |
100 | @Test
101 | public void testGetLocalPortDelegatesToServerSocket() throws Exception {
102 | when(mockSS.getLocalPort()).thenReturn(42);
103 | int result = wsss.getLocalPort();
104 | assertEquals(42, result);
105 | verify(mockSS).getLocalPort();
106 | }
107 |
108 | @Test
109 | public void testGetLocalAddressDelegatesToServerSocket() throws Exception {
110 | SocketAddress sa = mock(SocketAddress.class);
111 | when(mockSS.getLocalSocketAddress()).thenReturn(sa);
112 | SocketAddress result = wsss.getLocalSocketAddress();
113 | assertEquals(sa, result);
114 | verify(mockSS).getLocalSocketAddress();
115 | verifyZeroInteractions(sa);
116 | }
117 |
118 | @Test
119 | public void testGetChannelThrowsUnsupportedOperationException() throws Exception {
120 | try {
121 | ServerSocketChannel ssc = wsss.getChannel();
122 | fail();
123 | } catch (UnsupportedOperationException e) {
124 | // expected
125 | }
126 | verifyZeroInteractions(mockSS);
127 | }
128 |
129 | @Test
130 | public void testSetSoTimeoutDelegatesToSocket() throws Exception {
131 | wsss.setSoTimeout(123456);
132 | verify(mockSS).setSoTimeout(123456);
133 | }
134 |
135 | @Test
136 | public void testGetSoTimeoutDelegatesToSocket() throws Exception {
137 | when(mockSS.getSoTimeout()).thenReturn(123456);
138 | assertEquals(123456, wsss.getSoTimeout());
139 | verify(mockSS).getSoTimeout();
140 | }
141 |
142 | @Test
143 | public void testSetReceiveBufferSizeDelegatesToSocket() throws Exception {
144 | wsss.setReceiveBufferSize(654321);
145 | verify(mockSS).setReceiveBufferSize(654321);
146 | }
147 |
148 | @Test
149 | public void testGetReceiveBufferSizeDelegatesToSocket() throws Exception {
150 | when(mockSS.getReceiveBufferSize()).thenReturn(654321);
151 | assertEquals(654321, wsss.getReceiveBufferSize());
152 | verify(mockSS).getReceiveBufferSize();
153 | }
154 |
155 | @Test
156 | public void testSetReuseAddressDelegatesToSocket() throws Exception {
157 | wsss.setReuseAddress(true);
158 | wsss.setReuseAddress(false);
159 | verify(mockSS).setReuseAddress(true);
160 | verify(mockSS).setReuseAddress(false);
161 | }
162 |
163 | @Test
164 | public void testGetReuseAddressDelegatesToSocket() throws Exception {
165 | when(mockSS.getReuseAddress()).thenReturn(false);
166 | assertFalse(wsss.getReuseAddress());
167 | verify(mockSS).getReuseAddress();
168 | }
169 |
170 | @Test
171 | public void testCloseDelegatesToSocket() throws Exception {
172 | wsss.close();
173 | verify(mockSS).close();
174 | }
175 |
176 | @Test
177 | public void testToStringDelegatesToSocket() throws Exception {
178 | when(mockSS.toString()).thenReturn("Exterminate!");
179 | assertEquals("Exterminate!", wsss.toString());
180 | // toString() ... Mockito's Kryptonite
181 | // verify(mockSS).toString();
182 | }
183 |
184 | @Test
185 | public void testIsBoundDelegatesToSocket() throws Exception {
186 | when(mockSS.isBound()).thenReturn(true);
187 | assertTrue(wsss.isBound());
188 | verify(mockSS).isBound();
189 | }
190 |
191 | @Test
192 | public void testIsClosedDelegatesToSocket() throws Exception {
193 | when(mockSS.isClosed()).thenReturn(true);
194 | assertTrue(wsss.isClosed());
195 | verify(mockSS).isClosed();
196 | }
197 |
198 | @Test
199 | public void testSetPerformancePreferencesDelegatesToSocket() throws Exception {
200 | wsss.setPerformancePreferences(123, 456, 789);
201 | verify(mockSS).setPerformancePreferences(123, 456, 789);
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/src/test/java/com/pmeade/websocket/net/WebSocketTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketTest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.net;
20 |
21 | import com.pmeade.websocket.io.WebSocketServerInputStream;
22 | import com.pmeade.websocket.io.WebSocketServerOutputStream;
23 | import java.io.InputStream;
24 | import java.io.OutputStream;
25 | import java.net.InetAddress;
26 | import java.net.ServerSocket;
27 | import java.net.Socket;
28 | import java.net.SocketAddress;
29 | import java.nio.channels.SocketChannel;
30 | import org.junit.After;
31 | import org.junit.AfterClass;
32 | import org.junit.Before;
33 | import org.junit.BeforeClass;
34 | import org.junit.Test;
35 | import static org.junit.Assert.*;
36 | import static org.mockito.Mockito.*;
37 |
38 | /**
39 | * @author veloxi
40 | */
41 | public class WebSocketTest
42 | {
43 | private Socket mockS;
44 | private ServerSocket mockSS;
45 | private Socket ws;
46 | private WebSocketServerSocket wsss;
47 |
48 | public WebSocketTest() {
49 | }
50 |
51 | @BeforeClass
52 | public static void setUpClass() {
53 | }
54 |
55 | @AfterClass
56 | public static void tearDownClass() {
57 | }
58 |
59 | @Before
60 | public void setUp() throws Exception {
61 | mockS = mock(Socket.class);
62 | mockSS = mock(ServerSocket.class);
63 | when(mockSS.accept()).thenReturn(mockS);
64 | wsss = new WebSocketServerSocket(mockSS);
65 | ws = wsss.accept();
66 | }
67 |
68 | @After
69 | public void tearDown() throws Exception {
70 | verify(mockSS, times(1)).accept();
71 | }
72 |
73 | @Test
74 | public void testAlwaysSucceed() {
75 | assertTrue(true);
76 | verifyZeroInteractions(mockS);
77 | }
78 |
79 | @Test
80 | public void testExtendsSocket() {
81 | Socket s = new Socket();
82 | WebSocket webSocket = new WebSocket(s);
83 | assertTrue(webSocket instanceof Socket);
84 | verifyZeroInteractions(mockS);
85 | }
86 |
87 | @Test
88 | public void testAcceptReturnsWebSocket() throws Exception {
89 | assertTrue(ws instanceof WebSocket);
90 | verifyZeroInteractions(mockS);
91 | }
92 |
93 | @Test
94 | public void testConnectDelegatesToSocket() throws Exception {
95 | SocketAddress sa = mock(SocketAddress.class);
96 | ws.connect(sa);
97 | verify(mockS).connect(sa);
98 | verifyZeroInteractions(sa);
99 | }
100 |
101 | @Test
102 | public void testConnectWithTimeoutDelegatesToSocket() throws Exception {
103 | SocketAddress sa = mock(SocketAddress.class);
104 | ws.connect(sa, 9001);
105 | verify(mockS).connect(sa, 9001);
106 | verifyZeroInteractions(sa);
107 | }
108 |
109 | @Test
110 | public void testBindDelegatesToSocket() throws Exception {
111 | SocketAddress sa = mock(SocketAddress.class);
112 | ws.bind(sa);
113 | verify(mockS).bind(sa);
114 | verifyZeroInteractions(sa);
115 | }
116 |
117 | @Test
118 | public void testGetInetAddressDelegatesToSocket() throws Exception {
119 | InetAddress ia = mock(InetAddress.class);
120 | when(mockS.getInetAddress()).thenReturn(ia);
121 | InetAddress result = ws.getInetAddress();
122 | assertEquals(ia, result);
123 | verify(mockS).getInetAddress();
124 | verifyZeroInteractions(ia);
125 | }
126 |
127 | @Test
128 | public void testGetLocalAddressDelegatesToSocket() throws Exception {
129 | InetAddress ia = mock(InetAddress.class);
130 | when(mockS.getLocalAddress()).thenReturn(ia);
131 | InetAddress result = ws.getLocalAddress();
132 | assertEquals(ia, result);
133 | verify(mockS).getLocalAddress();
134 | verifyZeroInteractions(ia);
135 | }
136 |
137 | @Test
138 | public void testGetPortDelegatesToSocket() throws Exception {
139 | when(mockS.getPort()).thenReturn(42);
140 | int result = ws.getPort();
141 | assertEquals(42, result);
142 | verify(mockS).getPort();
143 | }
144 |
145 | @Test
146 | public void testGetLocalPortDelegatesToSocket() throws Exception {
147 | when(mockS.getLocalPort()).thenReturn(69);
148 | int result = ws.getLocalPort();
149 | assertEquals(69, result);
150 | verify(mockS).getLocalPort();
151 | }
152 |
153 | @Test
154 | public void testGetRemoteSocketAddressDelegatesToSocket() throws Exception {
155 | SocketAddress sa = mock(SocketAddress.class);
156 | when(mockS.getRemoteSocketAddress()).thenReturn(sa);
157 | SocketAddress result = ws.getRemoteSocketAddress();
158 | assertEquals(sa, result);
159 | verify(mockS).getRemoteSocketAddress();
160 | verifyZeroInteractions(sa);
161 | }
162 |
163 | @Test
164 | public void testGetLocalSocketAddressDelegatesToSocket() throws Exception {
165 | SocketAddress sa = mock(SocketAddress.class);
166 | when(mockS.getLocalSocketAddress()).thenReturn(sa);
167 | SocketAddress result = ws.getLocalSocketAddress();
168 | assertEquals(sa, result);
169 | verify(mockS).getLocalSocketAddress();
170 | verifyZeroInteractions(sa);
171 | }
172 |
173 | @Test
174 | public void testGetChannelThrowsUnsupportedOperationException() throws Exception {
175 | try {
176 | SocketChannel sc = ws.getChannel();
177 | fail();
178 | } catch (UnsupportedOperationException e) {
179 | // expected
180 | }
181 | verifyZeroInteractions(mockS);
182 | }
183 |
184 | @Test
185 | public void testGetInputStreamReturnsWebSocketServerInputStream() throws Exception {
186 | InputStream is = mock(InputStream.class);
187 | OutputStream os = mock(OutputStream.class);
188 | when(mockS.getInputStream()).thenReturn(is);
189 | when(mockS.getOutputStream()).thenReturn(os);
190 | InputStream result = ws.getInputStream();
191 | assertNotEquals(is, result);
192 | assertTrue(result instanceof WebSocketServerInputStream);
193 | verify(mockS).getInputStream();
194 | verify(mockS).getOutputStream();
195 | verifyZeroInteractions(is);
196 | verifyZeroInteractions(os);
197 | }
198 |
199 | @Test
200 | public void testWebSocketServerInputStreamHasOutputPeer() throws Exception {
201 | InputStream is = mock(InputStream.class);
202 | OutputStream os = mock(OutputStream.class);
203 | when(mockS.getInputStream()).thenReturn(is);
204 | when(mockS.getOutputStream()).thenReturn(os);
205 | InputStream result = ws.getInputStream();
206 | assertNotEquals(is, result);
207 | assertTrue(result instanceof WebSocketServerInputStream);
208 | WebSocketServerInputStream wssis = (WebSocketServerInputStream) result;
209 | assertNotNull(wssis.getOutputPeer());
210 | verify(mockS).getInputStream();
211 | verify(mockS).getOutputStream();
212 | verifyZeroInteractions(is);
213 | verifyZeroInteractions(os);
214 | }
215 |
216 | @Test
217 | public void testGetInputStreamTwiceReturnsSameObject() throws Exception {
218 | InputStream is = mock(InputStream.class);
219 | OutputStream os = mock(OutputStream.class);
220 | when(mockS.getInputStream()).thenReturn(is);
221 | when(mockS.getOutputStream()).thenReturn(os);
222 | InputStream result = ws.getInputStream();
223 | assertNotEquals(is, result);
224 | assertTrue(result instanceof WebSocketServerInputStream);
225 | WebSocketServerInputStream wssis = (WebSocketServerInputStream) result;
226 | assertNotNull(wssis.getOutputPeer());
227 | InputStream result2 = ws.getInputStream();
228 | assertNotNull(result2);
229 | assertEquals(result, result2);
230 | assertEquals(wssis, result2);
231 | verify(mockS, times(1)).getInputStream();
232 | verify(mockS, times(1)).getOutputStream();
233 | verifyZeroInteractions(is);
234 | verifyZeroInteractions(os);
235 | }
236 |
237 | @Test
238 | public void testGetOutputStreamReturnsWebSocketServerOutputStream() throws Exception {
239 | OutputStream os = mock(OutputStream.class);
240 | when(mockS.getOutputStream()).thenReturn(os);
241 | OutputStream result = ws.getOutputStream();
242 | assertNotEquals(os, result);
243 | assertTrue(result instanceof WebSocketServerOutputStream);
244 | verify(mockS).getOutputStream();
245 | verifyZeroInteractions(os);
246 | }
247 |
248 | @Test
249 | public void testGetOutputStreamTwiceReturnsSameObject() throws Exception {
250 | OutputStream os = mock(OutputStream.class);
251 | when(mockS.getOutputStream()).thenReturn(os);
252 | OutputStream result = ws.getOutputStream();
253 | assertNotEquals(os, result);
254 | assertTrue(result instanceof WebSocketServerOutputStream);
255 | WebSocketServerOutputStream wssos = (WebSocketServerOutputStream) result;
256 | OutputStream result2 = ws.getOutputStream();
257 | assertNotNull(result2);
258 | assertEquals(result, result2);
259 | assertEquals(wssos, result2);
260 | verify(mockS, times(1)).getOutputStream();
261 | verifyZeroInteractions(os);
262 | }
263 |
264 | @Test
265 | public void testSetTcpNoDelayDelegatesToSocket() throws Exception {
266 | ws.setTcpNoDelay(true);
267 | ws.setTcpNoDelay(false);
268 | verify(mockS).setTcpNoDelay(true);
269 | verify(mockS).setTcpNoDelay(false);
270 | }
271 |
272 | @Test
273 | public void testGetTcpNoDelayDelegatesToSocket() throws Exception {
274 | when(mockS.getTcpNoDelay()).thenReturn(false);
275 | assertFalse(ws.getTcpNoDelay());
276 | verify(mockS).getTcpNoDelay();
277 | }
278 |
279 | @Test
280 | public void testGetTcpNoDelayDelegatesToSocket2() throws Exception {
281 | when(mockS.getTcpNoDelay()).thenReturn(true);
282 | assertTrue(ws.getTcpNoDelay());
283 | verify(mockS).getTcpNoDelay();
284 | }
285 |
286 | @Test
287 | public void testSetSoLignerDelegatesToSocket() throws Exception {
288 | ws.setSoLinger(true, 1337);
289 | verify(mockS).setSoLinger(true, 1337);
290 | }
291 |
292 | @Test
293 | public void testGetSoLingerDelegatesToSocket() throws Exception {
294 | when(mockS.getSoLinger()).thenReturn(1337);
295 | assertEquals(1337, ws.getSoLinger());
296 | verify(mockS).getSoLinger();
297 | }
298 |
299 | @Test
300 | public void testSendUrgentDataDelegatesToSocket() throws Exception {
301 | ws.sendUrgentData(49152);
302 | verify(mockS).sendUrgentData(49152);
303 | }
304 |
305 | @Test
306 | public void testSetOobInlineDelegatesToSocket() throws Exception {
307 | ws.setOOBInline(true);
308 | verify(mockS).setOOBInline(true);
309 | }
310 |
311 | @Test
312 | public void testGetOobInlineDelegatesToSocket() throws Exception {
313 | when(mockS.getOOBInline()).thenReturn(true);
314 | assertTrue(ws.getOOBInline());
315 | verify(mockS).getOOBInline();
316 | }
317 |
318 | @Test
319 | public void testSetSoTimeoutDelegatesToSocket() throws Exception {
320 | ws.setSoTimeout(123456);
321 | verify(mockS).setSoTimeout(123456);
322 | }
323 |
324 | @Test
325 | public void testGetSoTimeoutDelegatesToSocket() throws Exception {
326 | when(mockS.getSoTimeout()).thenReturn(123456);
327 | assertEquals(123456, ws.getSoTimeout());
328 | verify(mockS).getSoTimeout();
329 | }
330 |
331 | @Test
332 | public void testSetSendBufferSizeDelegatesToSocket() throws Exception {
333 | ws.setSendBufferSize(654321);
334 | verify(mockS).setSendBufferSize(654321);
335 | }
336 |
337 | @Test
338 | public void testGetSendBufferSizeDelegatesToSocket() throws Exception {
339 | when(mockS.getSendBufferSize()).thenReturn(654321);
340 | assertEquals(654321, ws.getSendBufferSize());
341 | verify(mockS).getSendBufferSize();
342 | }
343 |
344 | @Test
345 | public void testSetReceiveBufferSizeDelegatesToSocket() throws Exception {
346 | ws.setReceiveBufferSize(654321);
347 | verify(mockS).setReceiveBufferSize(654321);
348 | }
349 |
350 | @Test
351 | public void testGetReceiveBufferSizeDelegatesToSocket() throws Exception {
352 | when(mockS.getReceiveBufferSize()).thenReturn(654321);
353 | assertEquals(654321, ws.getReceiveBufferSize());
354 | verify(mockS).getReceiveBufferSize();
355 | }
356 |
357 | @Test
358 | public void testSetKeepAliveDelegatesToSocket() throws Exception {
359 | ws.setKeepAlive(true);
360 | ws.setKeepAlive(false);
361 | verify(mockS).setKeepAlive(true);
362 | verify(mockS).setKeepAlive(false);
363 | }
364 |
365 | @Test
366 | public void testGetKeepAliveDelegatesToSocket() throws Exception {
367 | when(mockS.getKeepAlive()).thenReturn(false);
368 | assertFalse(ws.getKeepAlive());
369 | verify(mockS).getKeepAlive();
370 | }
371 |
372 | @Test
373 | public void testSetTrafficClassDelegatesToSocket() throws Exception {
374 | ws.setTrafficClass(5);
375 | verify(mockS).setTrafficClass(5);
376 | }
377 |
378 | @Test
379 | public void testGetTrafficClassDelegatesToSocket() throws Exception {
380 | when(mockS.getTrafficClass()).thenReturn(6);
381 | assertEquals(6, ws.getTrafficClass());
382 | verify(mockS).getTrafficClass();
383 | }
384 |
385 | @Test
386 | public void testSetReuseAddressDelegatesToSocket() throws Exception {
387 | ws.setReuseAddress(true);
388 | ws.setReuseAddress(false);
389 | verify(mockS).setReuseAddress(true);
390 | verify(mockS).setReuseAddress(false);
391 | }
392 |
393 | @Test
394 | public void testGetReuseAddressDelegatesToSocket() throws Exception {
395 | when(mockS.getReuseAddress()).thenReturn(false);
396 | assertFalse(ws.getReuseAddress());
397 | verify(mockS).getReuseAddress();
398 | }
399 |
400 | @Test
401 | public void testCloseDelegatesToSocket() throws Exception {
402 | ws.close();
403 | verify(mockS).close();
404 | }
405 |
406 | @Test
407 | public void testShutdownInputDelegatesToSocket() throws Exception {
408 | ws.shutdownInput();
409 | verify(mockS).shutdownInput();
410 | }
411 |
412 | @Test
413 | public void testShutdownOutputDelegatesToSocket() throws Exception {
414 | ws.shutdownOutput();
415 | verify(mockS).shutdownOutput();
416 | }
417 |
418 | @Test
419 | public void testToStringDelegatesToSocket() throws Exception {
420 | when(mockS.toString()).thenReturn("Exterminate!");
421 | assertEquals("Exterminate!", ws.toString());
422 | // toString() ... Mockito's Kryptonite
423 | // verify(mockS).toString();
424 | }
425 |
426 | @Test
427 | public void testIsConnectedDelegatesToSocket() throws Exception {
428 | when(mockS.isConnected()).thenReturn(true);
429 | assertTrue(ws.isConnected());
430 | verify(mockS).isConnected();
431 | }
432 |
433 | @Test
434 | public void testIsBoundDelegatesToSocket() throws Exception {
435 | when(mockS.isBound()).thenReturn(true);
436 | assertTrue(ws.isBound());
437 | verify(mockS).isBound();
438 | }
439 |
440 | @Test
441 | public void testIsClosedDelegatesToSocket() throws Exception {
442 | when(mockS.isClosed()).thenReturn(true);
443 | assertTrue(ws.isClosed());
444 | verify(mockS).isClosed();
445 | }
446 |
447 | @Test
448 | public void testIsInputShutdownDelegatesToSocket() throws Exception {
449 | when(mockS.isInputShutdown()).thenReturn(true);
450 | assertTrue(ws.isInputShutdown());
451 | verify(mockS).isInputShutdown();
452 | }
453 |
454 | @Test
455 | public void testIsOutputShutdownDelegatesToSocket() throws Exception {
456 | when(mockS.isOutputShutdown()).thenReturn(true);
457 | assertTrue(ws.isOutputShutdown());
458 | verify(mockS).isOutputShutdown();
459 | }
460 |
461 | @Test
462 | public void testSetPerformancePreferencesDelegatesToSocket() throws Exception {
463 | ws.setPerformancePreferences(123, 456, 789);
464 | verify(mockS).setPerformancePreferences(123, 456, 789);
465 | }
466 | }
467 |
--------------------------------------------------------------------------------
/start-echo-server:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # start-echo-server
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 | #----------------------------------------------------------------------------
17 |
18 | java -cp "target/classes:target/dependency/*" com.pmeade.websocket.example.EchoServer
19 |
20 | #----------------------------------------------------------------------------
21 | # end of start-echo-server
22 |
--------------------------------------------------------------------------------