diff --git a/assets/www/js/websocket.js b/assets/www/js/websocket.js index 4f7091c..42b5cd3 100755 Binary files a/assets/www/js/websocket.js and b/assets/www/js/websocket.js differ diff --git a/src/com/strumsoft/App.java b/src/com/strumsoft/App.java index 777141d..0b66fec 100755 --- a/src/com/strumsoft/App.java +++ b/src/com/strumsoft/App.java @@ -28,28 +28,19 @@ import android.os.Bundle; import com.phonegap.DroidGap; -import com.strumsoft.util.Logger; import com.strumsoft.websocket.phonegap.WebSocketFactory; /** * The Class App. */ public class App extends DroidGap { - - /** - * Called when the activity is first created. - * - * @param savedInstanceState the saved instance state - */ - @Override + + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.loadUrl("file:///android_asset/www/index.html"); - // attach logger - appView.addJavascriptInterface(Logger.class, "Logger"); // attach websocket factory appView.addJavascriptInterface(new WebSocketFactory(appView), "WebSocketFactory"); } - } \ No newline at end of file diff --git a/src/com/strumsoft/util/Logger.java b/src/com/strumsoft/util/Logger.java deleted file mode 100755 index 1442ba7..0000000 --- a/src/com/strumsoft/util/Logger.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2010 STRUMSOFT (http://www.strumsoft.com) - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - */ -package com.strumsoft.util; - -import android.util.Log; - -/** - * Logger for simple verbose logging. - * - * @author Animesh Kumar - * - */ -public class Logger { - - /** The Constant TAG. */ - public final static String TAG = "Phonegap::Android::Websocket"; - - /** - * Logs Android verbose log. - * - * @param obj the obj - */ - public static final void log(Object obj) { - Log.v(TAG, obj.toString()); - } -} diff --git a/src/com/strumsoft/websocket/WebSocket.java b/src/com/strumsoft/websocket/WebSocket.java deleted file mode 100755 index abea53c..0000000 --- a/src/com/strumsoft/websocket/WebSocket.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.strumsoft.websocket; - -import java.net.URI; -import java.net.URISyntaxException; - -import com.strumsoft.util.Logger; - -/** - * Sample WebSocket implementation. - * - * WebSocket socket = new WebSocket('ws://ip:port'); - * socket.connect(); - * - * @author Animesh Kumar - * - */ -public class WebSocket extends WebSocketListener { - - public WebSocket(URI uri) throws URISyntaxException { - super(uri); - } - - @Override - public void onClose() { - Logger.log("Connection closed!"); - } - - @Override - public void onMessage(String message) { - Logger.log("Message from server: " + message); - } - - @Override - public void onOpen() { - Logger.log("Connection opened!"); - } - - @Override - public void onReconnect() { - Logger.log("Reconnecing..."); - } -} diff --git a/src/com/strumsoft/websocket/WebSocketListener.java b/src/com/strumsoft/websocket/WebSocketListener.java deleted file mode 100755 index beeb5c5..0000000 --- a/src/com/strumsoft/websocket/WebSocketListener.java +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (c) 2010 STRUMSOFT (http://www.strumsoft.com) - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - */ -package com.strumsoft.websocket; - - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.URI; -import java.nio.ByteBuffer; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.SocketChannel; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Iterator; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; - -import com.strumsoft.websocket.phonegap.WebSocket; - -/** - * Originally written by Nathan Rajlich - * - * WebSocketListener is an abstract class that expects a valid - * "ws://" URI to connect to, and after establishing the connection, it recieves and - * subsequently calls methods related to the life of the connection. A subclass must - * implement onOpen, onClose, and onMessage to be - * useful. - * - * @author Animesh Kumar (animesh@strumsoft.com) - */ -public abstract class WebSocketListener implements Runnable { - - // INSTANCE PROPERTIES ///////////////////////////////////////////////////// - /** - * The URI this client is supposed to connect to. - */ - private URI uri ; - /** - * The WebSocket instance this client object wraps. - */ - private WebSocketProtocol conn; - /** - * The SocketChannel instance this client uses. - */ - private SocketChannel client; - /** - * The 'Selector' used to get event keys from the underlying socket. - */ - private Selector selector; - /** - * Keeps track of whether or not the client thread should continue running. - */ - private boolean running; - - /** Should try to reconnect?. */ - private int attempts; - - /** How long should it wait before it should reconnect?. */ - private int reConnectWaitDuration = 5000; // 5 seconds - /** - * The Draft of the WebSocket protocol the Client is adhering to. - */ - private WebSocketProtocol.Draft draft; - - /** Number 1 used in handshake. */ - private int number1 = 0; - - /** Number 2 used in handshake. */ - private int number2 = 0; - - /** Key3 used in handshake. */ - private byte[] key3 = null; - - /** - * Instantiates a new web socket listener. - * - * @param uri the uri - */ - public WebSocketListener(URI uri) { - this(uri, WebSocketProtocol.Draft.DRAFT75, 2); - } - - /** - * Constructs a WebSocketClient instance and sets it to the connect to the - * specified URI. The client does not attampt to connect automatically. You - * must call connect first to initiate the socket connection. - * - * @param uri the uri - * @param draft the draft - * @param attempts the attempts - */ - public WebSocketListener(URI uri, WebSocketProtocol.Draft draft, int attempts) { - this.uri = uri; - - if (draft == WebSocketProtocol.Draft.AUTO) { - throw new IllegalArgumentException(draft - + " is meant for `WebSocketServer` only!"); - } - - this.draft = draft; - this.attempts = attempts; - } - - // PUBLIC INSTANCE METHODS ///////////////////////////////////////////////// - /** - * Gets the URI that this WebSocketClient is connected to. - * - * @return The for this WebSocketClient. - */ - public URI getURI() { - return uri; - } - - /** - * Gets the draft. - * - * @return the draft - */ - public WebSocketProtocol.Draft getDraft() { - return this.draft; - } - - /** - * Starts a background thread that attempts and maintains a WebSocket - * connection to the URI specified in the constructor or via - * setURI. setURI. - */ - public void connect() { - this.running = true; - (new Thread(this)).start(); - } - - /** - * Calls close on the underlying SocketChannel, which in turn - * closes the socket connection, and ends the client socket thread. - * - * @throws IOException - * When socket related I/O errors occur. - */ - public void close() throws IOException { - this.running = false; - selector.wakeup(); - conn.close(); - } - - /** - * Sends text to the connected WebSocket server. - * - * @param text - * The String to send to the WebSocket server. - * @throws IOException - * When socket related I/O errors occur. - */ - public void send(String text) throws IOException { - conn.send(text); - } - - // Runnable IMPLEMENTATION ///////////////////////////////////////////////// - /* (non-Javadoc) - * @see java.lang.Runnable#run() - */ - public void run() { - int count = 0; - do { - try { - _connect(); - } catch (IOException e) { - e.printStackTrace(); - - // check for re-attempts - if (++count >= attempts) { - return; - } - - // put to sleep for some time - try { - Thread.sleep(reConnectWaitDuration); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } - } while (true); - } - - /** - * _connect. - * - * @throws IOException Signals that an I/O exception has occurred. - */ - private void _connect() throws IOException { - int port = uri.getPort(); - if (port == -1) { - port = WebSocketProtocol.DEFAULT_PORT; - } - - // The WebSocket constructor expects a SocketChannel that is - // non-blocking, and has a Selector attached to it. - - client = SocketChannel.open(); - client.configureBlocking(false); - client.connect(new InetSocketAddress(uri.getHost(), port)); - - // More info: http://groups.google.com/group/android-developers/browse_thread/thread/45a8b53e9bf60d82 - // http://stackoverflow.com/questions/2879455/android-2-2-and-bad-address-family-on-socket-connect - - System.setProperty("java.net.preferIPv4Stack", "true"); - System.setProperty("java.net.preferIPv6Addresses", "false"); - - selector = Selector.open(); - - this.conn = new WebSocketProtocol(client, - new LinkedBlockingQueue(), this); - - // At first, we're only interested in the 'CONNECT' keys. - client.register(selector, SelectionKey.OP_CONNECT); - - // Continuous loop that is only supposed to end when "close" is called. - while (this.running) { - selector.select(); - Set keys = selector.selectedKeys(); - Iterator i = keys.iterator(); - - while (i.hasNext()) { - SelectionKey key = i.next(); - i.remove(); - - // When 'conn' has connected to the host - if (key.isConnectable()) { - - // Ensure connection is finished - if (client.isConnectionPending()) { - client.finishConnect(); - } - - // Now that we're connected, re-register for only 'READ' - // keys. - client.register(selector, SelectionKey.OP_READ); - - // Now send the WebSocket client-side handshake - String path = uri.getPath(); - if (path.indexOf("/") != 0) { - path = "/" + path; - } - String host = uri.getHost() - + (port != WebSocketProtocol.DEFAULT_PORT ? ":" + port : ""); - String origin = "*"; // TODO: Make 'origin' configurable - String request = "GET " + path + " HTTP/1.1\r\n" - + "Upgrade: WebSocket\r\n" - + "Connection: Upgrade\r\n" + "Host: " + host - + "\r\n" + "Origin: " + origin + "\r\n"; - if (this.draft == WebSocketProtocol.Draft.DRAFT76) { - request += "Sec-WebSocket-Key1: " + this.generateKey() - + "\r\n"; - request += "Sec-WebSocket-Key2: " + this.generateKey() - + "\r\n"; - this.key3 = new byte[8]; - (new Random()).nextBytes(this.key3); - } - // extraHeaders.toString() + - request += "\r\n"; - conn.socketChannel().write( - ByteBuffer.wrap(request - .getBytes(WebSocketProtocol.UTF8_CHARSET))); - if (this.key3 != null) { - conn.socketChannel().write(ByteBuffer.wrap(this.key3)); - } - } - //Utils.printString("key.isReadable() : "+key.isReadable(), Utils.PRINT_IMPORTANT); - // When 'conn' has recieved some data - if (key.isReadable()) { - try { - //**************** - conn.handleRead(); - //************************ - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - } - } - } - } - - /** - * Generate key. - * - * @return the string - */ - private String generateKey() { - Random r = new Random(); - long maxNumber = 4294967295L; - long spaces = r.nextInt(12) + 1; - int max = new Long(maxNumber / spaces).intValue(); - max = Math.abs(max); - int number = r.nextInt(max) + 1; - if (this.number1 == 0) { - this.number1 = number; - } else { - this.number2 = number; - } - long product = number * spaces; - String key = Long.toString(product); - int numChars = r.nextInt(12); - for (int i = 0; i < numChars; i++) { - int position = r.nextInt(key.length()); - position = Math.abs(position); - char randChar = (char) (r.nextInt(95) + 33); - // exclude numbers here - if (randChar >= 48 && randChar <= 57) { - randChar -= 15; - } - key = new StringBuilder(key).insert(position, randChar).toString(); - } - for (int i = 0; i < spaces; i++) { - int position = r.nextInt(key.length() - 1) + 1; - position = Math.abs(position); - key = new StringBuilder(key).insert(position, "\u0020").toString(); - } - return key; - } - - // WebSocketListener IMPLEMENTATION //////////////////////////////////////// - /** - * Parses the server's handshake to verify that it's a valid WebSocket - * handshake. - * - * @param conn The {@link WebSocket} instance who's handshake has been - * recieved. In the case of WebSocketClient, this.conn - * == conn. - * @param handshake The entire UTF-8 decoded handshake from the connection. - * @param reply the reply - * @return is a valid WebSocket - * server handshake, otherwise. - * @throws IOException When socket related I/O errors occur. - * @throws NoSuchAlgorithmException the no such algorithm exception - */ - public boolean onHandshakeRecieved(WebSocketProtocol conn, String handshake, - byte[] reply) throws IOException, NoSuchAlgorithmException { - // TODO: Do some parsing of the returned handshake, and close connection - // (return false) if we recieved anything unexpected. - if (this.draft == WebSocketProtocol.Draft.DRAFT76) { - if (reply == null) { - return false; - } - byte[] challenge = new byte[] { (byte) (this.number1 >> 24), - (byte) ((this.number1 << 8) >> 24), - (byte) ((this.number1 << 16) >> 24), - (byte) ((this.number1 << 24) >> 24), - (byte) (this.number2 >> 24), - (byte) ((this.number2 << 8) >> 24), - (byte) ((this.number2 << 16) >> 24), - (byte) ((this.number2 << 24) >> 24), this.key3[0], - this.key3[1], this.key3[2], this.key3[3], this.key3[4], - this.key3[5], this.key3[6], this.key3[7] }; - MessageDigest md5 = MessageDigest.getInstance("MD5"); - byte[] expected = md5.digest(challenge); - for (int i = 0; i < reply.length; i++) { - if (expected[i] != reply[i]) { - return false; - } - } - } - return true; - } - - /** - * Calls subclass' implementation of onMessage. - * - * @param conn the conn - * @param message the message - */ - public void onMessage(WebSocketProtocol conn, String message) { - onMessage(message); - } - - /** - * Calls subclass' implementation of onOpen. - * - * @param conn the conn - */ - public void onOpen(WebSocketProtocol conn) { - onOpen(); - } - - /** - * Calls subclass' implementation of onClose. - * - * @param conn the conn - */ - public void onClose(WebSocketProtocol conn) { - onClose(); - } - - /** - * On reconnect. - * - * @param conn the conn - */ - public void onReconnect(WebSocketProtocol conn) { - onReconnect(); - } - // ABTRACT METHODS ///////////////////////////////////////////////////////// - /** - * On message. - * - * @param message the message - */ - public abstract void onMessage(String message); - - /** - * On open. - */ - public abstract void onOpen(); - - /** - * On close. - */ - public abstract void onClose(); - - /** - * On reconnect. - */ - public abstract void onReconnect(); -} diff --git a/src/com/strumsoft/websocket/WebSocketProtocol.java b/src/com/strumsoft/websocket/WebSocketProtocol.java deleted file mode 100755 index 20d79c8..0000000 --- a/src/com/strumsoft/websocket/WebSocketProtocol.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (c) 2010 STRUMSOFT (http://www.strumsoft.com) - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - */ -package com.strumsoft.websocket; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.nio.channels.NotYetConnectedException; -import java.nio.channels.SocketChannel; -import java.security.NoSuchAlgorithmException; -import java.util.concurrent.BlockingQueue; - -/** - * Originally written by Nathan Rajlich - * - * Represents one end (client or server) of a single WebSocket connection. - * Takes care of the "handshake" phase, then allows for easy sending of - * text frames, and recieving frames through an event-based model. - * - * This is an inner class, used by WebSocketClient and - * WebSocketServer, and should never need to be instantiated directly - * by your code. However, instances are exposed in WebSocketServer - * through the onClientOpen, onClientClose, - * onClientMessage callbacks. - * - * @author Animesh Kumar (smile.animesh@gmail.com) - */ -public final class WebSocketProtocol { - - /** - * The Enum Draft. - */ - public enum Draft { - - /** The AUTO. */ - AUTO, - /** The DRAF t75. */ - DRAFT75, - /** The DRAF t76. */ - DRAFT76 - } - - /** - * The default port of WebSockets, as defined in the spec. If the nullary - * constructor is used, DEFAULT_PORT will be the port the WebSocketServer is - * binded to. Note that ports under 1024 usually require root permissions. - */ - public static final int DEFAULT_PORT = 80; - /** - * The WebSocket protocol expects UTF-8 encoded bytes. - */ - // public static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); - public static final String UTF8_CHARSET = "UTF-8"; - - /** The byte representing CR, or Carriage Return, or \r. */ - public static final byte CR = (byte) 0x0D; - - /** The byte representing LF, or Line Feed, or \n. */ - public static final byte LF = (byte) 0x0A; - /** - * The byte representing the beginning of a WebSocket text frame. - */ - public static final byte START_OF_FRAME = (byte) 0x00; - /** - * The byte representing the end of a WebSocket text frame. - */ - public static final byte END_OF_FRAME = (byte) 0xFF; - - // INSTANCE PROPERTIES ///////////////////////////////////////////////////// - /** - * The SocketChannel instance to use for this server connection. - * This is used to read and write data to. - */ - private final SocketChannel socketChannel; - /** - * Internally used to determine whether to recieve data as part of the - * remote handshake, or as part of a text frame. - */ - private boolean handshakeComplete; - /** - * The listener to notify of WebSocket events. - */ - private WebSocketListener wsl; - /** - * The 1-byte buffer reused throughout the WebSocket connection to read - * data. - */ - private ByteBuffer buffer; - /** - * The bytes that make up the remote handshake. - */ - private ByteBuffer remoteHandshake; - /** - * The bytes that make up the current text frame being read. - */ - private ByteBuffer currentFrame; - /** - * Queue of buffers that need to be sent to the client. - */ - private BlockingQueue bufferQueue; - /** - * Lock object to ensure that data is sent from the bufferQueue in the - * proper order. - */ - private Object bufferQueueMutex = new Object(); - - // CONSTRUCTOR ///////////////////////////////////////////////////////////// - /** - * Used in {@link WebSocketServer} and {@link WebSocketClient}. - * - * @param socketChannel - * The SocketChannel instance to read and write to. The - * channel should already be registered with a Selector before - * construction of this object. - * @param bufferQueue - * The Queue that we should use to buffer data that hasn't been - * sent to the client yet. - * @param listener - * The {@link WebSocketListener} to notify of events when they - * occur. - */ - protected WebSocketProtocol(SocketChannel socketChannel, - BlockingQueue bufferQueue, WebSocketListener listener) { - - this.socketChannel = socketChannel; - this.bufferQueue = bufferQueue; - this.handshakeComplete = false; - this.remoteHandshake = this.currentFrame = null; - this.buffer = ByteBuffer.allocate(1); - this.wsl = listener; - } - - /** - * Should be called when a Selector has a key that is writable for this - * WebSocket's SocketChannel connection. - * - * @throws IOException When socket related I/O errors occur. - * @throws NoSuchAlgorithmException the no such algorithm exception - */ - public void handleRead() throws IOException, NoSuchAlgorithmException { - this.buffer.rewind(); - - int bytesRead = -1; - try { - bytesRead = this.socketChannel.read(this.buffer); - } catch (Exception ex) { - } - - if (bytesRead == -1) { - close(); - } else if (bytesRead > 0) { - this.buffer.rewind(); - - if (!this.handshakeComplete) { - recieveHandshake(); - } else { - recieveFrame(); - } - } - } - - // PUBLIC INSTANCE METHODS ///////////////////////////////////////////////// - /** - * Closes the underlying SocketChannel, and calls the listener's onClose - * event handler. - * - * @throws IOException - * When socket related I/O errors occur. - */ - public void close() throws IOException { - this.socketChannel.close(); - this.wsl.onClose(this); - } - - /** - * Send. - * - * @param text the text - * @return True if all of the text was sent to the client by this thread. - * False if some of the text had to be buffered to be sent later. - * @throws IOException Signals that an I/O exception has occurred. - */ - public boolean send(String text) throws IOException { - if (!this.handshakeComplete) - throw new NotYetConnectedException(); - if (text == null) - throw new NullPointerException( - "Cannot send 'null' data to a WebSocket."); - - // Get 'text' into a WebSocket "frame" of bytes - byte[] textBytes = text.getBytes(UTF8_CHARSET.toString()); - ByteBuffer b = ByteBuffer.allocate(textBytes.length + 2); - b.put(START_OF_FRAME); - b.put(textBytes); - b.put(END_OF_FRAME); - b.rewind(); - - // See if we have any backlog that needs to be sent first - if (handleWrite()) { - // Write the ByteBuffer to the socket - this.socketChannel.write(b); - } - - // If we didn't get it all sent, add it to the buffer of buffers - if (b.remaining() > 0) { - if (!this.bufferQueue.offer(b)) { - throw new IOException( - "Buffers are full, message could not be sent to" - + this.socketChannel.socket() - .getRemoteSocketAddress()); - } - return false; - } - - return true; - } - - /** - * Checks for buffered data. - * - * @return true, if successful - */ - boolean hasBufferedData() { - return !this.bufferQueue.isEmpty(); - } - - /** - * Handle write. - * - * @return True if all data has been sent to the client, false if there is - * still some buffered. - * @throws IOException Signals that an I/O exception has occurred. - */ - boolean handleWrite() throws IOException { - synchronized (this.bufferQueueMutex) { - ByteBuffer buffer = this.bufferQueue.peek(); - while (buffer != null) { - this.socketChannel.write(buffer); - if (buffer.remaining() > 0) { - return false; // Didn't finish this buffer. There's more to - // send. - } else { - this.bufferQueue.poll(); // Buffer finished. Remove it. - buffer = this.bufferQueue.peek(); - } - } - return true; - } - } - - /** - * Socket channel. - * - * @return the socket channel - */ - public SocketChannel socketChannel() { - return this.socketChannel; - } - - // PRIVATE INSTANCE METHODS //////////////////////////////////////////////// - /** - * Recieve frame. - * - * @throws UnsupportedEncodingException the unsupported encoding exception - */ - private void recieveFrame() throws UnsupportedEncodingException { - byte newestByte = this.buffer.get(); - - if (newestByte == START_OF_FRAME) { // Beginning of Frame - this.currentFrame = null; - - } else if (newestByte == END_OF_FRAME) { // End of Frame - String textFrame = null; - // currentFrame will be null if END_OF_FRAME was send directly after - // START_OF_FRAME, thus we will send 'null' as the sent message. - if (this.currentFrame != null) { - textFrame = new String(this.currentFrame.array(), UTF8_CHARSET - .toString()); - } - this.wsl.onMessage(this, textFrame); - - } else { // Regular frame data, add to current frame buffer - ByteBuffer frame = ByteBuffer - .allocate((this.currentFrame != null ? this.currentFrame - .capacity() : 0) - + this.buffer.capacity()); - if (this.currentFrame != null) { - this.currentFrame.rewind(); - frame.put(this.currentFrame); - } - frame.put(newestByte); - this.currentFrame = frame; - } - } - - /** - * Recieve handshake. - * - * @throws IOException Signals that an I/O exception has occurred. - * @throws NoSuchAlgorithmException the no such algorithm exception - */ - private void recieveHandshake() throws IOException, - NoSuchAlgorithmException { - ByteBuffer ch = ByteBuffer - .allocate((this.remoteHandshake != null ? this.remoteHandshake - .capacity() : 0) - + this.buffer.capacity()); - if (this.remoteHandshake != null) { - this.remoteHandshake.rewind(); - ch.put(this.remoteHandshake); - } - ch.put(this.buffer); - this.remoteHandshake = ch; - byte[] h = this.remoteHandshake.array(); - // If the ByteBuffer contains 16 random bytes, and ends with - // 0x0D 0x0A 0x0D 0x0A (or two CRLFs), then the client - // handshake is complete for Draft 76 Client. - if ((h.length >= 20 && h[h.length - 20] == CR && h[h.length - 19] == LF - && h[h.length - 18] == CR && h[h.length - 17] == LF)) { - completeHandshake(new byte[] { h[h.length - 16], h[h.length - 15], - h[h.length - 14], h[h.length - 13], h[h.length - 12], - h[h.length - 11], h[h.length - 10], h[h.length - 9], - h[h.length - 8], h[h.length - 7], h[h.length - 6], - h[h.length - 5], h[h.length - 4], h[h.length - 3], - h[h.length - 2], h[h.length - 1] }); - - // If the ByteBuffer contains 8 random bytes,ends with - // 0x0D 0x0A 0x0D 0x0A (or two CRLFs), and the response - // contains Sec-WebSocket-Key1 then the client - // handshake is complete for Draft 76 Server. - } else if ((h.length >= 12 && h[h.length - 12] == CR - && h[h.length - 11] == LF && h[h.length - 10] == CR && h[h.length - 9] == LF) - && new String(this.remoteHandshake.array(), UTF8_CHARSET) - .contains("Sec-WebSocket-Key1")) {// ************************ - completeHandshake(new byte[] { h[h.length - 8], h[h.length - 7], - h[h.length - 6], h[h.length - 5], h[h.length - 4], - h[h.length - 3], h[h.length - 2], h[h.length - 1] }); - - // Consider Draft 75, and the Flash Security Policy - // Request edge-case. - } else if ((h.length >= 4 && h[h.length - 4] == CR - && h[h.length - 3] == LF && h[h.length - 2] == CR && h[h.length - 1] == LF) - && !(new String(this.remoteHandshake.array(), UTF8_CHARSET) - .contains("Sec")) - || (h.length == 23 && h[h.length - 1] == 0)) { - completeHandshake(null); - } - } - - /** - * Complete handshake. - * - * @param handShakeBody the hand shake body - * @throws IOException Signals that an I/O exception has occurred. - * @throws NoSuchAlgorithmException the no such algorithm exception - */ - private void completeHandshake(byte[] handShakeBody) throws IOException, - NoSuchAlgorithmException { - byte[] handshakeBytes = this.remoteHandshake.array(); - // *************************************** - String handshake = new String(handshakeBytes, UTF8_CHARSET); - this.handshakeComplete = true; - if (this.wsl.onHandshakeRecieved(this, handshake, handShakeBody)) { - this.wsl.onOpen(this); - } else { - close(); - } - } - -} diff --git a/src/com/strumsoft/websocket/phonegap/WebSocket.java b/src/com/strumsoft/websocket/phonegap/WebSocket.java index c2fcff1..eb0d307 100755 --- a/src/com/strumsoft/websocket/phonegap/WebSocket.java +++ b/src/com/strumsoft/websocket/phonegap/WebSocket.java @@ -1,136 +1,607 @@ /* - * Copyright (c) 2010 STRUMSOFT (http://www.strumsoft.com) + * Copyright (c) 2010 Nathan Rajlich (https://github.com/TooTallNate) + * Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles) * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. * */ package com.strumsoft.websocket.phonegap; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; import java.net.URI; -import java.net.URISyntaxException; - -import com.strumsoft.util.Logger; -import com.strumsoft.websocket.WebSocketListener; +import java.nio.ByteBuffer; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Iterator; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import android.webkit.WebView; /** - * The Class WebSocket. - * + * The WebSocket is an implementation of WebSocket Client API, and + * expects a valid "ws://" URI to connect to. When connected, an instance + * recieves important events related to the life of the connection, like + * onOpen, onClose, onError and + * onMessage. An instance can send messages to the server via the + * send method. + * * @author Animesh Kumar */ -public class WebSocket extends WebSocketListener { +public class WebSocket implements Runnable { - /** The app view. */ - private WebView appView; + /** + * Enum for WebSocket Draft + */ + public enum Draft { + DRAFT75, DRAFT76 + } - /** The web socket id. */ - private String webSocketId; + ////////////////// CONSTANT + /** + * An empty string + */ + private static String BLANK_MESSAGE = ""; + /** + * The javascript method name for onOpen event. + */ + private static String EVENT_ON_OPEN = "onopen"; + /** + * The javascript method name for onMessage event. + */ + private static String EVENT_ON_MESSAGE = "onmessage"; + /** + * The javascript method name for onClose event. + */ + private static String EVENT_ON_CLOSE = "onclose"; + /** + * The javascript method name for onError event. + */ + private static String EVENT_ON_ERROR = "onerror"; + /** + * The default port of WebSockets, as defined in the spec. + */ + public static final int DEFAULT_PORT = 80; + /** + * The WebSocket protocol expects UTF-8 encoded bytes. + */ + public static final String UTF8_CHARSET = "UTF-8"; + /** + * The byte representing Carriage Return, or \r + */ + public static final byte DATA_CR = (byte) 0x0D; + /** + * The byte representing Line Feed, or \n + */ + public static final byte DATA_LF = (byte) 0x0A; + /** + * The byte representing the beginning of a WebSocket text frame. + */ + public static final byte DATA_START_OF_FRAME = (byte) 0x00; + /** + * The byte representing the end of a WebSocket text frame. + */ + public static final byte DATA_END_OF_FRAME = (byte) 0xFF; + + ////////////////// INSTANCE Variables + /** + * The WebView instance from Phonegap DroidGap + */ + private WebView appView; + /** + * The unique id for this instance (helps to bind this to javascript events) + */ + private String id; + /** + * The URI this client is supposed to connect to. + */ + private URI uri; + /** + * The port of the websocket server + */ + private int port; + /** + * The Draft of the WebSocket protocol the Client is adhering to. + */ + private Draft draft; + /** + * The SocketChannel instance to use for this server connection. + * This is used to read and write data to. + */ + private SocketChannel socketChannel; + /** + * The 'Selector' used to get event keys from the underlying socket. + */ + private Selector selector; + /** + * Keeps track of whether or not the client thread should continue running. + */ + private boolean running; + /** + * Internally used to determine whether to recieve data as part of the + * remote handshake, or as part of a text frame. + */ + private boolean handshakeComplete; + /** + * The 1-byte buffer reused throughout the WebSocket connection to read + * data. + */ + private ByteBuffer buffer; + /** + * The bytes that make up the remote handshake. + */ + private ByteBuffer remoteHandshake; + /** + * The bytes that make up the current text frame being read. + */ + private ByteBuffer currentFrame; + /** + * Queue of buffers that need to be sent to the client. + */ + private BlockingQueue bufferQueue; + /** + * Lock object to ensure that data is sent from the bufferQueue in the + * proper order + */ + private Object bufferQueueMutex = new Object(); + /** + * Number 1 used in handshake + */ + private int number1 = 0; + /** + * Number 2 used in handshake + */ + private int number2 = 0; + /** + * Key3 used in handshake + */ + private byte[] key3 = null; /** - * Deafualt method which invokes super class method. + * Constructor. + * + * Note: this is protected because it's supposed to be instantiated from + * {@link WebSocketFactory} only. * - * Note: constructor is protected because WebSocketFactory is supposed - * to generate instances. - * - * @param appView the app view - * @param uri the uri - * @throws URISyntaxException the uRI syntax exception - */ - protected WebSocket(WebView appView, URI uri) throws URISyntaxException { - super(uri); + * @param appView + * {@link android.webkit.WebView} + * @param uri + * websocket server {@link URI} + * @param draft + * websocket server {@link Draft} implementation (75/76) + * @param id + * unique id for this instance + */ + protected WebSocket(WebView appView, URI uri, Draft draft, String id) { this.appView = appView; - this.webSocketId = getClass().getSimpleName() + "." + hashCode(); + this.uri = uri; + this.draft = draft; + + // port + port = uri.getPort(); + if (port == -1) { + port = DEFAULT_PORT; + } + + // Id + this.id = id; + + this.bufferQueue = new LinkedBlockingQueue(); + this.handshakeComplete = false; + this.remoteHandshake = this.currentFrame = null; + this.buffer = ByteBuffer.allocate(1); } - /* (non-Javadoc) - * @see com.strumsoft.websocket.WebSocketListener#onClose() + // ////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////// WEB SOCKET API Methods + // /////////////////////////////////// + // ////////////////////////////////////////////////////////////////////////////////////// + /** + * Starts a new Thread and connects to server */ - @Override - public void onClose() { - appView.loadUrl(buildLoadData("close", "")); + public void connect() { + this.running = true; + (new Thread(this)).start(); } - /* (non-Javadoc) - * @see com.strumsoft.websocket.WebSocketListener#onReconnect() + public void run() { + try { + _connect(); + } catch (IOException e) { + this.onError(e); + } + } + + /** + * Closes connection with server */ - @Override - public void onReconnect() { - // TODO: Phase-II - //appView.loadUrl(buildLoadData("reconnect", "")); + public void close() { + // close socket channel + try { + this.socketChannel.close(); + } catch (IOException e) { + this.onError(e); + } + this.running = false; + selector.wakeup(); + + // fire onClose method + this.onClose(); } - /* (non-Javadoc) - * @see com.strumsoft.websocket.WebSocketListener#onMessage(java.lang.String) + /** + * Sends text to server + * + * @param text + * String to send to server */ - @Override - public void onMessage(String message) { - appView.loadUrl(buildLoadData("message", message)); + public void send(String text) { + try { + _send(text); + } catch (IOException e) { + this.onError(e); + } } - /* (non-Javadoc) - * @see com.strumsoft.websocket.WebSocketListener#onOpen() + /** + * Called when an entire text frame has been recieved. + * + * @param msg + * Message from websocket server */ - @Override + public void onMessage(String msg) { + appView.loadUrl(buildJavaScriptData(EVENT_ON_MESSAGE, msg)); + } + public void onOpen() { - appView.loadUrl(buildLoadData("open", "")); + appView.loadUrl(buildJavaScriptData(EVENT_ON_OPEN, BLANK_MESSAGE)); + } + + public void onClose() { + appView.loadUrl(buildJavaScriptData(EVENT_ON_CLOSE, BLANK_MESSAGE)); + } + + public void onError(Throwable t) { + String msg = t.getMessage(); + appView.loadUrl(buildJavaScriptData(EVENT_ON_ERROR, msg)); + } + + public String getId() { + return id; } /** - * builds data to be pushed to attached WebView so as to be - * passed to Javascript. - * - * @param _event event type (open, close, message) - * @param _data data - * @return the string + * Builds text for javascript engine to invoke proper event method with proper data. + * + * @param event websocket event (onOpen, onMessage etc.) + * @param msg Text message received from websocket server + * @return */ - private String buildLoadData(String _event, String _data) { - String _d = "javascript:WebSocket.on" + _event + "(" + - "{" - + "\"_target\":\"" + webSocketId + "\"," + - "\"_data\":'" + _data + "'" + - "}" + - ")"; - Logger.log(_d); + private String buildJavaScriptData(String event, String msg) { + String _d = "javascript:WebSocket." + event + "(" + "{" + "\"_target\":\"" + id + "\"," + + "\"_data\":'" + msg + "'" + "}" + ")"; return _d; } - /** - * Gets the web socket id. - * - * @return the web socket id - */ - public String getWebSocketId() { - return this.webSocketId; + // ////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////// WEB SOCKET Internal Methods + // ////////////////////////////// + // ////////////////////////////////////////////////////////////////////////////////////// + + private boolean _send(String text) throws IOException { + if (!this.handshakeComplete) { + throw new NotYetConnectedException(); + } + if (text == null) { + throw new NullPointerException("Cannot send 'null' data to a WebSocket."); + } + + // Get 'text' into a WebSocket "frame" of bytes + byte[] textBytes = text.getBytes(UTF8_CHARSET.toString()); + ByteBuffer b = ByteBuffer.allocate(textBytes.length + 2); + b.put(DATA_START_OF_FRAME); + b.put(textBytes); + b.put(DATA_END_OF_FRAME); + b.rewind(); + + // See if we have any backlog that needs to be sent first + if (_write()) { + // Write the ByteBuffer to the socket + this.socketChannel.write(b); + } + + // If we didn't get it all sent, add it to the buffer of buffers + if (b.remaining() > 0) { + if (!this.bufferQueue.offer(b)) { + throw new IOException("Buffers are full, message could not be sent to" + + this.socketChannel.socket().getRemoteSocketAddress()); + } + return false; + } + return true; } - /* (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((appView == null) ? 0 : appView.hashCode()); - return result; + // actual connection logic + private void _connect() throws IOException { + + socketChannel = SocketChannel.open(); + socketChannel.configureBlocking(false); + socketChannel.connect(new InetSocketAddress(uri.getHost(), port)); + + // More info: + // http://groups.google.com/group/android-developers/browse_thread/thread/45a8b53e9bf60d82 + // http://stackoverflow.com/questions/2879455/android-2-2-and-bad-address-family-on-socket-connect + System.setProperty("java.net.preferIPv4Stack", "true"); + System.setProperty("java.net.preferIPv6Addresses", "false"); + + selector = Selector.open(); + socketChannel.register(selector, SelectionKey.OP_CONNECT); + + // Continuous loop that is only supposed to end when "close" is called. + while (this.running) { + selector.select(); + Set keys = selector.selectedKeys(); + Iterator i = keys.iterator(); + + while (i.hasNext()) { + SelectionKey key = i.next(); + i.remove(); + if (key.isConnectable()) { + if (socketChannel.isConnectionPending()) { + socketChannel.finishConnect(); + } + socketChannel.register(selector, SelectionKey.OP_READ); + _writeHandshake(); + } + if (key.isReadable()) { + try { + _read(); + } catch (NoSuchAlgorithmException nsa) { + this.onError(nsa); + } + } + } + } + } + + private void _writeHandshake() throws IOException { + String path = this.uri.getPath(); + if (path.indexOf("/") != 0) { + path = "/" + path; + } + + String host = uri.getHost() + (port != DEFAULT_PORT ? ":" + port : ""); + String origin = "*"; // TODO: Make 'origin' configurable + String request = "GET " + path + " HTTP/1.1\r\n" + "Upgrade: WebSocket\r\n" + + "Connection: Upgrade\r\n" + "Host: " + host + "\r\n" + "Origin: " + origin + + "\r\n"; + + // Add randon keys for Draft76 + if (this.draft == Draft.DRAFT76) { + request += "Sec-WebSocket-Key1: " + this._randomKey() + "\r\n"; + request += "Sec-WebSocket-Key2: " + this._randomKey() + "\r\n"; + this.key3 = new byte[8]; + (new Random()).nextBytes(this.key3); + } + + request += "\r\n"; + _write(request.getBytes(UTF8_CHARSET)); + if (this.key3 != null) { + _write(this.key3); + } + + } + + private boolean _write() throws IOException { + synchronized (this.bufferQueueMutex) { + ByteBuffer buffer = this.bufferQueue.peek(); + while (buffer != null) { + this.socketChannel.write(buffer); + if (buffer.remaining() > 0) { + return false; // Didn't finish this buffer. There's more to + // send. + } else { + this.bufferQueue.poll(); // Buffer finished. Remove it. + buffer = this.bufferQueue.peek(); + } + } + return true; + } + } + + private void _write(byte[] bytes) throws IOException { + this.socketChannel.write(ByteBuffer.wrap(bytes)); + } + + private void _read() throws IOException, NoSuchAlgorithmException { + this.buffer.rewind(); + + int bytesRead = -1; + try { + bytesRead = this.socketChannel.read(this.buffer); + } catch (Exception ex) { + } + + if (bytesRead == -1) { + close(); + } else if (bytesRead > 0) { + this.buffer.rewind(); + + if (!this.handshakeComplete) { + _readHandshake(); + } else { + _readFrame(); + } + } + } + + private void _readFrame() throws UnsupportedEncodingException { + byte newestByte = this.buffer.get(); + + if (newestByte == DATA_START_OF_FRAME) { // Beginning of Frame + this.currentFrame = null; + + } else if (newestByte == DATA_END_OF_FRAME) { // End of Frame + String textFrame = null; + // currentFrame will be null if END_OF_FRAME was send directly after + // START_OF_FRAME, thus we will send 'null' as the sent message. + if (this.currentFrame != null) { + textFrame = new String(this.currentFrame.array(), UTF8_CHARSET.toString()); + } + // fire onMessage method + this.onMessage(textFrame); + + } else { // Regular frame data, add to current frame buffer + ByteBuffer frame = ByteBuffer.allocate((this.currentFrame != null ? this.currentFrame + .capacity() : 0) + + this.buffer.capacity()); + if (this.currentFrame != null) { + this.currentFrame.rewind(); + frame.put(this.currentFrame); + } + frame.put(newestByte); + this.currentFrame = frame; + } + } + + private void _readHandshake() throws IOException, NoSuchAlgorithmException { + ByteBuffer ch = ByteBuffer.allocate((this.remoteHandshake != null ? this.remoteHandshake + .capacity() : 0) + + this.buffer.capacity()); + if (this.remoteHandshake != null) { + this.remoteHandshake.rewind(); + ch.put(this.remoteHandshake); + } + ch.put(this.buffer); + this.remoteHandshake = ch; + byte[] h = this.remoteHandshake.array(); + // If the ByteBuffer contains 16 random bytes, and ends with + // 0x0D 0x0A 0x0D 0x0A (or two CRLFs), then the client + // handshake is complete for Draft 76 Client. + if ((h.length >= 20 && h[h.length - 20] == DATA_CR && h[h.length - 19] == DATA_LF + && h[h.length - 18] == DATA_CR && h[h.length - 17] == DATA_LF)) { + _readHandshake(new byte[] { h[h.length - 16], h[h.length - 15], h[h.length - 14], + h[h.length - 13], h[h.length - 12], h[h.length - 11], h[h.length - 10], + h[h.length - 9], h[h.length - 8], h[h.length - 7], h[h.length - 6], + h[h.length - 5], h[h.length - 4], h[h.length - 3], h[h.length - 2], + h[h.length - 1] }); + + // If the ByteBuffer contains 8 random bytes,ends with + // 0x0D 0x0A 0x0D 0x0A (or two CRLFs), and the response + // contains Sec-WebSocket-Key1 then the client + // handshake is complete for Draft 76 Server. + } else if ((h.length >= 12 && h[h.length - 12] == DATA_CR && h[h.length - 11] == DATA_LF + && h[h.length - 10] == DATA_CR && h[h.length - 9] == DATA_LF) + && new String(this.remoteHandshake.array(), UTF8_CHARSET) + .contains("Sec-WebSocket-Key1")) {// ************************ + _readHandshake(new byte[] { h[h.length - 8], h[h.length - 7], h[h.length - 6], + h[h.length - 5], h[h.length - 4], h[h.length - 3], h[h.length - 2], + h[h.length - 1] }); + + // Consider Draft 75, and the Flash Security Policy + // Request edge-case. + } else if ((h.length >= 4 && h[h.length - 4] == DATA_CR && h[h.length - 3] == DATA_LF + && h[h.length - 2] == DATA_CR && h[h.length - 1] == DATA_LF) + && !(new String(this.remoteHandshake.array(), UTF8_CHARSET).contains("Sec")) + || (h.length == 23 && h[h.length - 1] == 0)) { + _readHandshake(null); + } + } + + private void _readHandshake(byte[] handShakeBody) throws IOException, NoSuchAlgorithmException { + // byte[] handshakeBytes = this.remoteHandshake.array(); + // String handshake = new String(handshakeBytes, UTF8_CHARSET); + // TODO: Do some parsing of the returned handshake, and close connection + // in received anything unexpected! + + this.handshakeComplete = true; + boolean isConnectionReady = true; + + if (this.draft == WebSocket.Draft.DRAFT76) { + if (handShakeBody == null) { + isConnectionReady = true; + } + byte[] challenge = new byte[] { (byte) (this.number1 >> 24), + (byte) ((this.number1 << 8) >> 24), (byte) ((this.number1 << 16) >> 24), + (byte) ((this.number1 << 24) >> 24), (byte) (this.number2 >> 24), + (byte) ((this.number2 << 8) >> 24), (byte) ((this.number2 << 16) >> 24), + (byte) ((this.number2 << 24) >> 24), this.key3[0], this.key3[1], this.key3[2], + this.key3[3], this.key3[4], this.key3[5], this.key3[6], this.key3[7] }; + MessageDigest md5 = MessageDigest.getInstance("MD5"); + byte[] expected = md5.digest(challenge); + for (int i = 0; i < handShakeBody.length; i++) { + if (expected[i] != handShakeBody[i]) { + isConnectionReady = true; + } + } + } + + if (isConnectionReady) { + // fire onOpen method + this.onOpen(); + } else { + close(); + } + } + + private String _randomKey() { + Random r = new Random(); + long maxNumber = 4294967295L; + long spaces = r.nextInt(12) + 1; + int max = new Long(maxNumber / spaces).intValue(); + max = Math.abs(max); + int number = r.nextInt(max) + 1; + if (this.number1 == 0) { + this.number1 = number; + } else { + this.number2 = number; + } + long product = number * spaces; + String key = Long.toString(product); + int numChars = r.nextInt(12); + for (int i = 0; i < numChars; i++) { + int position = r.nextInt(key.length()); + position = Math.abs(position); + char randChar = (char) (r.nextInt(95) + 33); + // exclude numbers here + if (randChar >= 48 && randChar <= 57) { + randChar -= 15; + } + key = new StringBuilder(key).insert(position, randChar).toString(); + } + for (int i = 0; i < spaces; i++) { + int position = r.nextInt(key.length() - 1) + 1; + position = Math.abs(position); + key = new StringBuilder(key).insert(position, "\u0020").toString(); + } + return key; } } + \ No newline at end of file diff --git a/src/com/strumsoft/websocket/phonegap/WebSocketFactory.java b/src/com/strumsoft/websocket/phonegap/WebSocketFactory.java index 4cd9650..ba7edd3 100755 Binary files a/src/com/strumsoft/websocket/phonegap/WebSocketFactory.java and b/src/com/strumsoft/websocket/phonegap/WebSocketFactory.java differ