Android Open Source - android-websocket-client Client






From Project

Back to project page android-websocket-client.

License

The source code is released under:

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DI...

If you think the Android project android-websocket-client listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.elaxys.android.websocket;
//from  ww w  .j  a  va 2  s. com
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.Header;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.message.BasicLineParser;

import android.net.Uri;
import android.os.Handler;
import android.util.Base64;
import android.util.Log;


/**
 * Web Socket Client (RFC6455)
 */
public class Client {
    
    /**
     * Configuration parameters
     */
    public static class Config {
        /** Server URI in the format "ws[s]://host[:port][/path]" */
        public String  mURI;
        /** Size of the transmission queue in number of messages */
        public int     mQueueSize;
        /** Connection timeout in milliseconds */
        public int     mConnTimeout;
        /** Retry interval in milliseconds */
        public int     mRetryInterval;
        /** Maximum size of received packet PAYLOAD (0=unlimited) */
        public int     mMaxRxSize;
        /** True to respond to PINGS, false sends the PING frame to the application */
        public boolean mRespondPing;
        /** Checks server certificate on SSL connection */
        public boolean mServerCert;
        /** Log tag used for debugging. If null no log is generated */
        public String mLogTag;
        /** Default constructor */
        public Config() {
            mURI            = "ws://www.example.com/path";
            mQueueSize      = 10;
            mConnTimeout    = 5000;
            mRetryInterval  = 1000;
            mMaxRxSize      = 128*1024;
            mRespondPing    = true;
            mServerCert     = false;
            mLogTag         = "WSCLIENT";
        }
        /** Copy constructor */
        public Config(Config config) {
            mURI            = config.mURI;
            mQueueSize      = config.mQueueSize;
            mConnTimeout    = config.mConnTimeout;
            mRetryInterval  = config.mRetryInterval;
            mMaxRxSize      = config.mMaxRxSize;
            mRespondPing    = config.mRespondPing;
            mServerCert     = config.mServerCert;
            mLogTag         = config.mLogTag;
        }
    }
    
    /**
     * Interface to listen to generated events
     */
    public interface Listener {
        /**
         * Called when reception thread is started.
         */
        public void onClientStart();
        /**
         * Called when reception thread is about to connect with the server.
         */
        public void onClientConnect();
        /**
         * Called when connection and handshake with the server
         * completed successfully.
         */
        public void onClientConnected();
        /**
         * Called when any error occurs, including the closing of
         * the connection.
         * @param code Error type
         * @param msg Error message
         */
        public void onClientError(int code, String msg);
        /**
         * Called when a BINARY frame is received from the server
         * @param type Frame type. Possible values are F_BINARY* and F_PING
         * @param data Frame PAYLOAD byte array
         */
        public void onClientRecv(int type, byte[] data);
        /**
         * Called when a TEXT frame is received from the server
         * @param type Frame type. Possible values are F_TEXT*
         * @param data Frame PAYLOAD string
         */
        public void onClientRecv(int type, String data);
        /**
         * Called when a frame removed is from the transmission queue
         * and sent to server.
         * @param fid  Frame id.
         */
        public void onClientSent(int fid);
        /**
         * Called when the client is stopped
         */
        public void onClientStop();
    }
    
    /**
     * Statistics Data.
     * Contains communication statistics collected during the
     * communication.
     */
    public static class Stats {
        /**
         * Total number of received frames, including control frames.
         */
        public int mRxFrames;
        /**
         * Total number of bytes received.
         */
        public int mRxBytes;
        /**
         * Total number of PAYLOAD bytes received.
         */
        public int mRxData;
        /**
         * Total number of transmitted frames, including control frames.
         */
        public int mTxFrames;
        /**
         * Total number of transmitted bytes.
         */
        public int mTxBytes;
        /**
         * Total number of transmitted PAYLOAD bytes.
         */
        public int mTxData;
        /**
         * Current number of messages in transmission queue
         */
        public int mInQueue;
    }
    
    
    /**
     * Exception generated on some API calls
     */
    public static class Error extends IOException {
        private static final long serialVersionUID = 1L;
        public Error(String msg) {
            super(msg);
        }
    }
    
    
    /**
     * Internal exception generated on any detected frame error
     */
    private static class ErrorInternal extends IOException {
        private static final long serialVersionUID = 1L;
        public ErrorInternal(String msg) {
            super(msg);
        }
    }
    
    /**
     * Type for received messages sent to handler
     */
    private static class RxMsg {
        int     mType;
        byte[]  mBytes;
        String  mText;
    }
    
    /**
     * Type for messages inserted in transmission queue
     */
    private static class TxMsg {
        int     mID;
        int     mOpcode;
        int     mHeadsize;
        byte[]  mFrame;
    }

    /**
     * Type for error events
     */
    private static class ErrorEvent {
      int    mCode;
      String  mMsg;
      
      public ErrorEvent(int code, String msg) {
        mCode = code;
        mMsg  = msg;
      }
    }

    
    /** Client Version String */
    public static final String VERSION = "0.9.0";
    
    /** Connection error */
    public static final int E_CONNECT  = 1;
    /** SSL error */
    public static final int E_SSL      = 2;
    /** Input/Output error */
    public static final int E_IO    = 3;
    /** Server handshake error */
    public static final int E_HANDSHAKE = 4;
    /** WebSocket protocol error */
    public static final int E_PROTOCOL  = 5;
    /** Connection closed by  Server */
    public static final int E_CLOSED    = 6;
    
    /** Client is stopped */
    public static final int ST_STOPPED      = 0;
    /** Client is started by not connected yet */
    public static final int ST_STARTED      = 1;
    /** Client is connected to server */
    public static final int ST_CONNECTED    = 2;    

    /** Frame Types */
    /** Single TEXT frame of a NOT fragmented message */
    public static final int F_TEXT          = 1;
    /** First TEXT frame of a fragmented message */
    public static final int F_TEXT_FIRST    = 2;
    /** Next TEXT frame of a fragmented message */
    public static final int F_TEXT_NEXT     = 3;
    /** Last TEXT frame of a fragmented message */
    public static final int F_TEXT_LAST     = 4;
    /** Single BINARY frame of a NOT fragmented message */
    public static final int F_BINARY        = 5;
    /** First BINARY frame of a fragmented message */
    public static final int F_BINARY_FIRST  = 6;
    /** Next BINARY frame of a fragmented message */
    public static final int F_BINARY_NEXT   = 7;
    /** Last BINARY frame of a fragmented message */
    public static final int F_BINARY_LAST   = 8;
    /** PING frame with BINARY PAYLOAD */
    public static final int F_PING          = 9;
    /** PONG frame with BINARY PAYLOAD */
    public static final int F_PONG          = 10;
    
    /** Private Data */
    private Config      mConfig;
    private Handler     mHandler;
    private Logger      mLog;
    private String      mHost;
    private int         mPort;
    private boolean     mSSL;
    private String      mPath;
    private String      mOrigin;
    private Socket      mSocket;
    private Thread                  mRxThread = null;
    private Thread                  mTxThread = null;
    private BufferedInputStream     mSocketIn;
    private OutputStream            mSocketOut;
    private volatile int            mStatus;    
    private volatile boolean        mRxRun;
    private volatile boolean        mTxRun;
    private volatile boolean        mStopping;
    private boolean                 mFIN;
    private boolean                 mMasked;
    private int                     mOpcode;
    private int                     mContinuation = -1;
    private long                    mLength;
    private int                     mHeadSize;
    private int                     mNextFrameID = 1;
    private byte[]                  mMask = new byte[4];
    private Random mRandom          = new Random();
    private ArrayBlockingQueue<TxMsg> mTxQueue;
    private volatile Stats          mStats = new Stats();
    
    // WebSocket Frame
    private static final int LENGTH1        = 126;
    private static final int MASK_FIN       = 0x80;
    private static final int MASK_RSV       = 0x70;
    private static final int MASK_OP        = 0x0F;
    private static final int MASK_MASKED    = 0x80;
    private static final int MASK_LENGTH    = 0x7F;
    private static final int OP_CONT        = 0x00;
    private static final int OP_TEXT        = 0x01;
    private static final int OP_BINARY      = 0x02;
    private static final int OP_CLOSE       = 0x08;
    private static final int OP_PING        = 0x09;
    private static final int OP_PONG        = 0x0A;
    private static final boolean USE_MASK   = true;
    // Magic string for calculating keys.
    private static final String MAGIC       = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    /** Event codes sent to Handler */
    private static final int EV_START       = 1;
    private static final int EV_CONNECT     = 2;
    private static final int EV_CONNECTED   = 3;
    private static final int EV_RECV        = 4;
    private static final int EV_SENT        = 5;
    private static final int EV_ERROR       = 6;
    private static final int EV_STOP        = 7;
    /** Fragmentation */
    private static final int FRAG_NONE      = 0;
    private static final int FRAG_FIRST     = 1;
    private static final int FRAG_NEXT      = 2;
    private static final int FRAG_LAST      = 3;
    /** Opcode types */
    private static final int[] OPCODES = {
        1,              // 0x00 - OP_CONT
        1,              // 0x01 - OP_TEXT
        1,              // 0x02 - OP_BINARY
        0,0,0,0,0,      //        INVALID
        1,              // 0x08 - OP_CLOSE
        1,              // 0x09 - OP_PING
        1,              // 0x0A - OP_PONG
    };
    /** Handshake related */
    private static final int HANDSHAKE_TIMEOUT = 5000;
    private static final int MAX_HEADER_LINE   = 512;
    private static final int MAX_HEADER_LINES  = 10;
 
    
    /**
     * Constructor
     * @param config Reference to configuration object
     * @throws Error 
     */
    public Client(Config config, Listener listener) throws Error {
        
        mConfig  = new Config(config);
        mHandler = new ListenerHandler(listener);
        // Creates logger
        if (mConfig.mLogTag == null) {
            mLog = new NullLogger();
        }
        else {
            mLog = new DefaultLogger(mConfig.mLogTag);
        }
        // Parse server URI
        Uri uri = Uri.parse(config.mURI);
        String scheme = uri.getScheme();
        if (scheme == null) {
            throw new Error("Invalid URI");
        }
        String originScheme = "http";
        if (scheme.equals("wss") || scheme.equals("https")) {
            mSSL = true;
            originScheme = "https";
        }
        mHost = uri.getHost();
        if (mHost == null) {
            throw new Error("Invalid URI");
        }
        // Get server port
        mPort = uri.getPort();
        if (mPort == -1) {
            if (mSSL) {
                mPort = 443;
            }
            else {
                mPort = 80;
            }
        }
        mPath = uri.getPath();
        Uri.Builder uriOrigin = new Uri.Builder();
        uriOrigin.scheme(originScheme);
        uriOrigin.authority(mHost);
        mOrigin = uriOrigin.toString();
        mTxQueue = new ArrayBlockingQueue<TxMsg>(mConfig.mQueueSize);
        clearStats();
        mStatus = ST_STOPPED;
    }

    
    /**
     * Starts the client
     * The client will try to connect to the server until stopped.
     */
    public void start() {
        // If already started or connected, ignore.
        if (mStatus != ST_STOPPED) {
            return;
        }
        // Starts RxThread
        mStatus = ST_STARTED;
        mStopping = false;
        mRxRun = true;
        mRxThread = new RxThread();
        mRxThread.start();
    }
   
    
    /**
     * Stops the client.
     * Clears the transmission queue and sends close frame to the server
     */
    public void stop() {
        // If already stopped, ignore.
        if (mStatus == ST_STOPPED || mStopping) {
            return;
        }
        mStopping = true;
        Thread t = new Thread(new Runnable(){
      @Override
      public void run() {
            // If connected, sends CLOSE frame and waits interval
        // to allow frame to be sent
            if (mStatus == ST_CONNECTED) {
              mTxQueue.clear();
                sendFrame(OP_CLOSE, FRAG_NONE, new byte[0]);
                try {
            Thread.sleep(100);
          } catch (InterruptedException e) {}
            }
            stopTxThread();
            stopRxThread();
      }
         });
        t.start();
    }
   
    
    /**
     * Returns the current client status 
     * @return current status (ST_*)
     */
    public int getStatus() {
        return mStatus;
    }

    
    /**
     * Get statistics data
     * @param stats object to be updated
     */
    public void getStats(Stats stats) {
        stats.mRxFrames = mStats.mRxFrames;
        stats.mRxData   = mStats.mRxData;
        stats.mRxBytes  = mStats.mRxBytes;
        stats.mTxFrames = mStats.mTxFrames;
        stats.mTxData   = mStats.mTxData;
        stats.mTxBytes  = mStats.mTxBytes;
        stats.mInQueue  = mTxQueue.size();
    }
    
    
    /**
     * Clear transmission queue
     */
    public void clearTx() {
        mTxQueue.clear();
    }
   
  
    /**
     * Clears Statistics data
     */
    public void clearStats() {
        mStats.mRxFrames = 0;
        mStats.mRxBytes = 0;
        mStats.mRxData = 0;
        mStats.mTxFrames = 0;
        mStats.mTxBytes = 0;
        mStats.mTxData = 0;
    }
    
    
    /**
     * Inserts TEXT message in transmission queue
     * @param data String with message PAYLOAD
     * @return frame id or null if transmission queue is full.
     * @throws Error
     */
    public Integer send(String data) throws Error {
        return sendFrame(OP_TEXT, FRAG_NONE, encodeString(data));
    }
    
   
    /**
     * Inserts BINARY message in transmission queue
     * @param data Byte array with message PAYLOAD
     * @return frame id or null if queue is full.
     * @throws Error
     */
    public Integer send(byte[] data) throws Error {
        return sendFrame(OP_BINARY, FRAG_NONE, data);
    }
   
  
    /**
     * Inserts PING frame in transmission queue
     * @param payload Byte array with PAYLOAD (up to 125 bytes)
     * @return frame id or null if queue is full.
     * @throws Error 
     */
    public Integer ping(byte[] payload) throws Error {
        if (payload.length >= LENGTH1) {
            throw new Error("Payload MUST be less than " + LENGTH1);
        }
        return sendFrame(OP_PING, FRAG_NONE, payload);
    }
    
    
    /**
     * Inserts PONG frame in transmission queue
     * @param payload Byte array with PAYLOAD (up to 125 bytes)
     * @return frame id or null if queue is full.
     * @throws Error 
     */
    public Integer pong(byte[] payload) throws Error {
        if (payload.length >= LENGTH1) {
            throw new Error("Payload MUST be less than " + LENGTH1);
        }
        return sendFrame(OP_PONG, FRAG_NONE, payload);
    }

   
    /**
     * Insert first fragment of TEXT message in transmission queue
     * @param payload String with frame PAYLOAD
     * @return frame id or null if queue is full.
     * @throws Error
     */
    public Integer sendFirst(String payload) throws Error {
        return sendFrame(OP_TEXT, FRAG_FIRST, encodeString(payload));
    }
 
    
    /**
     * Inserts next fragment of TEXT message in transmission queue
     * @param payload String with frame PAYLOAD
     * @return frame id or null if queue is full.
     * @throws Error
     */
    public Integer sendNext(String payload) throws Error {
        return sendFrame(OP_TEXT, FRAG_NEXT, encodeString(payload));
    }

    
    /**
     * Inserts last fragment of TEXT message in transmission queue
     * @param payload String with frame PAYLOAD
     * @return frame id or null if queue is full.
     * @throws ErrorInternal 
     */
    public Integer sendLast(String payload) throws Error {
        return sendFrame(OP_TEXT, FRAG_LAST, encodeString(payload));
    }

    
    /**
     * Insert first fragment of BINARY message in transmission queue
     * @param payload Byte array with frame PAYLOAD
     * @return frame id or null if queue is full.
     * @throws Error 
     */
    public Integer sendFirst(byte[] payload) throws Error {
        return sendFrame(OP_BINARY, FRAG_FIRST, payload);
    }
    
    
    /**
     * Inserts next fragment of BINARY message in transmission queue
     * @param payload Byte array with frame PAYLOAD
     * @return frame id or null if queue is full.
     * @throws Error 
     */
    public Integer sendNext(byte[] payload) throws Error {
        return sendFrame(OP_BINARY, FRAG_NEXT, payload);
    }

    
    /**
     * Inserts last fragment of BINARY message in transmission queue
     * @param payload Byte array with frame PAYLOAD
     * @return frame id or null if queue is full.
     * @throws Error 
     */
    public Integer sendLast(byte[] payload) throws Error {
        return sendFrame(OP_BINARY, FRAG_LAST, payload);
    }
   
   
    
    /**************************************************************************
     * Start of Private Methods
     */
  
    
    /**
     * Stops reception thread
     */
    private void stopRxThread() {
        mRxRun = false;
        if (mRxThread == null) {
            return;
        }
        // Interrupt reception thread (when sleep)
        // Closes socket to force thread IO exception
        mRxThread.interrupt();
        try {
            if (mSocket != null) {
                if (!mSSL) {
                    mSocket.shutdownInput();
                    mSocket.shutdownOutput();
                }
                else {
                    mSocket.close();
                }
            }
        } catch (IOException e) {}
    }

    
    /**
     * Stops transmission thread
     */
    private void stopTxThread() {
        
        mTxRun = false;
        if (mTxThread != null) {
            mTxThread.interrupt();
        }
    }
   
    
    /**************************************************************************
     * Connection/Reception Thread
     */
    private class RxThread extends Thread {
        
        public RxThread() {
            super("WSCLIENT.RxThread");
        }
        
        @Override
        public void run() {
            long delay   = 0;
            mLog.debug("RxThread started");
            sendEvent(EV_START, null);
            // Main Read Loop: try to connect and read frames
            while (mRxRun) {
                // Delay before trying to connect. The first time the delay is 0.
                try {
                    Thread.sleep(delay);
                } catch (InterruptedException e) {
                    break;
                }
                delay = mConfig.mRetryInterval;
                sendEvent(EV_CONNECT, null);
                // Try to connect with server and if error, retry.
                if (!connect()) {
                    continue;
                }
                // Do handshake with server and if error, retry
                if (!doHandshake()) {
                    continue;
                }
                // Starts the writer thread.
                if (mTxThread == null) {
                    mTxThread = new TxThread();
                    mTxThread.start();
                }
                mStatus = ST_CONNECTED;
                sendEvent(EV_CONNECTED, null);
                // Process received frames and returns on any error.
                processFrames();
                mStatus = ST_STARTED;
                // Stops transmission thread
                mTxRun = false;
                if (mTxThread != null) {
                  mLog.debug("Stopping TxThread");
                    mTxThread.interrupt();
                }
                // Continue to retry
            }
            // This thread is stopping.
            mRxThread = null;
            mStatus = ST_STOPPED;
            sendEvent(EV_STOP, null);
            mLog.debug("RxThread stopped");   
        }
        
        
        /**
         * Connects to server and creates streams
         * @return true if OK, false otherwise
         */
        private boolean connect() {
            // Closes previous connection if any
            if (mSocket != null) {
                try {
                    mSocket.close();
                } catch (IOException e) {}
            }
            // Try to connect with server
            try {
                mLog.debug("RxThread connecting with: %s", mConfig.mURI);
                // Creates normal socket
                if (!mSSL) {
                    mSocket = new Socket();
                }
                // Connects SSL socket
                else {
                    SSLContext sctx = createSSLContext();
                    if (sctx == null) {
                        return false;
                    }
                    SocketFactory socketFactory = sctx.getSocketFactory();
                    mSocket = socketFactory.createSocket();
                }
                // Try to connect with server
                InetSocketAddress address = new InetSocketAddress(mHost, mPort);
                mSocket.connect(address, mConfig.mConnTimeout);
                // Creates streams to read and write to the socket
                mSocketIn  = new BufferedInputStream(mSocket.getInputStream(), 8*1024);
                mSocketOut = mSocket.getOutputStream();
            } catch (UnknownHostException e) {
                sendEvent(EV_ERROR, new ErrorEvent(E_CONNECT, e.getMessage()));
                return false;
            } catch (IOException e) {
                mLog.error("RxThread ERROR: IOException: %s", e.getMessage());
                sendEvent(EV_ERROR, new ErrorEvent(E_CONNECT, e.getMessage()));
                return false;
            }
            mLog.debug("RxThread connected OK");
            return true;
        }
        
       
        /**
         * Sends WebSocket handshake to server and checks response
         * @return true if OK, false otherwise
         */
        private boolean doHandshake() {
            StringBuilder req;
            String seckey;
        
            mLog.debug("RxThread: Sending handshake request");
            seckey = createSecKey();
            // Sends HTTP upgrade request
            req = new StringBuilder();
            req.append("GET " + mPath + " HTTP/1.1\r\n");
            req.append("Upgrade: websocket\r\n");
            req.append("Connection: Upgrade\r\n");
            req.append("Host: " + mHost + "\r\n");
            req.append("Origin: " + mOrigin + "\r\n");
            req.append("Sec-WebSocket-Key: " + seckey + "\r\n");
            req.append("Sec-WebSocket-Version: 13\r\n");
            req.append("\r\n");
            byte[] data = req.toString().getBytes();
            try {
                mSocketOut.write(data);
            } catch (IOException e) {
                sendEvent(EV_ERROR, new ErrorEvent(E_IO, e.getMessage()));
                return false;
            }
            // Sets socket timeout to wait for handshake
            try {
                mSocket.setSoTimeout(HANDSHAKE_TIMEOUT);
            } catch (SocketException e) {
                sendEvent(EV_ERROR, new ErrorEvent(E_IO, e.getMessage()));
                return false;
            }
            // Reads response lines from server
            String[] Responses = new String[MAX_HEADER_LINES];
            int nlines = 0;
            while (true) {
                String line = readLine(mSocketIn);
                if (line == null) {
                    return false;
                }
                // Empty line is response terminator.
                if (line.length() == 0) {
                    break;
                }
                // Saves line in array
                if (nlines < Responses.length) {
                    Responses[nlines++] = line;
                }
            }
            if (nlines < 1) {
                sendEvent(EV_ERROR, new ErrorEvent(E_HANDSHAKE, "Empty response from server"));
                return false;
            }
            // Parses status line
            StatusLine status = BasicLineParser.parseStatusLine(Responses[0], new BasicLineParser());
            if (status.getStatusCode() != HttpStatus.SC_SWITCHING_PROTOCOLS) {
                sendEvent(EV_ERROR, new ErrorEvent(E_HANDSHAKE,
                    String.format("Server response status: %d", status.getStatusCode())));
                return false;
            }
            // Parse headers
            boolean found = false;
            for (int line = 1; line < nlines; line++) {
                Header header = BasicLineParser.parseHeader(Responses[line], new BasicLineParser());
                if (header.getName().equals("Sec-WebSocket-Accept")) {
                    String accept = header.getValue();
                    String calc = calcAccept(seckey);
                    if (!accept.equals(calc)) {
                        sendEvent(EV_ERROR, new ErrorEvent(E_HANDSHAKE, "Server accept key is invalid"));
                        return false;
                    }
                    found = true;
                }
            }
            if (!found) {
                sendEvent(EV_ERROR, new ErrorEvent(E_HANDSHAKE, "Server accept header not found"));
                return false;
            }
            // Remove socket timeout
            try {
                mSocket.setSoTimeout(0);
            } catch (SocketException e) {
                sendEvent(EV_ERROR, new ErrorEvent(E_IO, e.getMessage()));
                return false;
            }
            mLog.debug("RxThread: Handshake OK");
            return true;
        }
      
        
        /**
         * Process received frames and returns on any error
         */
        private void processFrames() {
            boolean text = false;
            
            while (mRxRun) {
                byte[] payload = null;
                // Read next frame
                try {
                    payload = readFrame();
                } catch (IOException e) {
                    sendEvent(EV_ERROR, new ErrorEvent(E_IO, e.getMessage()));
                    return;
                }
                // Update statistics
                mStats.mRxFrames++;
                mStats.mRxData += payload.length;
                mStats.mRxBytes += (mHeadSize + payload.length);
                // Prepare message to send to Handler
                RxMsg m = new RxMsg();
                m.mBytes = payload;
                // Process each frame type
                switch (mOpcode) {
                case OP_CONT:
                    if (mContinuation == OP_TEXT) {
                        text = true;
                        if (mFIN) {
                            m.mType = F_TEXT_LAST;
                            mContinuation = -1;
                        }
                        else {
                            m.mType = F_TEXT_NEXT;
                        }
                        break;
                    }
                    if (mContinuation == OP_BINARY) {
                        if (mFIN) {
                            m.mType = F_BINARY_LAST;
                            mContinuation = -1;
                        }
                        else {
                            m.mType = F_BINARY_NEXT;
                        }
                        break;
                    }
                    sendEvent(EV_ERROR, new ErrorEvent(E_PROTOCOL,
                        "Received Unexpected OP_CONT Frame"));
                    return;
                case OP_TEXT:
                    if (mContinuation >= 0) {
                        sendEvent(EV_ERROR, new ErrorEvent(E_PROTOCOL,
                            "Received OP_TEXT Frame instead of fragment"));
                        return;
                    }
                    text = true;
                    if (mFIN) {
                        m.mType = F_TEXT;
                    }
                    else {
                        m.mType = F_TEXT_FIRST;
                        mContinuation = OP_TEXT;
                    }
                    break;
                case OP_BINARY:
                    if (mContinuation >= 0) {
                        sendEvent(EV_ERROR, new ErrorEvent(E_PROTOCOL,
                            "Received OP_BINARY Frame instead of fragment"));
                        return;
                    }
                    if (mFIN) {
                        m.mType = F_BINARY;
                    }
                    else {
                        m.mType = F_BINARY_FIRST;
                        mContinuation = OP_BINARY;
                    }
                    break;
                case OP_CLOSE:
                  mLog.debug("RxThread: OP_CLOSE from server");
                  // If client is already stopping, this frame was a response from
                  // a previous sent CLOSE frame.
                  if (mStopping) {
                    return;
                  }
                    // Stops client.
                  // We should have sent a CLOSE response to the server but for
                  // simplification we just shutdown the client.
                    sendEvent(EV_ERROR, new ErrorEvent(E_CLOSED, "Closed by Server"));
                    stopTxThread();
                    stopRxThread();
                    return;
                case OP_PING:
                    if (payload.length >= LENGTH1) {
                        sendEvent(EV_ERROR, new ErrorEvent(E_PROTOCOL,
                            "Received PING with payload >=" + LENGTH1));
                        return;
                    }
                    // Sends PONG if configured to do so.
                    if (mConfig.mRespondPing) { 
                        sendFrame(OP_PONG, FRAG_NONE, payload);
                        mLog.debug("RxThread: Sent PONG");
                        continue;
                    }
                    m.mType = F_PING;
                    break;
                case OP_PONG:
                    if (payload.length >= LENGTH1) {
                        sendEvent(EV_ERROR, new ErrorEvent(E_PROTOCOL, "Received PONG with payload >=" + LENGTH1));
                        return;
                    }
                    m.mType = F_PONG;
                    break;
                }
                // Decodes the PAYLOAD of TEXT frames
                if (text) {
                    try {
                        m.mText = decodeString(payload);
                    } catch (ErrorInternal e) {
                        sendEvent(EV_ERROR, new ErrorEvent(E_PROTOCOL, e.getMessage()));
                        return;
                    }
                }
                sendEvent(EV_RECV, m);
            }
        }
    }
    
    
    /**
     * Reads complete frame from input
     * @return PAYLOAD of received frame 
     * @throws IOException
     */
    private byte[] readFrame() throws IOException {
 
        readOpcode();
        readLength();
        readMask();
        return readPayload();
    }
  
   
    /**
     * Blocks reading for frame start byte
     * @throws IOException for any error
     */
    private void readOpcode() throws IOException {
        int data;
        
        data = mSocketIn.read();
        if (data < 0) {
            throw new ErrorInternal("Connection Closed");
        }
        if ((data & MASK_RSV) != 0) {
            throw new ErrorInternal("Received RSV different from zero");
        }
        mFIN = (data & MASK_FIN) == MASK_FIN;
        mOpcode = data & MASK_OP;
        if (mOpcode >= OPCODES.length || OPCODES[mOpcode] == 0) {
            throw new ErrorInternal("Received Invalid OPCODE");
        }
    }
    
   
    /**
     * Blocks reading the frame length
     * The frame length can have from 1 to 9 bytes
     * @throws IOException for any error
     */
    private void readLength() throws IOException {
        int mlen;
   
        // Reads mask + length byte
        mlen = mSocketIn.read();
        if (mlen < 0) {
            mLog.debug("RxThread: readLength EOF");
            throw new ErrorInternal("Connection Closed");
        }
        mMasked = (mlen & MASK_MASKED) == MASK_MASKED;
        mLength = (mlen & MASK_LENGTH);
        mHeadSize = 2;
        
        if (mLength < 126) {
            return;
        }
        if (mLength == 126)  {
            byte[] len = new byte[2];
            readBytes(mSocketIn, len);
            mLength = 0;
            for (int pos = 0; pos < 2; pos++) {
                mLength = (mLength << 8) + (len[pos] & 0xFF);
            }
            mHeadSize += 2;
            return;
        }
        // Length = 127
        byte[] len = new byte[8];
        readBytes(mSocketIn, len);
        mLength = 0;
        for (int pos = 0; pos < 8; pos++) {
            mLength = (mLength << 8) + (len[pos] & 0xFF);
        }
        mHeadSize += 8;
    }

  
    /**
     * Blocks reading the frame mask (if applicable)
     * @throws IOException
     */
    private void readMask() throws IOException {
        if (!mMasked) {
            return;
        }
        mHeadSize += mMask.length;
        readBytes(mSocketIn, mMask);
    }
    
   
    /**
     * Blocks reading the frame pay load
     * @return byte array with frame pay load
     * @throws IOException
     */
    private byte[] readPayload() throws IOException {
        
        if (mConfig.mMaxRxSize != 0 && mLength > mConfig.mMaxRxSize) {
            throw new ErrorInternal("Received Frame size greater than maximum");
        }
        byte[] payload = new byte[(int)mLength];
        readBytes(mSocketIn, payload);
        return payload;
    }
    
    
    /**************************************************************************
     * Client Transmission Thread
     */
    private class TxThread extends Thread {
            
        public TxThread() {
            super("WSCLIENT.TxThread");
        }
        
        @Override
        public void run() {
            mLog.debug("TxThread started");
            TxMsg txmsg;
            mTxRun = true;
            while (mTxRun) {
                // Blocks waiting for next frame to send
                try {
                   txmsg = mTxQueue.take();
                } catch (InterruptedException e1) {
                    mLog.debug("TxThread interrupted");
                    break;
                }
                // Sends frame
                try {
                    mSocketOut.write(txmsg.mFrame, 0, txmsg.mFrame.length);
                    mSocketOut.flush();
                    mStats.mTxFrames++;
                    mStats.mTxBytes += txmsg.mFrame.length; 
                    mStats.mTxData  += (txmsg.mFrame.length - txmsg.mHeadsize);
                    sendEvent(EV_SENT, txmsg);
                    if (txmsg.mOpcode == OP_CLOSE) {
                      mLog.debug("TxThread: CLOSE sent");
                      break;
                    }
                } catch (IOException e) {
                    break;
                }
            }
            mTxThread = null;
            mLog.debug("TxThread: stopped");
        }
    }

  
    /**
     * Builds and inserts frame into transmission queue
     * @param opcode Frame opcode (OP_*)
     * @param frag   Fragmentation type  (FRAG_*)
     * @param payload Byte array with pay load
     * @return Frame id if frame inserted or null if queue is full.
     */
    private Integer sendFrame(int opcode, int frag, byte[] payload) {
        int paylen;
        int headsize;
        int masksize;
        int next;
        int fid;
        byte[] frame;
        byte[] mask;
  
        // Calculates the header size and allocates frame buffer
        paylen = payload.length;
        if (paylen < 126) {
            headsize = 2;
        }
        else if (paylen <= 0xFFFF) {
            headsize = 4;
        }
        else {
            headsize = 10;
        }
        masksize = 0;
        if (USE_MASK) {
            masksize = 4;
        }
        frame = new byte[headsize + masksize + paylen];
        // Sets the FIN bit and opcode of the frame
        switch (frag) {
        case FRAG_NONE:
            frame[0] = (byte)(MASK_FIN | opcode);
            break;
        case FRAG_FIRST:
            frame[0] = (byte)opcode;
            break;
        case FRAG_NEXT:
            frame[0] = (byte)OP_CONT;
            break;
        case FRAG_LAST:
            frame[0] = (byte)(MASK_FIN | OP_CONT);
            break;
        }
        if (USE_MASK) {
            frame[1] = (byte)MASK_MASKED;
        }
        else {
            frame[1] = 0;
        }
        if (headsize == 2) {
            frame[1] |= (byte)paylen;
            next = 2;
        }
        else if (headsize == 4) {
            frame[1] |= 126;
            frame[2] = (byte)(paylen >> 8);
            frame[3] = (byte)paylen;
            next = 4;
        }
        else {
            frame[1] |= 127;
            frame[2] = 0;
            frame[3] = 0;
            frame[4] = 0;
            frame[5] = 0;
            frame[6] = (byte)(paylen >> 24);
            frame[7] = (byte)(paylen >> 16);
            frame[8] = (byte)(paylen >> 8);
            frame[9] = (byte)(paylen);
            next = 10;
        }
        // Generates random mask
        if (USE_MASK) {
            mask = new byte[4];
            mRandom.nextBytes(mask);
            System.arraycopy(mask, 0, frame, next, mask.length);
            next += 4;
        }
        // Copy pay load to frame and masks it
        System.arraycopy(payload, 0, frame, next, paylen);
        if (USE_MASK) {
            maskPayload(frame, next, mask);
        }
        // Inserts frame in transmission queue
        fid = mNextFrameID++;
        TxMsg txmsg = new TxMsg();
        txmsg.mOpcode = opcode;
        txmsg.mID = fid;
        txmsg.mHeadsize = headsize + masksize;
        txmsg.mFrame = frame;
        if (mTxQueue.offer(txmsg)) {
            return fid;
        }
        return null;
    }
   
    
    /**
     * Creates an SSL Context which could check or not the server certificate,
     * depending on configuration option.
     * @return SSLContext to use or null if error
     */
    private SSLContext createSSLContext() {
        SSLContext context = null;
        try {
            context = SSLContext.getInstance("TLS");
        } catch (NoSuchAlgorithmException e) {
            sendEvent(EV_ERROR, new ErrorEvent(E_SSL, e.getMessage()));
            return null;
        }
        TrustManager[] trustManagers;
        if (mConfig.mServerCert) {
            trustManagers = new TrustManager[0];
        }
        else {
            trustManagers = new X509TrustManager[]{
                new X509TrustManager() {
                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
                    public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
                }
            };
        }
        try {
            context.init(null, trustManagers, new SecureRandom());
        } catch (KeyManagementException e) {
            sendEvent(EV_ERROR, new ErrorEvent(E_SSL, e.getMessage()));
        }
        return context;
    }
   
    
    /**
     * Masks frame pay load
     * @param frame  Buffer with frame to mask
     * @param pos    Position in the frame where the pay load starts
     * @param mask   Byte array with mask
     */
    private static void maskPayload(byte[] frame, int pos, byte[] mask) {
        int idx;
        int len;
        
        len = frame.length - pos;
        for (idx = 0; idx < len; idx++) {
            frame[pos + idx] ^= (byte)(mask[idx % 4]);
        }
    }
   
   
    /**
     * Creates accept key string for handshake
     * @return String with secret key BASE64 encoded
     */
    private String createSecKey() {
        
        byte[] seckey = new byte[16];
        mRandom.nextBytes(seckey);
        return Base64.encodeToString(seckey, Base64.DEFAULT).trim();
    }

   
    /**
     * Calculates the accept key for specified secret
     * @param secret String with secret key
     * @return String with accept key
     */
    private String calcAccept(String secret) {
        String concat;
        MessageDigest md;
        byte[] sha1;
    
        concat = secret + MAGIC;
        try {
            md = MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException e) {
            return "";
        }
        md.update(concat.getBytes(), 0, concat.length());
        sha1 = md.digest();
        return Base64.encodeToString(sha1, Base64.DEFAULT).trim();
    }

   
    /**
     * Decodes String from UTF-8 byte array
     * @param data Byte array to decode
     * @return decoded string
     * @throws ErrorInternal 
     */
    private static String decodeString(byte[] data) throws ErrorInternal {
        try {
            return new String(data, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new ErrorInternal(e.getMessage());
        }
    }
   
    
    /**
     * Encodes String into UTF-8 byte array 
     * @param data String to encode
     * @return byte array or null if error
     * @throws Error 
     */
    private static byte[] encodeString(String data) throws Error {
        try {
            return data.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new Error(e.getMessage());
        }
    }
   
    
    /**
     * Read all the specified number of bytes from the input stream
     * @param buffer Buffer to read
     * @return number of bytes read or null if error
     * @throws IOException 
     */
    private void readBytes(InputStream is, byte[] buffer) throws IOException {
        int size = buffer.length;
        int nread = 0;
        int bytes;
        
        while (nread < size) {
            bytes = is.read(buffer, nread, size - nread);
            if (bytes <= 0) {
                throw new ErrorInternal("Connection Closed");
            }
            nread += bytes;
        }
    }
  
    
    /**
     * Reads next line terminated by "\r\n" from stream
     * @param stream InputStream to read from
     * @return String with line or null if error or EOF
     * @throws IOException
     */
    private String readLine(InputStream stream) {
        int readChar;
        int len = 0;
        StringBuilder line = new StringBuilder();
        
        while (true) {
            try {
                readChar = stream.read();
            } catch (SocketTimeoutException e) {
                sendEvent(EV_ERROR, new ErrorEvent(E_HANDSHAKE, "Handshake Timeout"));
                return null;
            } catch (IOException e) {
                sendEvent(EV_ERROR, new ErrorEvent(E_IO, e.getMessage()));
                return null;
            }
            if (readChar == -1) {
                sendEvent(EV_ERROR, new ErrorEvent(E_CLOSED, "Connection Closed"));
                return null;
            }
            if (readChar == '\r') {
                continue;
            }
            if (readChar == '\n') {
                return line.toString();
            }
            if (len >= MAX_HEADER_LINE) {
                sendEvent(EV_ERROR, new ErrorEvent(E_PROTOCOL, "Max header line recv"));
                return null;
            }
            line.append((char)readChar);
            len++;
        }
    }

    
    /**
     * Sends event to handler
     * @param what Event code
     * @param obj  Optional event data
     */
    private void sendEvent(int what, Object obj) {
  
        if (what == EV_ERROR) {
          ErrorEvent ee = (ErrorEvent)obj;
            mLog.error("Error: %d:%s", ee.mCode, ee.mMsg);
        }
        mHandler.sendMessage(mHandler.obtainMessage(what, obj));
    }
   
    
    /**
     * Handler to process events in user thread
     */
    private static class ListenerHandler extends Handler {
        private Listener mListener; 
        
        public ListenerHandler(Listener listener) {
            super();
            mListener = listener;
        }
    
        public void handleMessage(android.os.Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
            case EV_START:
                mListener.onClientStart();
                break;
            case EV_CONNECT:
                mListener.onClientConnect();
                break;
            case EV_CONNECTED:
                mListener.onClientConnected();
                break;
            case EV_RECV:
                RxMsg rx = (RxMsg)msg.obj;
                if (rx.mType == OP_TEXT) {
                    mListener.onClientRecv(rx.mType, rx.mText);
                }
                else {
                    mListener.onClientRecv(rx.mType, rx.mBytes);
                }
                break;
            case EV_SENT:
                TxMsg tx = (TxMsg)msg.obj;
                mListener.onClientSent(tx.mID);
                break;
            case EV_ERROR:
              ErrorEvent ee = (ErrorEvent)msg.obj;
                mListener.onClientError(ee.mCode, ee.mMsg);
                break;
            case EV_STOP:
                mListener.onClientStop();
                break;
            }
        }
    }
  
    
    /**
     * Internal Logger interface
     */
    private interface Logger {
        void debug(String format, Object... args);
        void error(String format, Object... args);
    }
   
    
    /**
     * Logger which does not generate any logs
     */
    private static class NullLogger implements Logger {
        @Override
        public void debug(String format, Object... args) {}
        @Override
        public void error(String format, Object... args) {}
    }
   
    
    /**
     * Logger which uses standard Android log facility
     */
    private static class DefaultLogger implements Logger {
        private String mTag;
        
        public DefaultLogger(String tag) {
            mTag = tag;
        }

        @Override
        public void debug(String format, Object... args) {
            Log.println(Log.DEBUG, mTag, String.format(format, args));
        }

        @Override
        public void error(String format, Object... args) {
            Log.println(Log.ERROR, mTag, String.format(format, args));
        }
    }
}




Java Source Code List

com.elaxys.android.websocket.Client.java
com.elaxys.android.wsclient_test.MainActivity.java
com.elaxys.android.wsclient_test.PrefsActivity.java