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

changeset 0
6474c204b198
     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 +}

mercurial