Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(546)

Side by Side Diff: plugins/org.chromium.sdk.wipbackend.wk118685/src/org/chromium/sdk/internal/websocket/Hybi17WsConnection.java

Issue 11829027: drop old backends (Closed) Base URL: https://chromedevtools.googlecode.com/svn/trunk
Patch Set: Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.sdk.internal.websocket;
6
7 import java.io.IOException;
8 import java.net.InetSocketAddress;
9 import java.util.Random;
10 import java.util.logging.Level;
11 import java.util.logging.Logger;
12
13 import org.chromium.sdk.ConnectionLogger;
14 import org.chromium.sdk.internal.websocket.ManualLoggingSocketWrapper.LoggableIn put;
15 import org.chromium.sdk.internal.websocket.ManualLoggingSocketWrapper.LoggableOu tput;
16 import org.chromium.sdk.util.BasicUtil;
17
18 /**
19 * WebSocket connection. Sends and receives messages. Implements HyBi-17 protoco l specification.
20 * @see http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
21 */
22 public class Hybi17WsConnection extends AbstractWsConnection<LoggableInput, Logg ableOutput> {
23 private static final Logger LOGGER = Logger.getLogger(Hybi17WsConnection.class .getName());
24 private static final Random RANDOM = new Random();
25
26 /**
27 * Specifies how outgoing frames get masked. While protocol specification requ ires that every
28 * outgoing frame must be masked (to disable provocative content that socket c lient may send),
29 * this doesn't really make sense when the client is trusted. On the other han d, transparent mask
30 * makes debug sniffering easier.
31 */
32 public enum MaskStrategy {
33 /**
34 * Directs to use no mask at all. This is explicitly against protocol specif ication, peer
35 * is expected to terminate connection in response.
36 */
37 NO_MASK() {
38 @Override public byte[] generate() {
39 return null;
40 }
41
42 @Override
43 ManualLoggingSocketWrapper.FactoryBase getLogWrapperFactory() {
44 return ManualLoggingSocketWrapper.PLAIN_ASCII;
45 }
46 },
47 /**
48 * Directs to always use transparent mask (i.e. all zeroes). This makes all frames clear-text.
49 * Not suitable when untrusted client uses the WebSocket.
50 */
51 TRANSPARENT_MASK() {
52 private final byte[] bytes = new byte[4];
53
54 @Override public byte[] generate() {
55 return bytes;
56 }
57
58 @Override
59 ManualLoggingSocketWrapper.FactoryBase getLogWrapperFactory() {
60 return ManualLoggingSocketWrapper.PLAIN_ASCII;
61 }
62 },
63 /**
64 * Directs to use randomly generated masks as specified by specification. As a by-product makes
65 * traffic hard to sniff.
66 */
67 NORMAL_MASK() {
68 @Override
69 byte[] generate() {
70 byte[] result = new byte[4];
71 RANDOM.nextBytes(result);
72 return result;
73 }
74
75 @Override
76 ManualLoggingSocketWrapper.FactoryBase getLogWrapperFactory() {
77 return ManualLoggingSocketWrapper.ANNOTATED;
78 }
79 };
80
81 /** @return 4-byte array or null */
82 abstract byte[] generate();
83
84 abstract ManualLoggingSocketWrapper.FactoryBase getLogWrapperFactory();
85 }
86
87 public static Hybi17WsConnection connect(InetSocketAddress endpoint, int timeo ut,
88 String resourceId, MaskStrategy maskStrategy, ConnectionLogger connectionL ogger)
89 throws IOException {
90 ManualLoggingSocketWrapper socketWrapper = new ManualLoggingSocketWrapper(en dpoint, timeout,
91 connectionLogger, maskStrategy.getLogWrapperFactory());
92
93 boolean handshakeDone = false;
94 Exception handshakeException = null;
95 try {
96 performHandshakeOrFail(socketWrapper, endpoint, resourceId);
97 handshakeDone = true;
98 } catch (RuntimeException e) {
99 handshakeException = e;
100 throw e;
101 } catch (IOException e) {
102 handshakeException = e;
103 throw e;
104 } finally {
105 if (!handshakeDone) {
106 socketWrapper.getShutdownRelay().sendSignal(null, handshakeException);
107 }
108 }
109
110 return new Hybi17WsConnection(socketWrapper, maskStrategy, connectionLogger) ;
111 }
112
113 private final MaskStrategy maskStrategy;
114
115 private Hybi17WsConnection(ManualLoggingSocketWrapper socketWrapper, MaskStrat egy maskStrategy,
116 ConnectionLogger connectionLogger) {
117 super(socketWrapper, connectionLogger);
118 this.maskStrategy = maskStrategy;
119 }
120
121 @Override
122 public void sendTextualMessage(final String message) throws IOException {
123 final byte[] bytes = message.getBytes(UTF_8_CHARSET);
124
125 LoggablePayload payload = new LoggablePayload() {
126 @Override void send(LoggableOutput output, byte[] maskBytes) throws IOExce ption {
127 output.writeToLog(message, "utf-8 demasked");
128 if (maskBytes != null) {
129 for (int i = 0; i < bytes.length; i++) {
130 bytes[i] = (byte) (bytes[i] ^ maskBytes[i % 4]);
131 }
132 }
133 output.writeBytesNoLogging(bytes);
134 }
135 @Override int getLength() {
136 return bytes.length;
137 }
138 };
139
140 sendMessage(OpCode.TEXT, payload, false);
141 }
142
143 @Override
144 protected CloseReason runListenLoop(LoggableInput loggableReader)
145 throws IOException, InterruptedException {
146 try {
147 return runListenLoopImpl(loggableReader);
148 } catch (IOException e) {
149 String stackTrace = BasicUtil.getStacktraceString(e);
150 try {
151 sendClosingMessage(StatusCode.PROTOCOL_ERROR, stackTrace);
152 } catch (IOException e2) {
153 // Connection may be closed by this time. We probably don't want to log this exception.
154 }
155 throw new IOException(e);
156 } catch (IncomingProtocolException e) {
157 String stackTrace = BasicUtil.getStacktraceString(e);
158 sendClosingMessage(e.getStatusCode(), stackTrace);
159 throw new IOException(e);
160 }
161 }
162
163 private CloseReason runListenLoopImpl(LoggableInput loggableReader)
164 throws IOException, InterruptedException, IncomingProtocolException {
165 while (true) {
166 loggableReader.markSeparatorForLog();
167 int firstByte;
168 try {
169 firstByte = loggableReader.readByteOrEos();
170 } catch (IOException e) {
171 if (isClosingGracefully()) {
172 return CloseReason.USER_REQUEST;
173 } else {
174 throw e;
175 }
176 }
177 if (firstByte == -1) {
178 if (isClosingGracefully()) {
179 return CloseReason.USER_REQUEST;
180 } else {
181 return CloseReason.REMOTE_SILENTLY_CLOSED;
182 }
183 }
184
185 if ((firstByte & FrameBits.FIN_BIT) == 0) {
186 throw new IncomingProtocolException("Fragments unsupported",
187 StatusCode.CANNOT_ACCEPT, null);
188 }
189 if ((firstByte & FrameBits.RESERVED_MASK) != 0) {
190 throw new IncomingProtocolException("Unexpected reserved bits",
191 StatusCode.PROTOCOL_ERROR, null);
192 }
193
194 int opcode = firstByte & FrameBits.OPCODE_MASK;
195
196 IncomingFrameHandler frameHandler;
197
198 switch (opcode) {
199 case OpCode.CONTINUATION:
200 throw new IncomingProtocolException("Continuation is not supported",
201 StatusCode.CANNOT_ACCEPT, null);
202 case OpCode.TEXT:
203 frameHandler = IncomingFrameHandler.TEXT_MESSAGE;
204 break;
205 case OpCode.BINARY:
206 throw new IncomingProtocolException("Binary is not supported",
207 StatusCode.CANNOT_ACCEPT, null);
208 case OpCode.CLOSE:
209 sendClosingMessage(StatusCode.NORMAL, null);
210 return CloseReason.REMOTE_CLOSE_REQUEST;
211 case OpCode.PING:
212 frameHandler = IncomingFrameHandler.PING;
213 break;
214 case OpCode.PONG:
215 frameHandler = IncomingFrameHandler.PONG;
216 break;
217 default:
218 throw new IncomingProtocolException("Unsupported opcode " + opcode,
219 StatusCode.CANNOT_ACCEPT, null);
220 }
221
222 int secondByte = readByteOfFail(loggableReader);
223
224 boolean hasMask = (secondByte & FrameBits.MASK_BIT) != 0;
225
226 if (hasMask) {
227 throw new IncomingProtocolException("Masked server-to-client message is not supported",
228 StatusCode.PROTOCOL_ERROR, null);
229 }
230
231 int payloadLenByte = secondByte & FrameBits.LENGTH_MASK;
232 int payloadLen;
233 if (payloadLenByte == FrameBits.LENGTH_2_BYTE_CODE) {
234 int lengthTemp = readByteOfFail(loggableReader);
235 lengthTemp <<= 8;
236 lengthTemp += readByteOfFail(loggableReader);
237 payloadLen = lengthTemp;
238 } else if (payloadLenByte == FrameBits.LENGTH_8_BYTE_CODE) {
239 for (int i = 0; i < 4; i++) {
240 int b = readByteOfFail(loggableReader);
241 if (b != 0) {
242 throw new IncomingProtocolException("Payload length is too large",
243 StatusCode.CANNOT_ACCEPT, null);
244 }
245 }
246 int lengthTemp = readByteOfFail(loggableReader);
247 if ((lengthTemp & FrameBits.HIGH_BIT) != 0) {
248 throw new IncomingProtocolException("Payload length is too large",
249 StatusCode.CANNOT_ACCEPT, null);
250 }
251 for (int i = 0; i < 3; i++) {
252 lengthTemp <<= 8;
253 lengthTemp += readByteOfFail(loggableReader);
254 }
255 payloadLen = lengthTemp;
256 } else {
257 payloadLen = payloadLenByte;
258 }
259
260 byte [] bytes = loggableReader.readBytes(payloadLen);
261 frameHandler.process(bytes, this);
262 }
263 }
264
265 private static class IncomingProtocolException extends Exception {
266 private final int statusCode;
267
268 private IncomingProtocolException(String message, int statusCode, Throwable cause) {
269 super(message, cause);
270 this.statusCode = statusCode;
271 }
272
273 int getStatusCode() {
274 return statusCode;
275 }
276 }
277
278 private static abstract class IncomingFrameHandler {
279 abstract void process(byte[] bytes, Hybi17WsConnection hybiWsConnection);
280
281 static final IncomingFrameHandler TEXT_MESSAGE = new IncomingFrameHandler() {
282 @Override
283 void process(byte[] bytes, Hybi17WsConnection hybiWsConnection) {
284 final String text = new String(bytes, UTF_8_CHARSET);
285 hybiWsConnection.getDispatchQueue().add(new MessageDispatcher() {
286 @Override
287 boolean dispatch(Listener userListener) {
288 userListener.textMessageRecieved(text);
289 return false;
290 }
291 });
292 }
293 };
294
295 static final IncomingFrameHandler PING = new IncomingFrameHandler() {
296 @Override
297 void process(final byte[] bytes, Hybi17WsConnection hybiWsConnection) {
298 LoggablePayload payload = new LoggablePayload() {
299 @Override
300 void send(LoggableOutput output, byte[] maskBytes) throws IOException {
301 output.writeBytesToLog(bytes);
302 if (maskBytes != null) {
303 for (int i = 0; i < bytes.length; i++) {
304 bytes[i] = (byte) (bytes[i] ^ maskBytes[i % 4]);
305 }
306 }
307 output.writeBytes(bytes);
308 output.markSeparatorForLog();
309 }
310 @Override int getLength() {
311 return bytes.length;
312 }
313 };
314 try {
315 // Should we do in this thread or relay it to Dispatch thread?
316 hybiWsConnection.sendMessage(OpCode.PONG, payload, false);
317 } catch (IOException e) {
318 LOGGER.log(Level.WARNING, "Failed to send pong", e);
319 }
320 }
321 };
322
323 static final IncomingFrameHandler PONG = new IncomingFrameHandler() {
324 @Override
325 void process(byte[] bytes, Hybi17WsConnection hybiWsConnection) {
326 // Ignore
327 }
328 };
329 }
330
331 /**
332 * Payload that can send and properly log itself. Good logging requires that t he body
333 * is not masked.
334 */
335 private static abstract class LoggablePayload {
336 abstract void send(LoggableOutput output, byte[] maskBytes) throws IOExcepti on;
337 abstract int getLength();
338 }
339
340 private void sendClosingMessage(final int statusCode, final String message) th rows IOException {
341 final byte[] bytes;
342 if (message == null) {
343 bytes = new byte[0];
344 } else {
345 bytes = message.getBytes(UTF_8_CHARSET);
346 }
347
348 LoggablePayload payload = new LoggablePayload() {
349 @Override
350 void send(LoggableOutput output, byte[] maskBytes) throws IOException {
351 byte codeByte1 = (byte) ((statusCode >> 8) & 0xFF);
352 byte codeByte2 = (byte) (statusCode & 0xFF);
353
354 byte codeByteMasked1 = codeByte1;
355 byte codeByteMasked2 = codeByte2;
356 if (maskBytes != null) {
357 codeByteMasked1 ^= maskBytes[0];
358 codeByteMasked2 ^= maskBytes[1];
359
360 for (int i = 0; i < bytes.length; i++) {
361 bytes[i] = (byte) (bytes[i] ^ maskBytes[(i + STATUS_CODE_LENTGH) % 4 ]);
362 }
363 }
364 output.writeByteNoLogging(codeByteMasked1);
365 output.writeByteNoLogging(codeByteMasked2);
366
367 output.writeByteToLog(codeByte1);
368 output.writeByteToLog(codeByte2);
369
370 output.writeBytesNoLogging(bytes);
371 output.writeToLog(message, "utf-8 demasked");
372 }
373
374 @Override int getLength() {
375 return STATUS_CODE_LENTGH + bytes.length;
376 }
377 };
378
379 sendMessage(OpCode.CLOSE, payload, true);
380 }
381
382 private void sendMessage(int opCode, LoggablePayload loggablePayload, boolean isClosingMessage)
383 throws IOException {
384 int length = loggablePayload.getLength();
385 LoggableOutput output = getSocketWrapper().getLoggableOutput();
386
387 byte[] maskBytes = maskStrategy.generate();
388
389 synchronized (this) {
390 if (isOutputClosed()) {
391 throw new IOException("WebSocket is already closed for output");
392 }
393
394 if (isClosingMessage) {
395 // Close it before actually sending, because we can fail on it.
396 setOutputClosed(true);
397 }
398
399 byte firstByte = (byte) (FrameBits.FIN_BIT | OpCode.TEXT);
400
401 output.writeByte(firstByte);
402
403 int maskFlag = maskBytes == null ? 0 : FrameBits.MASK_BIT;
404
405 if (length <= 125) {
406 output.writeByte((byte) (length | maskFlag));
407 } else if (length <= FrameBits.MAX_TWO_BYTE_INT) {
408 output.writeByte((byte) (FrameBits.LENGTH_2_BYTE_CODE | maskFlag));
409 output.writeByte((byte) ((length >> 8) & 0xFF));
410 output.writeByte((byte) (length & 0xFF));
411 } else {
412 output.writeByte((byte) (FrameBits.LENGTH_8_BYTE_CODE | maskFlag));
413 output.writeByte((byte) 0);
414 output.writeByte((byte) 0);
415 output.writeByte((byte) 0);
416 output.writeByte((byte) 0);
417 output.writeByte((byte) (length >>> 24));
418 output.writeByte((byte) ((length >> 16) & 0xFF));
419 output.writeByte((byte) ((length >> 8) & 0xFF));
420 output.writeByte((byte) (length & 0xFF));
421 }
422
423 if (maskBytes != null) {
424 output.writeBytes(maskBytes);
425 }
426 loggablePayload.send(output, maskBytes);
427 }
428
429 output.markSeparatorForLog();
430 }
431
432 private static void performHandshakeOrFail(ManualLoggingSocketWrapper socket,
433 InetSocketAddress endpoint, String resourceId) throws IOException {
434 Hybi17Handshake.Result result =
435 Hybi17Handshake.performHandshake(socket, endpoint, resourceId, RANDOM);
436 result.accept(HANDSHAKE_RESULT_VISITOR).get();
437 }
438
439 private static final Hybi17Handshake.Result.Visitor<DataOrException<Void>>
440 HANDSHAKE_RESULT_VISITOR =
441 new Hybi17Handshake.Result.Visitor<DataOrException<Void>>() {
442 @Override
443 public DataOrException<Void> visitConnected() {
444 return new DataOrException<Void>() {
445 @Override Void get() throws IOException {
446 return null;
447 }
448 };
449 }
450
451 @Override
452 public DataOrException<Void> visitUnknownError(final Exception exception ) {
453 return new DataOrException<Void>() {
454 @Override Void get() throws IOException {
455 throw new IOException("Failed to establish WebSocket connection", exception);
456 }
457 };
458 }
459
460 @Override
461 public DataOrException<Void> visitErrorMessage(final int code,
462 final String errorName, final String text) {
463 return new DataOrException<Void>() {
464 @Override Void get() throws IOException {
465 throw new IOException("Failed to establish WebSocket connection: " + code + " " +
466 errorName + " | " + text);
467 }
468 };
469 }
470 };
471
472 /**
473 * This class is used solely to put IOException through Visitor.
474 */
475 private static abstract class DataOrException<T> {
476 abstract T get() throws IOException;
477 }
478
479 private static int readByteOfFail(LoggableInput loggableReader) throws IOExcep tion {
480 int b = loggableReader.readByteOrEos();
481 if (b == -1) {
482 throw new IOException("Unexpected EOS");
483 }
484 return b;
485 }
486
487 private interface FrameBits {
488 // First byte bits.
489 int FIN_BIT = 1 << 7;
490 int MASK_BIT = 1 << 7;
491
492 // Second byte bits.
493 int OPCODE_LENGTH = 4;
494 int OPCODE_MASK = (1 << OPCODE_LENGTH) - 1;
495 int RESERVED_MASK = ((1 << 3) - 1) << OPCODE_LENGTH ;
496
497 int LENGTH_MASK = (1 << 7) - 1;
498 int LENGTH_2_BYTE_CODE = 126;
499 int LENGTH_8_BYTE_CODE = 127;
500
501 // Length bytes.
502 int HIGH_BIT = 1 << 7;
503 int MAX_TWO_BYTE_INT = 1 << 16 - 1;
504 }
505
506 private interface OpCode {
507 int CONTINUATION = 0x0;
508 int TEXT = 0x1;
509 int BINARY = 0x2;
510 int CLOSE = 0x8;
511 int PING = 0x9;
512 int PONG = 0xA;
513 }
514
515 private interface StatusCode {
516 int NORMAL = 1000;
517 int PROTOCOL_ERROR = 1002;
518 int CANNOT_ACCEPT = 1003;
519 }
520
521 private static final int STATUS_CODE_LENTGH = 2;
522 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698