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