1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/thirdparty/com/codebutler/android_websockets/HybiParser.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,416 @@ 1.4 +// 1.5 +// HybiParser.java: draft-ietf-hybi-thewebsocketprotocol-13 parser 1.6 +// 1.7 +// Based on code from the faye project. 1.8 +// https://github.com/faye/faye-websocket-node 1.9 +// Copyright (c) 2009-2012 James Coglan 1.10 +// 1.11 +// Ported from Javascript to Java by Eric Butler <eric@codebutler.com> 1.12 +// 1.13 +// (The MIT License) 1.14 +// 1.15 +// Permission is hereby granted, free of charge, to any person obtaining 1.16 +// a copy of this software and associated documentation files (the 1.17 +// "Software"), to deal in the Software without restriction, including 1.18 +// without limitation the rights to use, copy, modify, merge, publish, 1.19 +// distribute, sublicense, and/or sell copies of the Software, and to 1.20 +// permit persons to whom the Software is furnished to do so, subject to 1.21 +// the following conditions: 1.22 +// 1.23 +// The above copyright notice and this permission notice shall be 1.24 +// included in all copies or substantial portions of the Software. 1.25 +// 1.26 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 1.27 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 1.28 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 1.29 +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 1.30 +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 1.31 +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1.32 +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1.33 + 1.34 +package com.codebutler.android_websockets; 1.35 + 1.36 +import android.util.Log; 1.37 + 1.38 +import java.io.*; 1.39 +import java.util.Arrays; 1.40 +import java.util.List; 1.41 + 1.42 +public class HybiParser { 1.43 + private static final String TAG = "HybiParser"; 1.44 + 1.45 + private WebSocketClient mClient; 1.46 + 1.47 + private boolean mMasking = true; 1.48 + 1.49 + private int mStage; 1.50 + 1.51 + private boolean mFinal; 1.52 + private boolean mMasked; 1.53 + private int mOpcode; 1.54 + private int mLengthSize; 1.55 + private int mLength; 1.56 + private int mMode; 1.57 + 1.58 + private byte[] mMask = new byte[0]; 1.59 + private byte[] mPayload = new byte[0]; 1.60 + 1.61 + private boolean mClosed = false; 1.62 + 1.63 + private ByteArrayOutputStream mBuffer = new ByteArrayOutputStream(); 1.64 + 1.65 + private static final int BYTE = 255; 1.66 + private static final int FIN = 128; 1.67 + private static final int MASK = 128; 1.68 + private static final int RSV1 = 64; 1.69 + private static final int RSV2 = 32; 1.70 + private static final int RSV3 = 16; 1.71 + private static final int OPCODE = 15; 1.72 + private static final int LENGTH = 127; 1.73 + 1.74 + private static final int MODE_TEXT = 1; 1.75 + private static final int MODE_BINARY = 2; 1.76 + 1.77 + private static final int OP_CONTINUATION = 0; 1.78 + private static final int OP_TEXT = 1; 1.79 + private static final int OP_BINARY = 2; 1.80 + private static final int OP_CLOSE = 8; 1.81 + private static final int OP_PING = 9; 1.82 + private static final int OP_PONG = 10; 1.83 + 1.84 + private static final List<Integer> OPCODES = Arrays.asList( 1.85 + OP_CONTINUATION, 1.86 + OP_TEXT, 1.87 + OP_BINARY, 1.88 + OP_CLOSE, 1.89 + OP_PING, 1.90 + OP_PONG 1.91 + ); 1.92 + 1.93 + private static final List<Integer> FRAGMENTED_OPCODES = Arrays.asList( 1.94 + OP_CONTINUATION, OP_TEXT, OP_BINARY 1.95 + ); 1.96 + 1.97 + public HybiParser(WebSocketClient client) { 1.98 + mClient = client; 1.99 + } 1.100 + 1.101 + private static byte[] mask(byte[] payload, byte[] mask, int offset) { 1.102 + if (mask.length == 0) return payload; 1.103 + 1.104 + for (int i = 0; i < payload.length - offset; i++) { 1.105 + payload[offset + i] = (byte) (payload[offset + i] ^ mask[i % 4]); 1.106 + } 1.107 + return payload; 1.108 + } 1.109 + 1.110 + public void start(HappyDataInputStream stream) throws IOException { 1.111 + while (true) { 1.112 + if (stream.available() == -1) break; 1.113 + switch (mStage) { 1.114 + case 0: 1.115 + parseOpcode(stream.readByte()); 1.116 + break; 1.117 + case 1: 1.118 + parseLength(stream.readByte()); 1.119 + break; 1.120 + case 2: 1.121 + parseExtendedLength(stream.readBytes(mLengthSize)); 1.122 + break; 1.123 + case 3: 1.124 + mMask = stream.readBytes(4); 1.125 + mStage = 4; 1.126 + break; 1.127 + case 4: 1.128 + mPayload = stream.readBytes(mLength); 1.129 + emitFrame(); 1.130 + mStage = 0; 1.131 + break; 1.132 + } 1.133 + } 1.134 + mClient.getListener().onDisconnect(0, "EOF"); 1.135 + } 1.136 + 1.137 + private void parseOpcode(byte data) throws ProtocolError { 1.138 + boolean rsv1 = (data & RSV1) == RSV1; 1.139 + boolean rsv2 = (data & RSV2) == RSV2; 1.140 + boolean rsv3 = (data & RSV3) == RSV3; 1.141 + 1.142 + if (rsv1 || rsv2 || rsv3) { 1.143 + throw new ProtocolError("RSV not zero"); 1.144 + } 1.145 + 1.146 + mFinal = (data & FIN) == FIN; 1.147 + mOpcode = (data & OPCODE); 1.148 + mMask = new byte[0]; 1.149 + mPayload = new byte[0]; 1.150 + 1.151 + if (!OPCODES.contains(mOpcode)) { 1.152 + throw new ProtocolError("Bad opcode"); 1.153 + } 1.154 + 1.155 + if (!FRAGMENTED_OPCODES.contains(mOpcode) && !mFinal) { 1.156 + throw new ProtocolError("Expected non-final packet"); 1.157 + } 1.158 + 1.159 + mStage = 1; 1.160 + } 1.161 + 1.162 + private void parseLength(byte data) { 1.163 + mMasked = (data & MASK) == MASK; 1.164 + mLength = (data & LENGTH); 1.165 + 1.166 + if (mLength >= 0 && mLength <= 125) { 1.167 + mStage = mMasked ? 3 : 4; 1.168 + } else { 1.169 + mLengthSize = (mLength == 126) ? 2 : 8; 1.170 + mStage = 2; 1.171 + } 1.172 + } 1.173 + 1.174 + private void parseExtendedLength(byte[] buffer) throws ProtocolError { 1.175 + mLength = getInteger(buffer); 1.176 + mStage = mMasked ? 3 : 4; 1.177 + } 1.178 + 1.179 + public byte[] frame(String data) { 1.180 + return frame(data, OP_TEXT, -1); 1.181 + } 1.182 + 1.183 + public byte[] frame(byte[] data) { 1.184 + return frame(data, OP_BINARY, -1); 1.185 + } 1.186 + 1.187 + private byte[] frame(byte[] data, int opcode, int errorCode) { 1.188 + return frame((Object)data, opcode, errorCode); 1.189 + } 1.190 + 1.191 + private byte[] frame(String data, int opcode, int errorCode) { 1.192 + return frame((Object)data, opcode, errorCode); 1.193 + } 1.194 + 1.195 + private byte[] frame(Object data, int opcode, int errorCode) { 1.196 + if (mClosed) return null; 1.197 + 1.198 + Log.d(TAG, "Creating frame for: " + data + " op: " + opcode + " err: " + errorCode); 1.199 + 1.200 + byte[] buffer = (data instanceof String) ? decode((String) data) : (byte[]) data; 1.201 + int insert = (errorCode > 0) ? 2 : 0; 1.202 + int length = buffer.length + insert; 1.203 + int header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10); 1.204 + int offset = header + (mMasking ? 4 : 0); 1.205 + int masked = mMasking ? MASK : 0; 1.206 + byte[] frame = new byte[length + offset]; 1.207 + 1.208 + frame[0] = (byte) ((byte)FIN | (byte)opcode); 1.209 + 1.210 + if (length <= 125) { 1.211 + frame[1] = (byte) (masked | length); 1.212 + } else if (length <= 65535) { 1.213 + frame[1] = (byte) (masked | 126); 1.214 + frame[2] = (byte) Math.floor(length / 256); 1.215 + frame[3] = (byte) (length & BYTE); 1.216 + } else { 1.217 + frame[1] = (byte) (masked | 127); 1.218 + frame[2] = (byte) (((int) Math.floor(length / Math.pow(2, 56))) & BYTE); 1.219 + frame[3] = (byte) (((int) Math.floor(length / Math.pow(2, 48))) & BYTE); 1.220 + frame[4] = (byte) (((int) Math.floor(length / Math.pow(2, 40))) & BYTE); 1.221 + frame[5] = (byte) (((int) Math.floor(length / Math.pow(2, 32))) & BYTE); 1.222 + frame[6] = (byte) (((int) Math.floor(length / Math.pow(2, 24))) & BYTE); 1.223 + frame[7] = (byte) (((int) Math.floor(length / Math.pow(2, 16))) & BYTE); 1.224 + frame[8] = (byte) (((int) Math.floor(length / Math.pow(2, 8))) & BYTE); 1.225 + frame[9] = (byte) (length & BYTE); 1.226 + } 1.227 + 1.228 + if (errorCode > 0) { 1.229 + frame[offset] = (byte) (((int) Math.floor(errorCode / 256)) & BYTE); 1.230 + frame[offset+1] = (byte) (errorCode & BYTE); 1.231 + } 1.232 + System.arraycopy(buffer, 0, frame, offset + insert, buffer.length); 1.233 + 1.234 + if (mMasking) { 1.235 + byte[] mask = { 1.236 + (byte) Math.floor(Math.random() * 256), (byte) Math.floor(Math.random() * 256), 1.237 + (byte) Math.floor(Math.random() * 256), (byte) Math.floor(Math.random() * 256) 1.238 + }; 1.239 + System.arraycopy(mask, 0, frame, header, mask.length); 1.240 + mask(frame, mask, offset); 1.241 + } 1.242 + 1.243 + return frame; 1.244 + } 1.245 + 1.246 + public void ping(String message) { 1.247 + mClient.send(frame(message, OP_PING, -1)); 1.248 + } 1.249 + 1.250 + public void close(int code, String reason) { 1.251 + if (mClosed) return; 1.252 + mClient.send(frame(reason, OP_CLOSE, code)); 1.253 + mClosed = true; 1.254 + } 1.255 + 1.256 + private void emitFrame() throws IOException { 1.257 + byte[] payload = mask(mPayload, mMask, 0); 1.258 + int opcode = mOpcode; 1.259 + 1.260 + if (opcode == OP_CONTINUATION) { 1.261 + if (mMode == 0) { 1.262 + throw new ProtocolError("Mode was not set."); 1.263 + } 1.264 + mBuffer.write(payload); 1.265 + if (mFinal) { 1.266 + byte[] message = mBuffer.toByteArray(); 1.267 + if (mMode == MODE_TEXT) { 1.268 + mClient.getListener().onMessage(encode(message)); 1.269 + } else { 1.270 + mClient.getListener().onMessage(message); 1.271 + } 1.272 + reset(); 1.273 + } 1.274 + 1.275 + } else if (opcode == OP_TEXT) { 1.276 + if (mFinal) { 1.277 + String messageText = encode(payload); 1.278 + mClient.getListener().onMessage(messageText); 1.279 + } else { 1.280 + mMode = MODE_TEXT; 1.281 + mBuffer.write(payload); 1.282 + } 1.283 + 1.284 + } else if (opcode == OP_BINARY) { 1.285 + if (mFinal) { 1.286 + mClient.getListener().onMessage(payload); 1.287 + } else { 1.288 + mMode = MODE_BINARY; 1.289 + mBuffer.write(payload); 1.290 + } 1.291 + 1.292 + } else if (opcode == OP_CLOSE) { 1.293 + int code = (payload.length >= 2) ? 256 * payload[0] + payload[1] : 0; 1.294 + String reason = (payload.length > 2) ? encode(slice(payload, 2)) : null; 1.295 + Log.d(TAG, "Got close op! " + code + " " + reason); 1.296 + mClient.getListener().onDisconnect(code, reason); 1.297 + 1.298 + } else if (opcode == OP_PING) { 1.299 + if (payload.length > 125) { throw new ProtocolError("Ping payload too large"); } 1.300 + Log.d(TAG, "Sending pong!!"); 1.301 + mClient.sendFrame(frame(payload, OP_PONG, -1)); 1.302 + 1.303 + } else if (opcode == OP_PONG) { 1.304 + String message = encode(payload); 1.305 + // FIXME: Fire callback... 1.306 + Log.d(TAG, "Got pong! " + message); 1.307 + } 1.308 + } 1.309 + 1.310 + private void reset() { 1.311 + mMode = 0; 1.312 + mBuffer.reset(); 1.313 + } 1.314 + 1.315 + private String encode(byte[] buffer) { 1.316 + try { 1.317 + return new String(buffer, "UTF-8"); 1.318 + } catch (UnsupportedEncodingException e) { 1.319 + throw new RuntimeException(e); 1.320 + } 1.321 + } 1.322 + 1.323 + private byte[] decode(String string) { 1.324 + try { 1.325 + return (string).getBytes("UTF-8"); 1.326 + } catch (UnsupportedEncodingException e) { 1.327 + throw new RuntimeException(e); 1.328 + } 1.329 + } 1.330 + 1.331 + private int getInteger(byte[] bytes) throws ProtocolError { 1.332 + long i = byteArrayToLong(bytes, 0, bytes.length); 1.333 + if (i < 0 || i > Integer.MAX_VALUE) { 1.334 + throw new ProtocolError("Bad integer: " + i); 1.335 + } 1.336 + return (int) i; 1.337 + } 1.338 + 1.339 + /** 1.340 + * Copied from AOSP Arrays.java. 1.341 + */ 1.342 + /** 1.343 + * Copies elements from {@code original} into a new array, from indexes start (inclusive) to 1.344 + * end (exclusive). The original order of elements is preserved. 1.345 + * If {@code end} is greater than {@code original.length}, the result is padded 1.346 + * with the value {@code (byte) 0}. 1.347 + * 1.348 + * @param original the original array 1.349 + * @param start the start index, inclusive 1.350 + * @param end the end index, exclusive 1.351 + * @return the new array 1.352 + * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length} 1.353 + * @throws IllegalArgumentException if {@code start > end} 1.354 + * @throws NullPointerException if {@code original == null} 1.355 + * @since 1.6 1.356 + */ 1.357 + private static byte[] copyOfRange(byte[] original, int start, int end) { 1.358 + if (start > end) { 1.359 + throw new IllegalArgumentException(); 1.360 + } 1.361 + int originalLength = original.length; 1.362 + if (start < 0 || start > originalLength) { 1.363 + throw new ArrayIndexOutOfBoundsException(); 1.364 + } 1.365 + int resultLength = end - start; 1.366 + int copyLength = Math.min(resultLength, originalLength - start); 1.367 + byte[] result = new byte[resultLength]; 1.368 + System.arraycopy(original, start, result, 0, copyLength); 1.369 + return result; 1.370 + } 1.371 + 1.372 + private byte[] slice(byte[] array, int start) { 1.373 + return copyOfRange(array, start, array.length); 1.374 + } 1.375 + 1.376 + public static class ProtocolError extends IOException { 1.377 + public ProtocolError(String detailMessage) { 1.378 + super(detailMessage); 1.379 + } 1.380 + } 1.381 + 1.382 + private static long byteArrayToLong(byte[] b, int offset, int length) { 1.383 + if (b.length < length) 1.384 + throw new IllegalArgumentException("length must be less than or equal to b.length"); 1.385 + 1.386 + long value = 0; 1.387 + for (int i = 0; i < length; i++) { 1.388 + int shift = (length - 1 - i) * 8; 1.389 + value += (b[i + offset] & 0x000000FF) << shift; 1.390 + } 1.391 + return value; 1.392 + } 1.393 + 1.394 + public static class HappyDataInputStream extends DataInputStream { 1.395 + public HappyDataInputStream(InputStream in) { 1.396 + super(in); 1.397 + } 1.398 + 1.399 + public byte[] readBytes(int length) throws IOException { 1.400 + byte[] buffer = new byte[length]; 1.401 + 1.402 + int total = 0; 1.403 + 1.404 + while (total < length) { 1.405 + int count = read(buffer, total, length - total); 1.406 + if (count == -1) { 1.407 + break; 1.408 + } 1.409 + total += count; 1.410 + } 1.411 + 1.412 + if (total != length) { 1.413 + throw new IOException(String.format("Read wrong number of bytes. Got: %s, Expected: %s.", total, length)); 1.414 + } 1.415 + 1.416 + return buffer; 1.417 + } 1.418 + } 1.419 +}