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.

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

mercurial