mobile/android/thirdparty/com/codebutler/android_websockets/HybiParser.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 //
michael@0 2 // HybiParser.java: draft-ietf-hybi-thewebsocketprotocol-13 parser
michael@0 3 //
michael@0 4 // Based on code from the faye project.
michael@0 5 // https://github.com/faye/faye-websocket-node
michael@0 6 // Copyright (c) 2009-2012 James Coglan
michael@0 7 //
michael@0 8 // Ported from Javascript to Java by Eric Butler <eric@codebutler.com>
michael@0 9 //
michael@0 10 // (The MIT License)
michael@0 11 //
michael@0 12 // Permission is hereby granted, free of charge, to any person obtaining
michael@0 13 // a copy of this software and associated documentation files (the
michael@0 14 // "Software"), to deal in the Software without restriction, including
michael@0 15 // without limitation the rights to use, copy, modify, merge, publish,
michael@0 16 // distribute, sublicense, and/or sell copies of the Software, and to
michael@0 17 // permit persons to whom the Software is furnished to do so, subject to
michael@0 18 // the following conditions:
michael@0 19 //
michael@0 20 // The above copyright notice and this permission notice shall be
michael@0 21 // included in all copies or substantial portions of the Software.
michael@0 22 //
michael@0 23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
michael@0 24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
michael@0 25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
michael@0 26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
michael@0 27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
michael@0 28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
michael@0 29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
michael@0 30
michael@0 31 package com.codebutler.android_websockets;
michael@0 32
michael@0 33 import android.util.Log;
michael@0 34
michael@0 35 import java.io.*;
michael@0 36 import java.util.Arrays;
michael@0 37 import java.util.List;
michael@0 38
michael@0 39 public class HybiParser {
michael@0 40 private static final String TAG = "HybiParser";
michael@0 41
michael@0 42 private WebSocketClient mClient;
michael@0 43
michael@0 44 private boolean mMasking = true;
michael@0 45
michael@0 46 private int mStage;
michael@0 47
michael@0 48 private boolean mFinal;
michael@0 49 private boolean mMasked;
michael@0 50 private int mOpcode;
michael@0 51 private int mLengthSize;
michael@0 52 private int mLength;
michael@0 53 private int mMode;
michael@0 54
michael@0 55 private byte[] mMask = new byte[0];
michael@0 56 private byte[] mPayload = new byte[0];
michael@0 57
michael@0 58 private boolean mClosed = false;
michael@0 59
michael@0 60 private ByteArrayOutputStream mBuffer = new ByteArrayOutputStream();
michael@0 61
michael@0 62 private static final int BYTE = 255;
michael@0 63 private static final int FIN = 128;
michael@0 64 private static final int MASK = 128;
michael@0 65 private static final int RSV1 = 64;
michael@0 66 private static final int RSV2 = 32;
michael@0 67 private static final int RSV3 = 16;
michael@0 68 private static final int OPCODE = 15;
michael@0 69 private static final int LENGTH = 127;
michael@0 70
michael@0 71 private static final int MODE_TEXT = 1;
michael@0 72 private static final int MODE_BINARY = 2;
michael@0 73
michael@0 74 private static final int OP_CONTINUATION = 0;
michael@0 75 private static final int OP_TEXT = 1;
michael@0 76 private static final int OP_BINARY = 2;
michael@0 77 private static final int OP_CLOSE = 8;
michael@0 78 private static final int OP_PING = 9;
michael@0 79 private static final int OP_PONG = 10;
michael@0 80
michael@0 81 private static final List<Integer> OPCODES = Arrays.asList(
michael@0 82 OP_CONTINUATION,
michael@0 83 OP_TEXT,
michael@0 84 OP_BINARY,
michael@0 85 OP_CLOSE,
michael@0 86 OP_PING,
michael@0 87 OP_PONG
michael@0 88 );
michael@0 89
michael@0 90 private static final List<Integer> FRAGMENTED_OPCODES = Arrays.asList(
michael@0 91 OP_CONTINUATION, OP_TEXT, OP_BINARY
michael@0 92 );
michael@0 93
michael@0 94 public HybiParser(WebSocketClient client) {
michael@0 95 mClient = client;
michael@0 96 }
michael@0 97
michael@0 98 private static byte[] mask(byte[] payload, byte[] mask, int offset) {
michael@0 99 if (mask.length == 0) return payload;
michael@0 100
michael@0 101 for (int i = 0; i < payload.length - offset; i++) {
michael@0 102 payload[offset + i] = (byte) (payload[offset + i] ^ mask[i % 4]);
michael@0 103 }
michael@0 104 return payload;
michael@0 105 }
michael@0 106
michael@0 107 public void start(HappyDataInputStream stream) throws IOException {
michael@0 108 while (true) {
michael@0 109 if (stream.available() == -1) break;
michael@0 110 switch (mStage) {
michael@0 111 case 0:
michael@0 112 parseOpcode(stream.readByte());
michael@0 113 break;
michael@0 114 case 1:
michael@0 115 parseLength(stream.readByte());
michael@0 116 break;
michael@0 117 case 2:
michael@0 118 parseExtendedLength(stream.readBytes(mLengthSize));
michael@0 119 break;
michael@0 120 case 3:
michael@0 121 mMask = stream.readBytes(4);
michael@0 122 mStage = 4;
michael@0 123 break;
michael@0 124 case 4:
michael@0 125 mPayload = stream.readBytes(mLength);
michael@0 126 emitFrame();
michael@0 127 mStage = 0;
michael@0 128 break;
michael@0 129 }
michael@0 130 }
michael@0 131 mClient.getListener().onDisconnect(0, "EOF");
michael@0 132 }
michael@0 133
michael@0 134 private void parseOpcode(byte data) throws ProtocolError {
michael@0 135 boolean rsv1 = (data & RSV1) == RSV1;
michael@0 136 boolean rsv2 = (data & RSV2) == RSV2;
michael@0 137 boolean rsv3 = (data & RSV3) == RSV3;
michael@0 138
michael@0 139 if (rsv1 || rsv2 || rsv3) {
michael@0 140 throw new ProtocolError("RSV not zero");
michael@0 141 }
michael@0 142
michael@0 143 mFinal = (data & FIN) == FIN;
michael@0 144 mOpcode = (data & OPCODE);
michael@0 145 mMask = new byte[0];
michael@0 146 mPayload = new byte[0];
michael@0 147
michael@0 148 if (!OPCODES.contains(mOpcode)) {
michael@0 149 throw new ProtocolError("Bad opcode");
michael@0 150 }
michael@0 151
michael@0 152 if (!FRAGMENTED_OPCODES.contains(mOpcode) && !mFinal) {
michael@0 153 throw new ProtocolError("Expected non-final packet");
michael@0 154 }
michael@0 155
michael@0 156 mStage = 1;
michael@0 157 }
michael@0 158
michael@0 159 private void parseLength(byte data) {
michael@0 160 mMasked = (data & MASK) == MASK;
michael@0 161 mLength = (data & LENGTH);
michael@0 162
michael@0 163 if (mLength >= 0 && mLength <= 125) {
michael@0 164 mStage = mMasked ? 3 : 4;
michael@0 165 } else {
michael@0 166 mLengthSize = (mLength == 126) ? 2 : 8;
michael@0 167 mStage = 2;
michael@0 168 }
michael@0 169 }
michael@0 170
michael@0 171 private void parseExtendedLength(byte[] buffer) throws ProtocolError {
michael@0 172 mLength = getInteger(buffer);
michael@0 173 mStage = mMasked ? 3 : 4;
michael@0 174 }
michael@0 175
michael@0 176 public byte[] frame(String data) {
michael@0 177 return frame(data, OP_TEXT, -1);
michael@0 178 }
michael@0 179
michael@0 180 public byte[] frame(byte[] data) {
michael@0 181 return frame(data, OP_BINARY, -1);
michael@0 182 }
michael@0 183
michael@0 184 private byte[] frame(byte[] data, int opcode, int errorCode) {
michael@0 185 return frame((Object)data, opcode, errorCode);
michael@0 186 }
michael@0 187
michael@0 188 private byte[] frame(String data, int opcode, int errorCode) {
michael@0 189 return frame((Object)data, opcode, errorCode);
michael@0 190 }
michael@0 191
michael@0 192 private byte[] frame(Object data, int opcode, int errorCode) {
michael@0 193 if (mClosed) return null;
michael@0 194
michael@0 195 Log.d(TAG, "Creating frame for: " + data + " op: " + opcode + " err: " + errorCode);
michael@0 196
michael@0 197 byte[] buffer = (data instanceof String) ? decode((String) data) : (byte[]) data;
michael@0 198 int insert = (errorCode > 0) ? 2 : 0;
michael@0 199 int length = buffer.length + insert;
michael@0 200 int header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10);
michael@0 201 int offset = header + (mMasking ? 4 : 0);
michael@0 202 int masked = mMasking ? MASK : 0;
michael@0 203 byte[] frame = new byte[length + offset];
michael@0 204
michael@0 205 frame[0] = (byte) ((byte)FIN | (byte)opcode);
michael@0 206
michael@0 207 if (length <= 125) {
michael@0 208 frame[1] = (byte) (masked | length);
michael@0 209 } else if (length <= 65535) {
michael@0 210 frame[1] = (byte) (masked | 126);
michael@0 211 frame[2] = (byte) Math.floor(length / 256);
michael@0 212 frame[3] = (byte) (length & BYTE);
michael@0 213 } else {
michael@0 214 frame[1] = (byte) (masked | 127);
michael@0 215 frame[2] = (byte) (((int) Math.floor(length / Math.pow(2, 56))) & BYTE);
michael@0 216 frame[3] = (byte) (((int) Math.floor(length / Math.pow(2, 48))) & BYTE);
michael@0 217 frame[4] = (byte) (((int) Math.floor(length / Math.pow(2, 40))) & BYTE);
michael@0 218 frame[5] = (byte) (((int) Math.floor(length / Math.pow(2, 32))) & BYTE);
michael@0 219 frame[6] = (byte) (((int) Math.floor(length / Math.pow(2, 24))) & BYTE);
michael@0 220 frame[7] = (byte) (((int) Math.floor(length / Math.pow(2, 16))) & BYTE);
michael@0 221 frame[8] = (byte) (((int) Math.floor(length / Math.pow(2, 8))) & BYTE);
michael@0 222 frame[9] = (byte) (length & BYTE);
michael@0 223 }
michael@0 224
michael@0 225 if (errorCode > 0) {
michael@0 226 frame[offset] = (byte) (((int) Math.floor(errorCode / 256)) & BYTE);
michael@0 227 frame[offset+1] = (byte) (errorCode & BYTE);
michael@0 228 }
michael@0 229 System.arraycopy(buffer, 0, frame, offset + insert, buffer.length);
michael@0 230
michael@0 231 if (mMasking) {
michael@0 232 byte[] mask = {
michael@0 233 (byte) Math.floor(Math.random() * 256), (byte) Math.floor(Math.random() * 256),
michael@0 234 (byte) Math.floor(Math.random() * 256), (byte) Math.floor(Math.random() * 256)
michael@0 235 };
michael@0 236 System.arraycopy(mask, 0, frame, header, mask.length);
michael@0 237 mask(frame, mask, offset);
michael@0 238 }
michael@0 239
michael@0 240 return frame;
michael@0 241 }
michael@0 242
michael@0 243 public void ping(String message) {
michael@0 244 mClient.send(frame(message, OP_PING, -1));
michael@0 245 }
michael@0 246
michael@0 247 public void close(int code, String reason) {
michael@0 248 if (mClosed) return;
michael@0 249 mClient.send(frame(reason, OP_CLOSE, code));
michael@0 250 mClosed = true;
michael@0 251 }
michael@0 252
michael@0 253 private void emitFrame() throws IOException {
michael@0 254 byte[] payload = mask(mPayload, mMask, 0);
michael@0 255 int opcode = mOpcode;
michael@0 256
michael@0 257 if (opcode == OP_CONTINUATION) {
michael@0 258 if (mMode == 0) {
michael@0 259 throw new ProtocolError("Mode was not set.");
michael@0 260 }
michael@0 261 mBuffer.write(payload);
michael@0 262 if (mFinal) {
michael@0 263 byte[] message = mBuffer.toByteArray();
michael@0 264 if (mMode == MODE_TEXT) {
michael@0 265 mClient.getListener().onMessage(encode(message));
michael@0 266 } else {
michael@0 267 mClient.getListener().onMessage(message);
michael@0 268 }
michael@0 269 reset();
michael@0 270 }
michael@0 271
michael@0 272 } else if (opcode == OP_TEXT) {
michael@0 273 if (mFinal) {
michael@0 274 String messageText = encode(payload);
michael@0 275 mClient.getListener().onMessage(messageText);
michael@0 276 } else {
michael@0 277 mMode = MODE_TEXT;
michael@0 278 mBuffer.write(payload);
michael@0 279 }
michael@0 280
michael@0 281 } else if (opcode == OP_BINARY) {
michael@0 282 if (mFinal) {
michael@0 283 mClient.getListener().onMessage(payload);
michael@0 284 } else {
michael@0 285 mMode = MODE_BINARY;
michael@0 286 mBuffer.write(payload);
michael@0 287 }
michael@0 288
michael@0 289 } else if (opcode == OP_CLOSE) {
michael@0 290 int code = (payload.length >= 2) ? 256 * payload[0] + payload[1] : 0;
michael@0 291 String reason = (payload.length > 2) ? encode(slice(payload, 2)) : null;
michael@0 292 Log.d(TAG, "Got close op! " + code + " " + reason);
michael@0 293 mClient.getListener().onDisconnect(code, reason);
michael@0 294
michael@0 295 } else if (opcode == OP_PING) {
michael@0 296 if (payload.length > 125) { throw new ProtocolError("Ping payload too large"); }
michael@0 297 Log.d(TAG, "Sending pong!!");
michael@0 298 mClient.sendFrame(frame(payload, OP_PONG, -1));
michael@0 299
michael@0 300 } else if (opcode == OP_PONG) {
michael@0 301 String message = encode(payload);
michael@0 302 // FIXME: Fire callback...
michael@0 303 Log.d(TAG, "Got pong! " + message);
michael@0 304 }
michael@0 305 }
michael@0 306
michael@0 307 private void reset() {
michael@0 308 mMode = 0;
michael@0 309 mBuffer.reset();
michael@0 310 }
michael@0 311
michael@0 312 private String encode(byte[] buffer) {
michael@0 313 try {
michael@0 314 return new String(buffer, "UTF-8");
michael@0 315 } catch (UnsupportedEncodingException e) {
michael@0 316 throw new RuntimeException(e);
michael@0 317 }
michael@0 318 }
michael@0 319
michael@0 320 private byte[] decode(String string) {
michael@0 321 try {
michael@0 322 return (string).getBytes("UTF-8");
michael@0 323 } catch (UnsupportedEncodingException e) {
michael@0 324 throw new RuntimeException(e);
michael@0 325 }
michael@0 326 }
michael@0 327
michael@0 328 private int getInteger(byte[] bytes) throws ProtocolError {
michael@0 329 long i = byteArrayToLong(bytes, 0, bytes.length);
michael@0 330 if (i < 0 || i > Integer.MAX_VALUE) {
michael@0 331 throw new ProtocolError("Bad integer: " + i);
michael@0 332 }
michael@0 333 return (int) i;
michael@0 334 }
michael@0 335
michael@0 336 /**
michael@0 337 * Copied from AOSP Arrays.java.
michael@0 338 */
michael@0 339 /**
michael@0 340 * Copies elements from {@code original} into a new array, from indexes start (inclusive) to
michael@0 341 * end (exclusive). The original order of elements is preserved.
michael@0 342 * If {@code end} is greater than {@code original.length}, the result is padded
michael@0 343 * with the value {@code (byte) 0}.
michael@0 344 *
michael@0 345 * @param original the original array
michael@0 346 * @param start the start index, inclusive
michael@0 347 * @param end the end index, exclusive
michael@0 348 * @return the new array
michael@0 349 * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length}
michael@0 350 * @throws IllegalArgumentException if {@code start > end}
michael@0 351 * @throws NullPointerException if {@code original == null}
michael@0 352 * @since 1.6
michael@0 353 */
michael@0 354 private static byte[] copyOfRange(byte[] original, int start, int end) {
michael@0 355 if (start > end) {
michael@0 356 throw new IllegalArgumentException();
michael@0 357 }
michael@0 358 int originalLength = original.length;
michael@0 359 if (start < 0 || start > originalLength) {
michael@0 360 throw new ArrayIndexOutOfBoundsException();
michael@0 361 }
michael@0 362 int resultLength = end - start;
michael@0 363 int copyLength = Math.min(resultLength, originalLength - start);
michael@0 364 byte[] result = new byte[resultLength];
michael@0 365 System.arraycopy(original, start, result, 0, copyLength);
michael@0 366 return result;
michael@0 367 }
michael@0 368
michael@0 369 private byte[] slice(byte[] array, int start) {
michael@0 370 return copyOfRange(array, start, array.length);
michael@0 371 }
michael@0 372
michael@0 373 public static class ProtocolError extends IOException {
michael@0 374 public ProtocolError(String detailMessage) {
michael@0 375 super(detailMessage);
michael@0 376 }
michael@0 377 }
michael@0 378
michael@0 379 private static long byteArrayToLong(byte[] b, int offset, int length) {
michael@0 380 if (b.length < length)
michael@0 381 throw new IllegalArgumentException("length must be less than or equal to b.length");
michael@0 382
michael@0 383 long value = 0;
michael@0 384 for (int i = 0; i < length; i++) {
michael@0 385 int shift = (length - 1 - i) * 8;
michael@0 386 value += (b[i + offset] & 0x000000FF) << shift;
michael@0 387 }
michael@0 388 return value;
michael@0 389 }
michael@0 390
michael@0 391 public static class HappyDataInputStream extends DataInputStream {
michael@0 392 public HappyDataInputStream(InputStream in) {
michael@0 393 super(in);
michael@0 394 }
michael@0 395
michael@0 396 public byte[] readBytes(int length) throws IOException {
michael@0 397 byte[] buffer = new byte[length];
michael@0 398
michael@0 399 int total = 0;
michael@0 400
michael@0 401 while (total < length) {
michael@0 402 int count = read(buffer, total, length - total);
michael@0 403 if (count == -1) {
michael@0 404 break;
michael@0 405 }
michael@0 406 total += count;
michael@0 407 }
michael@0 408
michael@0 409 if (total != length) {
michael@0 410 throw new IOException(String.format("Read wrong number of bytes. Got: %s, Expected: %s.", total, length));
michael@0 411 }
michael@0 412
michael@0 413 return buffer;
michael@0 414 }
michael@0 415 }
michael@0 416 }

mercurial