summaryrefslogtreecommitdiff
path: root/plugins/cordova-plugin-websocket/src/android/org/eclipse
diff options
context:
space:
mode:
authorArjun Roychowdhury <pliablepixels@gmail.com>2015-10-07 15:28:13 -0400
committerArjun Roychowdhury <pliablepixels@gmail.com>2015-10-07 15:28:13 -0400
commit41bef0dc67b4ec1437bedfee9efafd0aaeaedcdb (patch)
tree43974514aeb51b94549a5b3ae55ce2b423fd4b80 /plugins/cordova-plugin-websocket/src/android/org/eclipse
parent40fc4fc94ee0523aea1a36f8f6cf3acb4af0b599 (diff)
websockets for android
Diffstat (limited to 'plugins/cordova-plugin-websocket/src/android/org/eclipse')
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpException.java49
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpHeaderValues.java75
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpHeaders.java238
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpMethods.java63
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpParser.java1033
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpVersions.java47
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/Parser.java37
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractBuffer.java557
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractBuffers.java148
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractConnection.java67
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AsyncEndPoint.java42
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Buffer.java313
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/BufferCache.java130
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/BufferUtil.java68
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Buffers.java37
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ByteArrayBuffer.java325
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ConnectedEndPoint.java25
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Connection.java66
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/EndPoint.java131
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/EofException.java47
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/SimpleBuffers.java102
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ThreadLocalBuffers.java120
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/View.java232
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/AsyncConnection.java28
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/ChannelEndPoint.java478
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/DirectNIOBuffer.java226
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java43
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/NIOBuffer.java34
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java802
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SelectorManager.java891
-rwxr-xr-xplugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SslConnection.java775
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/BlockingArrayQueue.java482
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ConcurrentHashSet.java126
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/IO.java122
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/QuotedStringTokenizer.java312
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/StringMap.java603
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/TypeUtil.java48
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/AbstractLifeCycle.java193
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/AggregateLifeCycle.java250
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/Dumpable.java26
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/LifeCycle.java107
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/AndroidLogger.java110
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/Log.java151
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/Logger.java113
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/resource/Resource.java119
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/resource/URLResource.java130
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/CertificateUtils.java93
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/CertificateValidator.java228
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/Password.java84
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java107
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ssl/SslContextFactory.java620
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/QueuedThreadPool.java385
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/ThreadPool.java42
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/Timeout.java282
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/Extension.java31
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/MaskGen.java24
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/PerMessageDeflateExtension.java291
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/RandomMaskGen.java45
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocket.java273
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketBuffer.java244
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketBuffers.java57
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketClient.java611
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketClientFactory.java634
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketConnection.java36
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java887
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketGenerator.java33
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java319
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketParser.java49
-rw-r--r--plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketParserRFC6455.java392
69 files changed, 15888 insertions, 0 deletions
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpException.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpException.java
new file mode 100644
index 00000000..4ae60c02
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpException.java
@@ -0,0 +1,49 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+
+@SuppressWarnings("serial")
+public class HttpException extends IOException
+{
+ int _status;
+ String _reason;
+
+ /* ------------------------------------------------------------ */
+ public HttpException(int status)
+ {
+ _status=status;
+ _reason=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpException(int status,String reason)
+ {
+ _status=status;
+ _reason=reason;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return ("HttpException("+_status+","+_reason+","+super.getCause()+")");
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpHeaderValues.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpHeaderValues.java
new file mode 100644
index 00000000..c8d5ed58
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpHeaderValues.java
@@ -0,0 +1,75 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+
+/**
+ * Cached HTTP Header values.
+ * This class caches the conversion of common HTTP Header values to and from {@link ByteArrayBuffer} instances.
+ * The resource "/org/eclipse/jetty/useragents" is checked for a list of common user agents, so that repeated
+ * creation of strings for these agents can be avoided.
+ *
+ *
+ */
+public class HttpHeaderValues extends BufferCache
+{
+ public final static String
+ CLOSE="close",
+ CHUNKED="chunked",
+ GZIP="gzip",
+ IDENTITY="identity",
+ KEEP_ALIVE="keep-alive",
+ CONTINUE="100-continue",
+ PROCESSING="102-processing",
+ TE="TE",
+ BYTES="bytes",
+ NO_CACHE="no-cache",
+ UPGRADE="Upgrade";
+
+ public final static int
+ CLOSE_ORDINAL=1,
+ CHUNKED_ORDINAL=2,
+ GZIP_ORDINAL=3,
+ IDENTITY_ORDINAL=4,
+ KEEP_ALIVE_ORDINAL=5,
+ CONTINUE_ORDINAL=6,
+ PROCESSING_ORDINAL=7,
+ TE_ORDINAL=8,
+ BYTES_ORDINAL=9,
+ NO_CACHE_ORDINAL=10,
+ UPGRADE_ORDINAL=11;
+
+ public final static HttpHeaderValues CACHE= new HttpHeaderValues();
+
+ public final static Buffer
+ CLOSE_BUFFER=CACHE.add(CLOSE,CLOSE_ORDINAL),
+ CHUNKED_BUFFER=CACHE.add(CHUNKED,CHUNKED_ORDINAL),
+ GZIP_BUFFER=CACHE.add(GZIP,GZIP_ORDINAL),
+ IDENTITY_BUFFER=CACHE.add(IDENTITY,IDENTITY_ORDINAL),
+ KEEP_ALIVE_BUFFER=CACHE.add(KEEP_ALIVE,KEEP_ALIVE_ORDINAL),
+ CONTINUE_BUFFER=CACHE.add(CONTINUE, CONTINUE_ORDINAL),
+ PROCESSING_BUFFER=CACHE.add(PROCESSING, PROCESSING_ORDINAL),
+ TE_BUFFER=CACHE.add(TE,TE_ORDINAL),
+ BYTES_BUFFER=CACHE.add(BYTES,BYTES_ORDINAL),
+ NO_CACHE_BUFFER=CACHE.add(NO_CACHE,NO_CACHE_ORDINAL),
+ UPGRADE_BUFFER=CACHE.add(UPGRADE,UPGRADE_ORDINAL);
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpHeaders.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpHeaders.java
new file mode 100644
index 00000000..87f5d1e3
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpHeaders.java
@@ -0,0 +1,238 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public class HttpHeaders extends BufferCache
+{
+ /* ------------------------------------------------------------ */
+ /** General Fields.
+ */
+ public final static String
+ CONNECTION= "Connection",
+ CACHE_CONTROL= "Cache-Control",
+ DATE= "Date",
+ PRAGMA= "Pragma",
+ PROXY_CONNECTION = "Proxy-Connection",
+ TRAILER= "Trailer",
+ TRANSFER_ENCODING= "Transfer-Encoding",
+ UPGRADE= "Upgrade",
+ VIA= "Via",
+ WARNING= "Warning",
+ NEGOTIATE= "Negotiate";
+
+ /* ------------------------------------------------------------ */
+ /** Entity Fields.
+ */
+ public final static String ALLOW= "Allow",
+ CONTENT_ENCODING= "Content-Encoding",
+ CONTENT_LANGUAGE= "Content-Language",
+ CONTENT_LENGTH= "Content-Length",
+ CONTENT_LOCATION= "Content-Location",
+ CONTENT_MD5= "Content-MD5",
+ CONTENT_RANGE= "Content-Range",
+ CONTENT_TYPE= "Content-Type",
+ EXPIRES= "Expires",
+ LAST_MODIFIED= "Last-Modified";
+
+ /* ------------------------------------------------------------ */
+ /** Request Fields.
+ */
+ public final static String ACCEPT= "Accept",
+ ACCEPT_CHARSET= "Accept-Charset",
+ ACCEPT_ENCODING= "Accept-Encoding",
+ ACCEPT_LANGUAGE= "Accept-Language",
+ AUTHORIZATION= "Authorization",
+ EXPECT= "Expect",
+ FORWARDED= "Forwarded",
+ FROM= "From",
+ HOST= "Host",
+ IF_MATCH= "If-Match",
+ IF_MODIFIED_SINCE= "If-Modified-Since",
+ IF_NONE_MATCH= "If-None-Match",
+ IF_RANGE= "If-Range",
+ IF_UNMODIFIED_SINCE= "If-Unmodified-Since",
+ KEEP_ALIVE= "Keep-Alive",
+ MAX_FORWARDS= "Max-Forwards",
+ PROXY_AUTHORIZATION= "Proxy-Authorization",
+ RANGE= "Range",
+ REQUEST_RANGE= "Request-Range",
+ REFERER= "Referer",
+ TE= "TE",
+ USER_AGENT= "User-Agent",
+ X_FORWARDED_FOR= "X-Forwarded-For",
+ X_FORWARDED_PROTO= "X-Forwarded-Proto",
+ X_FORWARDED_SERVER= "X-Forwarded-Server",
+ X_FORWARDED_HOST= "X-Forwarded-Host";
+
+ /* ------------------------------------------------------------ */
+ /** Response Fields.
+ */
+ public final static String ACCEPT_RANGES= "Accept-Ranges",
+ AGE= "Age",
+ ETAG= "ETag",
+ LOCATION= "Location",
+ PROXY_AUTHENTICATE= "Proxy-Authenticate",
+ RETRY_AFTER= "Retry-After",
+ SERVER= "Server",
+ SERVLET_ENGINE= "Servlet-Engine",
+ VARY= "Vary",
+ WWW_AUTHENTICATE= "WWW-Authenticate";
+
+ /* ------------------------------------------------------------ */
+ /** Other Fields.
+ */
+ public final static String COOKIE= "Cookie",
+ SET_COOKIE= "Set-Cookie",
+ SET_COOKIE2= "Set-Cookie2",
+ MIME_VERSION= "MIME-Version",
+ IDENTITY= "identity";
+
+ public final static int CONNECTION_ORDINAL= 1,
+ DATE_ORDINAL= 2,
+ PRAGMA_ORDINAL= 3,
+ TRAILER_ORDINAL= 4,
+ TRANSFER_ENCODING_ORDINAL= 5,
+ UPGRADE_ORDINAL= 6,
+ VIA_ORDINAL= 7,
+ WARNING_ORDINAL= 8,
+ ALLOW_ORDINAL= 9,
+ CONTENT_ENCODING_ORDINAL= 10,
+ CONTENT_LANGUAGE_ORDINAL= 11,
+ CONTENT_LENGTH_ORDINAL= 12,
+ CONTENT_LOCATION_ORDINAL= 13,
+ CONTENT_MD5_ORDINAL= 14,
+ CONTENT_RANGE_ORDINAL= 15,
+ CONTENT_TYPE_ORDINAL= 16,
+ EXPIRES_ORDINAL= 17,
+ LAST_MODIFIED_ORDINAL= 18,
+ ACCEPT_ORDINAL= 19,
+ ACCEPT_CHARSET_ORDINAL= 20,
+ ACCEPT_ENCODING_ORDINAL= 21,
+ ACCEPT_LANGUAGE_ORDINAL= 22,
+ AUTHORIZATION_ORDINAL= 23,
+ EXPECT_ORDINAL= 24,
+ FORWARDED_ORDINAL= 25,
+ FROM_ORDINAL= 26,
+ HOST_ORDINAL= 27,
+ IF_MATCH_ORDINAL= 28,
+ IF_MODIFIED_SINCE_ORDINAL= 29,
+ IF_NONE_MATCH_ORDINAL= 30,
+ IF_RANGE_ORDINAL= 31,
+ IF_UNMODIFIED_SINCE_ORDINAL= 32,
+ KEEP_ALIVE_ORDINAL= 33,
+ MAX_FORWARDS_ORDINAL= 34,
+ PROXY_AUTHORIZATION_ORDINAL= 35,
+ RANGE_ORDINAL= 36,
+ REQUEST_RANGE_ORDINAL= 37,
+ REFERER_ORDINAL= 38,
+ TE_ORDINAL= 39,
+ USER_AGENT_ORDINAL= 40,
+ X_FORWARDED_FOR_ORDINAL= 41,
+ ACCEPT_RANGES_ORDINAL= 42,
+ AGE_ORDINAL= 43,
+ ETAG_ORDINAL= 44,
+ LOCATION_ORDINAL= 45,
+ PROXY_AUTHENTICATE_ORDINAL= 46,
+ RETRY_AFTER_ORDINAL= 47,
+ SERVER_ORDINAL= 48,
+ SERVLET_ENGINE_ORDINAL= 49,
+ VARY_ORDINAL= 50,
+ WWW_AUTHENTICATE_ORDINAL= 51,
+ COOKIE_ORDINAL= 52,
+ SET_COOKIE_ORDINAL= 53,
+ SET_COOKIE2_ORDINAL= 54,
+ MIME_VERSION_ORDINAL= 55,
+ IDENTITY_ORDINAL= 56,
+ CACHE_CONTROL_ORDINAL=57,
+ PROXY_CONNECTION_ORDINAL=58,
+ X_FORWARDED_PROTO_ORDINAL=59,
+ X_FORWARDED_SERVER_ORDINAL=60,
+ X_FORWARDED_HOST_ORDINAL=61;
+
+ public final static HttpHeaders CACHE= new HttpHeaders();
+
+ public final static Buffer
+ HOST_BUFFER=CACHE.add(HOST,HOST_ORDINAL),
+ ACCEPT_BUFFER=CACHE.add(ACCEPT,ACCEPT_ORDINAL),
+ ACCEPT_CHARSET_BUFFER=CACHE.add(ACCEPT_CHARSET,ACCEPT_CHARSET_ORDINAL),
+ ACCEPT_ENCODING_BUFFER=CACHE.add(ACCEPT_ENCODING,ACCEPT_ENCODING_ORDINAL),
+ ACCEPT_LANGUAGE_BUFFER=CACHE.add(ACCEPT_LANGUAGE,ACCEPT_LANGUAGE_ORDINAL),
+ CONTENT_LENGTH_BUFFER=CACHE.add(CONTENT_LENGTH,CONTENT_LENGTH_ORDINAL),
+ CONNECTION_BUFFER=CACHE.add(CONNECTION,CONNECTION_ORDINAL),
+ CACHE_CONTROL_BUFFER=CACHE.add(CACHE_CONTROL,CACHE_CONTROL_ORDINAL),
+ DATE_BUFFER=CACHE.add(DATE,DATE_ORDINAL),
+ PRAGMA_BUFFER=CACHE.add(PRAGMA,PRAGMA_ORDINAL),
+ TRAILER_BUFFER=CACHE.add(TRAILER,TRAILER_ORDINAL),
+ TRANSFER_ENCODING_BUFFER=CACHE.add(TRANSFER_ENCODING,TRANSFER_ENCODING_ORDINAL),
+ UPGRADE_BUFFER=CACHE.add(UPGRADE,UPGRADE_ORDINAL),
+ VIA_BUFFER=CACHE.add(VIA,VIA_ORDINAL),
+ WARNING_BUFFER=CACHE.add(WARNING,WARNING_ORDINAL),
+ ALLOW_BUFFER=CACHE.add(ALLOW,ALLOW_ORDINAL),
+ CONTENT_ENCODING_BUFFER=CACHE.add(CONTENT_ENCODING,CONTENT_ENCODING_ORDINAL),
+ CONTENT_LANGUAGE_BUFFER=CACHE.add(CONTENT_LANGUAGE,CONTENT_LANGUAGE_ORDINAL),
+ CONTENT_LOCATION_BUFFER=CACHE.add(CONTENT_LOCATION,CONTENT_LOCATION_ORDINAL),
+ CONTENT_MD5_BUFFER=CACHE.add(CONTENT_MD5,CONTENT_MD5_ORDINAL),
+ CONTENT_RANGE_BUFFER=CACHE.add(CONTENT_RANGE,CONTENT_RANGE_ORDINAL),
+ CONTENT_TYPE_BUFFER=CACHE.add(CONTENT_TYPE,CONTENT_TYPE_ORDINAL),
+ EXPIRES_BUFFER=CACHE.add(EXPIRES,EXPIRES_ORDINAL),
+ LAST_MODIFIED_BUFFER=CACHE.add(LAST_MODIFIED,LAST_MODIFIED_ORDINAL),
+ AUTHORIZATION_BUFFER=CACHE.add(AUTHORIZATION,AUTHORIZATION_ORDINAL),
+ EXPECT_BUFFER=CACHE.add(EXPECT,EXPECT_ORDINAL),
+ FORWARDED_BUFFER=CACHE.add(FORWARDED,FORWARDED_ORDINAL),
+ FROM_BUFFER=CACHE.add(FROM,FROM_ORDINAL),
+ IF_MATCH_BUFFER=CACHE.add(IF_MATCH,IF_MATCH_ORDINAL),
+ IF_MODIFIED_SINCE_BUFFER=CACHE.add(IF_MODIFIED_SINCE,IF_MODIFIED_SINCE_ORDINAL),
+ IF_NONE_MATCH_BUFFER=CACHE.add(IF_NONE_MATCH,IF_NONE_MATCH_ORDINAL),
+ IF_RANGE_BUFFER=CACHE.add(IF_RANGE,IF_RANGE_ORDINAL),
+ IF_UNMODIFIED_SINCE_BUFFER=CACHE.add(IF_UNMODIFIED_SINCE,IF_UNMODIFIED_SINCE_ORDINAL),
+ KEEP_ALIVE_BUFFER=CACHE.add(KEEP_ALIVE,KEEP_ALIVE_ORDINAL),
+ MAX_FORWARDS_BUFFER=CACHE.add(MAX_FORWARDS,MAX_FORWARDS_ORDINAL),
+ PROXY_AUTHORIZATION_BUFFER=CACHE.add(PROXY_AUTHORIZATION,PROXY_AUTHORIZATION_ORDINAL),
+ RANGE_BUFFER=CACHE.add(RANGE,RANGE_ORDINAL),
+ REQUEST_RANGE_BUFFER=CACHE.add(REQUEST_RANGE,REQUEST_RANGE_ORDINAL),
+ REFERER_BUFFER=CACHE.add(REFERER,REFERER_ORDINAL),
+ TE_BUFFER=CACHE.add(TE,TE_ORDINAL),
+ USER_AGENT_BUFFER=CACHE.add(USER_AGENT,USER_AGENT_ORDINAL),
+ X_FORWARDED_FOR_BUFFER=CACHE.add(X_FORWARDED_FOR,X_FORWARDED_FOR_ORDINAL),
+ X_FORWARDED_PROTO_BUFFER=CACHE.add(X_FORWARDED_PROTO,X_FORWARDED_PROTO_ORDINAL),
+ X_FORWARDED_SERVER_BUFFER=CACHE.add(X_FORWARDED_SERVER,X_FORWARDED_SERVER_ORDINAL),
+ X_FORWARDED_HOST_BUFFER=CACHE.add(X_FORWARDED_HOST,X_FORWARDED_HOST_ORDINAL),
+ ACCEPT_RANGES_BUFFER=CACHE.add(ACCEPT_RANGES,ACCEPT_RANGES_ORDINAL),
+ AGE_BUFFER=CACHE.add(AGE,AGE_ORDINAL),
+ ETAG_BUFFER=CACHE.add(ETAG,ETAG_ORDINAL),
+ LOCATION_BUFFER=CACHE.add(LOCATION,LOCATION_ORDINAL),
+ PROXY_AUTHENTICATE_BUFFER=CACHE.add(PROXY_AUTHENTICATE,PROXY_AUTHENTICATE_ORDINAL),
+ RETRY_AFTER_BUFFER=CACHE.add(RETRY_AFTER,RETRY_AFTER_ORDINAL),
+ SERVER_BUFFER=CACHE.add(SERVER,SERVER_ORDINAL),
+ SERVLET_ENGINE_BUFFER=CACHE.add(SERVLET_ENGINE,SERVLET_ENGINE_ORDINAL),
+ VARY_BUFFER=CACHE.add(VARY,VARY_ORDINAL),
+ WWW_AUTHENTICATE_BUFFER=CACHE.add(WWW_AUTHENTICATE,WWW_AUTHENTICATE_ORDINAL),
+ COOKIE_BUFFER=CACHE.add(COOKIE,COOKIE_ORDINAL),
+ SET_COOKIE_BUFFER=CACHE.add(SET_COOKIE,SET_COOKIE_ORDINAL),
+ SET_COOKIE2_BUFFER=CACHE.add(SET_COOKIE2,SET_COOKIE2_ORDINAL),
+ MIME_VERSION_BUFFER=CACHE.add(MIME_VERSION,MIME_VERSION_ORDINAL),
+ IDENTITY_BUFFER=CACHE.add(IDENTITY,IDENTITY_ORDINAL),
+ PROXY_CONNECTION_BUFFER=CACHE.add(PROXY_CONNECTION,PROXY_CONNECTION_ORDINAL);
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpMethods.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpMethods.java
new file mode 100644
index 00000000..2c91fde9
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpMethods.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ *
+ *
+ */
+public class HttpMethods
+{
+ public final static String GET= "GET",
+ POST= "POST",
+ HEAD= "HEAD",
+ PUT= "PUT",
+ OPTIONS= "OPTIONS",
+ DELETE= "DELETE",
+ TRACE= "TRACE",
+ CONNECT= "CONNECT",
+ MOVE= "MOVE";
+
+ public final static int GET_ORDINAL= 1,
+ POST_ORDINAL= 2,
+ HEAD_ORDINAL= 3,
+ PUT_ORDINAL= 4,
+ OPTIONS_ORDINAL= 5,
+ DELETE_ORDINAL= 6,
+ TRACE_ORDINAL= 7,
+ CONNECT_ORDINAL= 8,
+ MOVE_ORDINAL= 9;
+
+ public final static BufferCache CACHE= new BufferCache();
+
+ public final static Buffer
+ GET_BUFFER= CACHE.add(GET, GET_ORDINAL),
+ POST_BUFFER= CACHE.add(POST, POST_ORDINAL),
+ HEAD_BUFFER= CACHE.add(HEAD, HEAD_ORDINAL),
+ PUT_BUFFER= CACHE.add(PUT, PUT_ORDINAL),
+ OPTIONS_BUFFER= CACHE.add(OPTIONS, OPTIONS_ORDINAL),
+ DELETE_BUFFER= CACHE.add(DELETE, DELETE_ORDINAL),
+ TRACE_BUFFER= CACHE.add(TRACE, TRACE_ORDINAL),
+ CONNECT_BUFFER= CACHE.add(CONNECT, CONNECT_ORDINAL),
+ MOVE_BUFFER= CACHE.add(MOVE, MOVE_ORDINAL);
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpParser.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpParser.java
new file mode 100644
index 00000000..0547ea93
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpParser.java
@@ -0,0 +1,1033 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Locale;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.io.BufferUtil;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.View;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpParser implements Parser
+{
+ private static final Logger LOG = Log.getLogger(HttpParser.class);
+
+ // States
+ public static final int STATE_START=-14;
+ public static final int STATE_FIELD0=-13;
+ public static final int STATE_SPACE1=-12;
+ public static final int STATE_STATUS=-11;
+ public static final int STATE_URI=-10;
+ public static final int STATE_SPACE2=-9;
+ public static final int STATE_END0=-8;
+ public static final int STATE_END1=-7;
+ public static final int STATE_FIELD2=-6;
+ public static final int STATE_HEADER=-5;
+ public static final int STATE_HEADER_NAME=-4;
+ public static final int STATE_HEADER_IN_NAME=-3;
+ public static final int STATE_HEADER_VALUE=-2;
+ public static final int STATE_HEADER_IN_VALUE=-1;
+ public static final int STATE_END=0;
+ public static final int STATE_EOF_CONTENT=1;
+ public static final int STATE_CONTENT=2;
+ public static final int STATE_CHUNKED_CONTENT=3;
+ public static final int STATE_CHUNK_SIZE=4;
+ public static final int STATE_CHUNK_PARAMS=5;
+ public static final int STATE_CHUNK=6;
+ public static final int STATE_SEEKING_EOF=7;
+
+ public static final Charset __ISO_8859_1 = Charset.forName("ISO-8859-1");
+
+ public static final int BAD_REQUEST_400 = 400;
+ public static final int REQUEST_ENTITY_TOO_LARGE_413 = 413;
+
+ static final byte COLON= (byte)':';
+ static final byte SPACE= 0x20;
+ static final byte CARRIAGE_RETURN= 0x0D;
+ static final byte LINE_FEED= 0x0A;
+ static final byte SEMI_COLON= (byte)';';
+ static final byte TAB= 0x09;
+
+ public static final int UNKNOWN_CONTENT= -3;
+ public static final int CHUNKED_CONTENT= -2;
+ public static final int EOF_CONTENT= -1;
+ public static final int NO_CONTENT= 0;
+
+ private final EventHandler _handler;
+ private final Buffers _buffers; // source of buffers
+ private final EndPoint _endp;
+ private Buffer _header; // Buffer for header data (and small _content)
+ private Buffer _body; // Buffer for large content
+ private Buffer _buffer; // The current buffer in use (either _header or _content)
+ private CachedBuffer _cached;
+ private final View.CaseInsensitive _tok0; // Saved token: header name, request method or response version
+ private final View.CaseInsensitive _tok1; // Saved token: header value, request URI or response code
+ private String _multiLineValue;
+ private int _responseStatus; // If >0 then we are parsing a response
+ private boolean _forceContentBuffer;
+ private boolean _persistent;
+
+ /* ------------------------------------------------------------------------------- */
+ protected final View _contentView=new View(); // View of the content in the buffer for {@link Input}
+ protected int _state=STATE_START;
+ protected byte _eol;
+ protected int _length;
+ protected long _contentLength;
+ protected long _contentPosition;
+ protected int _chunkLength;
+ protected int _chunkPosition;
+ private boolean _headResponse;
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * Constructor.
+ * @param buffers the buffers to use
+ * @param endp the endpoint
+ * @param handler the even handler
+ */
+ public HttpParser(Buffers buffers, EndPoint endp, EventHandler handler)
+ {
+ _buffers=buffers;
+ _endp=endp;
+ _handler=handler;
+ _tok0=new View.CaseInsensitive();
+ _tok1=new View.CaseInsensitive();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isIdle()
+ {
+ return isState(STATE_START);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isComplete()
+ {
+ if (_responseStatus > 0)
+ return isState(STATE_END) || isState(STATE_SEEKING_EOF);
+ return isState(STATE_END);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean isState(int state)
+ {
+ return _state == state;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * Parse until END state.
+ * This method will parse any remaining content in the current buffer as long as there is
+ * no unconsumed content. It does not care about the {@link #getState current state} of the parser.
+ * @see #parse
+ * @see #parseNext
+ */
+ public boolean parseAvailable() throws IOException
+ {
+ boolean progress=parseNext()>0;
+
+ // continue parsing
+ while (!isComplete() && _buffer!=null && _buffer.length()>0 && !_contentView.hasContent())
+ {
+ progress |= parseNext()>0;
+ }
+ return progress;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * Parse until next Event.
+ * @return an indication of progress <0 EOF, 0 no progress, >0 progress.
+ */
+ public int parseNext() throws IOException
+ {
+ try
+ {
+ int progress=0;
+
+ if (_state == STATE_END)
+ return 0;
+
+ if (_buffer==null)
+ _buffer=getHeaderBuffer();
+
+
+ if (_state == STATE_CONTENT && _contentPosition == _contentLength)
+ {
+ _state=STATE_END;
+ _handler.messageComplete(_contentPosition);
+ return 1;
+ }
+
+ int length=_buffer.length();
+
+ // Fill buffer if we can
+ if (length == 0)
+ {
+ int filled=-1;
+ IOException ex=null;
+ try
+ {
+ filled=fill();
+ LOG.debug("filled {}/{}",filled,_buffer.length());
+ }
+ catch(IOException e)
+ {
+ LOG.debug(this.toString(),e);
+ ex=e;
+ }
+
+ if (filled > 0 )
+ progress++;
+ else if (filled < 0 )
+ {
+ _persistent=false;
+
+ // do we have content to deliver?
+ if (_state>STATE_END)
+ {
+ if (_buffer.length()>0 && !_headResponse)
+ {
+ Buffer chunk=_buffer.get(_buffer.length());
+ _contentPosition += chunk.length();
+ _contentView.update(chunk);
+ _handler.content(chunk); // May recurse here
+ }
+ }
+
+ // was this unexpected?
+ switch(_state)
+ {
+ case STATE_END:
+ case STATE_SEEKING_EOF:
+ _state=STATE_END;
+ break;
+
+ case STATE_EOF_CONTENT:
+ _state=STATE_END;
+ _handler.messageComplete(_contentPosition);
+ break;
+
+ default:
+ _state=STATE_END;
+ if (!_headResponse)
+ _handler.earlyEOF();
+ _handler.messageComplete(_contentPosition);
+ }
+
+ if (ex!=null)
+ throw ex;
+
+ if (!isComplete() && !isIdle())
+ throw new EofException();
+
+ return -1;
+ }
+ length=_buffer.length();
+ }
+
+
+ // Handle header states
+ byte ch;
+ byte[] array=_buffer.array();
+ int last=_state;
+ while (_state<STATE_END && length-->0)
+ {
+ if (last!=_state)
+ {
+ progress++;
+ last=_state;
+ }
+
+ ch=_buffer.get();
+
+ if (_eol == CARRIAGE_RETURN)
+ {
+ if (ch == LINE_FEED)
+ {
+ _eol=LINE_FEED;
+ continue;
+ }
+ throw new HttpException(BAD_REQUEST_400);
+ }
+ _eol=0;
+
+ switch (_state)
+ {
+ case STATE_START:
+ _contentLength=UNKNOWN_CONTENT;
+ _cached=null;
+ if (ch > SPACE || ch<0)
+ {
+ _buffer.mark();
+ _state=STATE_FIELD0;
+ }
+ break;
+
+ case STATE_FIELD0:
+ if (ch == SPACE)
+ {
+ _tok0.update(_buffer.markIndex(), _buffer.getIndex() - 1);
+ _responseStatus=HttpVersions.CACHE.get(_tok0)==null?-1:0;
+ _state=STATE_SPACE1;
+ continue;
+ }
+ else if (ch < SPACE && ch>=0)
+ {
+ throw new HttpException(BAD_REQUEST_400);
+ }
+ break;
+
+ case STATE_SPACE1:
+ if (ch > SPACE || ch<0)
+ {
+ _buffer.mark();
+ if (_responseStatus>=0)
+ {
+ _state=STATE_STATUS;
+ _responseStatus=ch-'0';
+ }
+ else
+ _state=STATE_URI;
+ }
+ else if (ch < SPACE)
+ {
+ throw new HttpException(BAD_REQUEST_400);
+ }
+ break;
+
+ case STATE_STATUS:
+ if (ch == SPACE)
+ {
+ _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1);
+ _state=STATE_SPACE2;
+ continue;
+ }
+ else if (ch>='0' && ch<='9')
+ {
+ _responseStatus=_responseStatus*10+(ch-'0');
+ continue;
+ }
+ else if (ch < SPACE && ch>=0)
+ {
+ _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null);
+ _eol=ch;
+ _state=STATE_HEADER;
+ _tok0.setPutIndex(_tok0.getIndex());
+ _tok1.setPutIndex(_tok1.getIndex());
+ _multiLineValue=null;
+ continue;
+ }
+ // not a digit, so must be a URI
+ _state=STATE_URI;
+ _responseStatus=-1;
+ break;
+
+ case STATE_URI:
+ if (ch == SPACE)
+ {
+ _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1);
+ _state=STATE_SPACE2;
+ continue;
+ }
+ else if (ch < SPACE && ch>=0)
+ {
+ // HTTP/0.9
+ _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _buffer.sliceFromMark(), null);
+ _persistent=false;
+ _state=STATE_SEEKING_EOF;
+ _handler.headerComplete();
+ _handler.messageComplete(_contentPosition);
+ return 1;
+ }
+ break;
+
+ case STATE_SPACE2:
+ if (ch > SPACE || ch<0)
+ {
+ _buffer.mark();
+ _state=STATE_FIELD2;
+ }
+ else if (ch < SPACE)
+ {
+ if (_responseStatus>0)
+ {
+ _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null);
+ _eol=ch;
+ _state=STATE_HEADER;
+ _tok0.setPutIndex(_tok0.getIndex());
+ _tok1.setPutIndex(_tok1.getIndex());
+ _multiLineValue=null;
+ }
+ else
+ {
+ // HTTP/0.9
+ _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, null);
+ _persistent=false;
+ _state=STATE_SEEKING_EOF;
+ _handler.headerComplete();
+ _handler.messageComplete(_contentPosition);
+ return 1;
+ }
+ }
+ break;
+
+ case STATE_FIELD2:
+ if (ch == CARRIAGE_RETURN || ch == LINE_FEED)
+ {
+ Buffer version;
+ if (_responseStatus>0)
+ _handler.startResponse(version=HttpVersions.CACHE.lookup(_tok0), _responseStatus,_buffer.sliceFromMark());
+ else
+ _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, version=HttpVersions.CACHE.lookup(_buffer.sliceFromMark()));
+ _eol=ch;
+ _persistent=HttpVersions.CACHE.getOrdinal(version)>=HttpVersions.HTTP_1_1_ORDINAL;
+ _state=STATE_HEADER;
+ _tok0.setPutIndex(_tok0.getIndex());
+ _tok1.setPutIndex(_tok1.getIndex());
+ _multiLineValue=null;
+ continue;
+ }
+ break;
+
+ case STATE_HEADER:
+ switch(ch)
+ {
+ case COLON:
+ case SPACE:
+ case TAB:
+ {
+ // header value without name - continuation?
+ _length=-1;
+ _state=STATE_HEADER_VALUE;
+ break;
+ }
+
+ default:
+ {
+ // handler last header if any
+ if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null)
+ {
+ Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0);
+ _cached=null;
+ Buffer value=_multiLineValue == null ? _tok1 : new ByteArrayBuffer(_multiLineValue);
+
+ int ho=HttpHeaders.CACHE.getOrdinal(header);
+ if (ho >= 0)
+ {
+ int vo;
+
+ switch (ho)
+ {
+ case HttpHeaders.CONTENT_LENGTH_ORDINAL:
+ if (_contentLength != CHUNKED_CONTENT )
+ {
+ try
+ {
+ _contentLength=BufferUtil.toLong(value);
+ }
+ catch(NumberFormatException e)
+ {
+ LOG.ignore(e);
+ throw new HttpException(BAD_REQUEST_400);
+ }
+ if (_contentLength <= 0)
+ _contentLength=NO_CONTENT;
+ }
+ break;
+
+ case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
+ value=HttpHeaderValues.CACHE.lookup(value);
+ vo=HttpHeaderValues.CACHE.getOrdinal(value);
+ if (HttpHeaderValues.CHUNKED_ORDINAL == vo)
+ _contentLength=CHUNKED_CONTENT;
+ else
+ {
+ String c=value.toString(__ISO_8859_1);
+ if (c.endsWith(HttpHeaderValues.CHUNKED))
+ _contentLength=CHUNKED_CONTENT;
+
+ else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0)
+ throw new HttpException(400,null);
+ }
+ break;
+
+ case HttpHeaders.CONNECTION_ORDINAL:
+ switch(HttpHeaderValues.CACHE.getOrdinal(value))
+ {
+ case HttpHeaderValues.CLOSE_ORDINAL:
+ _persistent=false;
+ break;
+
+ case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+ _persistent=true;
+ break;
+
+ case -1: // No match, may be multi valued
+ {
+ for (String v : value.toString().split(","))
+ {
+ switch(HttpHeaderValues.CACHE.getOrdinal(v.trim()))
+ {
+ case HttpHeaderValues.CLOSE_ORDINAL:
+ _persistent=false;
+ break;
+
+ case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+ _persistent=true;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ _handler.parsedHeader(header, value);
+ _tok0.setPutIndex(_tok0.getIndex());
+ _tok1.setPutIndex(_tok1.getIndex());
+ _multiLineValue=null;
+ }
+ _buffer.setMarkIndex(-1);
+
+ // now handle ch
+ if (ch == CARRIAGE_RETURN || ch == LINE_FEED)
+ {
+ // is it a response that cannot have a body?
+ if (_responseStatus > 0 && // response
+ (_responseStatus == 304 || // not-modified response
+ _responseStatus == 204 || // no-content response
+ _responseStatus < 200)) // 1xx response
+ _contentLength=NO_CONTENT; // ignore any other headers set
+ // else if we don't know framing
+ else if (_contentLength == UNKNOWN_CONTENT)
+ {
+ if (_responseStatus == 0 // request
+ || _responseStatus == 304 // not-modified response
+ || _responseStatus == 204 // no-content response
+ || _responseStatus < 200) // 1xx response
+ _contentLength=NO_CONTENT;
+ else
+ _contentLength=EOF_CONTENT;
+ }
+
+ _contentPosition=0;
+ _eol=ch;
+ if (_eol==CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==LINE_FEED)
+ _eol=_buffer.get();
+
+ // We convert _contentLength to an int for this switch statement because
+ // we don't care about the amount of data available just whether there is some.
+ switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength)
+ {
+ case EOF_CONTENT:
+ _state=STATE_EOF_CONTENT;
+ _handler.headerComplete(); // May recurse here !
+ break;
+
+ case CHUNKED_CONTENT:
+ _state=STATE_CHUNKED_CONTENT;
+ _handler.headerComplete(); // May recurse here !
+ break;
+
+ case NO_CONTENT:
+ _handler.headerComplete();
+ _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF;
+ _handler.messageComplete(_contentPosition);
+ return 1;
+
+ default:
+ _state=STATE_CONTENT;
+ _handler.headerComplete(); // May recurse here !
+ break;
+ }
+ return 1;
+ }
+ else
+ {
+ // New header
+ _length=1;
+ _buffer.mark();
+ _state=STATE_HEADER_NAME;
+
+ // try cached name!
+ if (array!=null)
+ {
+ _cached=HttpHeaders.CACHE.getBest(array, _buffer.markIndex(), length+1);
+
+ if (_cached!=null)
+ {
+ _length=_cached.length();
+ _buffer.setGetIndex(_buffer.markIndex()+_length);
+ length=_buffer.length();
+ }
+ }
+ }
+ }
+ }
+
+ break;
+
+ case STATE_HEADER_NAME:
+ switch(ch)
+ {
+ case CARRIAGE_RETURN:
+ case LINE_FEED:
+ if (_length > 0)
+ _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+ _eol=ch;
+ _state=STATE_HEADER;
+ break;
+ case COLON:
+ if (_length > 0 && _cached==null)
+ _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+ _length=-1;
+ _state=STATE_HEADER_VALUE;
+ break;
+ case SPACE:
+ case TAB:
+ break;
+ default:
+ {
+ _cached=null;
+ if (_length == -1)
+ _buffer.mark();
+ _length=_buffer.getIndex() - _buffer.markIndex();
+ _state=STATE_HEADER_IN_NAME;
+ }
+ }
+
+ break;
+
+ case STATE_HEADER_IN_NAME:
+ switch(ch)
+ {
+ case CARRIAGE_RETURN:
+ case LINE_FEED:
+ if (_length > 0)
+ _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+ _eol=ch;
+ _state=STATE_HEADER;
+ break;
+ case COLON:
+ if (_length > 0 && _cached==null)
+ _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+ _length=-1;
+ _state=STATE_HEADER_VALUE;
+ break;
+ case SPACE:
+ case TAB:
+ _state=STATE_HEADER_NAME;
+ break;
+ default:
+ {
+ _cached=null;
+ _length++;
+ }
+ }
+ break;
+
+ case STATE_HEADER_VALUE:
+ switch(ch)
+ {
+ case CARRIAGE_RETURN:
+ case LINE_FEED:
+ if (_length > 0)
+ {
+ if (_tok1.length() == 0)
+ _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+ else
+ {
+ // Continuation line!
+ if (_multiLineValue == null) _multiLineValue=_tok1.toString(__ISO_8859_1);
+ _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+ _multiLineValue += " " + _tok1.toString(__ISO_8859_1);
+ }
+ }
+ _eol=ch;
+ _state=STATE_HEADER;
+ break;
+ case SPACE:
+ case TAB:
+ break;
+ default:
+ {
+ if (_length == -1)
+ _buffer.mark();
+ _length=_buffer.getIndex() - _buffer.markIndex();
+ _state=STATE_HEADER_IN_VALUE;
+ }
+ }
+ break;
+
+ case STATE_HEADER_IN_VALUE:
+ switch(ch)
+ {
+ case CARRIAGE_RETURN:
+ case LINE_FEED:
+ if (_length > 0)
+ {
+ if (_tok1.length() == 0)
+ _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+ else
+ {
+ // Continuation line!
+ if (_multiLineValue == null) _multiLineValue=_tok1.toString(__ISO_8859_1);
+ _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+ _multiLineValue += " " + _tok1.toString(__ISO_8859_1);
+ }
+ }
+ _eol=ch;
+ _state=STATE_HEADER;
+ break;
+ case SPACE:
+ case TAB:
+ _state=STATE_HEADER_VALUE;
+ break;
+ default:
+ _length++;
+ }
+ break;
+ }
+ } // end of HEADER states loop
+
+ // ==========================
+
+ // Handle HEAD response
+ if (_responseStatus>0 && _headResponse)
+ {
+ _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF;
+ _handler.messageComplete(_contentLength);
+ }
+
+
+ // ==========================
+
+ // Handle _content
+ length=_buffer.length();
+ Buffer chunk;
+ last=_state;
+ while (_state > STATE_END && length > 0)
+ {
+ if (last!=_state)
+ {
+ progress++;
+ last=_state;
+ }
+
+ if (_eol == CARRIAGE_RETURN && _buffer.peek() == LINE_FEED)
+ {
+ _eol=_buffer.get();
+ length=_buffer.length();
+ continue;
+ }
+ _eol=0;
+ switch (_state)
+ {
+ case STATE_EOF_CONTENT:
+ chunk=_buffer.get(_buffer.length());
+ _contentPosition += chunk.length();
+ _contentView.update(chunk);
+ _handler.content(chunk); // May recurse here
+ // TODO adjust the _buffer to keep unconsumed content
+ return 1;
+
+ case STATE_CONTENT:
+ {
+ long remaining=_contentLength - _contentPosition;
+ if (remaining == 0)
+ {
+ _state=_persistent?STATE_END:STATE_SEEKING_EOF;
+ _handler.messageComplete(_contentPosition);
+ return 1;
+ }
+
+ if (length > remaining)
+ {
+ // We can cast reamining to an int as we know that it is smaller than
+ // or equal to length which is already an int.
+ length=(int)remaining;
+ }
+
+ chunk=_buffer.get(length);
+ _contentPosition += chunk.length();
+ _contentView.update(chunk);
+ _handler.content(chunk); // May recurse here
+
+ if(_contentPosition == _contentLength)
+ {
+ _state=_persistent?STATE_END:STATE_SEEKING_EOF;
+ _handler.messageComplete(_contentPosition);
+ }
+ // TODO adjust the _buffer to keep unconsumed content
+ return 1;
+ }
+
+ case STATE_CHUNKED_CONTENT:
+ {
+ ch=_buffer.peek();
+ if (ch == CARRIAGE_RETURN || ch == LINE_FEED)
+ _eol=_buffer.get();
+ else if (ch <= SPACE)
+ _buffer.get();
+ else
+ {
+ _chunkLength=0;
+ _chunkPosition=0;
+ _state=STATE_CHUNK_SIZE;
+ }
+ break;
+ }
+
+ case STATE_CHUNK_SIZE:
+ {
+ ch=_buffer.get();
+ if (ch == CARRIAGE_RETURN || ch == LINE_FEED)
+ {
+ _eol=ch;
+
+ if (_chunkLength == 0)
+ {
+ if (_eol==CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==LINE_FEED)
+ _eol=_buffer.get();
+ _state=_persistent?STATE_END:STATE_SEEKING_EOF;
+ _handler.messageComplete(_contentPosition);
+ return 1;
+ }
+ else
+ _state=STATE_CHUNK;
+ }
+ else if (ch <= SPACE || ch == SEMI_COLON)
+ _state=STATE_CHUNK_PARAMS;
+ else if (ch >= '0' && ch <= '9')
+ _chunkLength=_chunkLength * 16 + (ch - '0');
+ else if (ch >= 'a' && ch <= 'f')
+ _chunkLength=_chunkLength * 16 + (10 + ch - 'a');
+ else if (ch >= 'A' && ch <= 'F')
+ _chunkLength=_chunkLength * 16 + (10 + ch - 'A');
+ else
+ throw new IOException("bad chunk char: " + ch);
+ break;
+ }
+
+ case STATE_CHUNK_PARAMS:
+ {
+ ch=_buffer.get();
+ if (ch == CARRIAGE_RETURN || ch == LINE_FEED)
+ {
+ _eol=ch;
+ if (_chunkLength == 0)
+ {
+ if (_eol==CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==LINE_FEED)
+ _eol=_buffer.get();
+ _state=_persistent?STATE_END:STATE_SEEKING_EOF;
+ _handler.messageComplete(_contentPosition);
+ return 1;
+ }
+ else
+ _state=STATE_CHUNK;
+ }
+ break;
+ }
+
+ case STATE_CHUNK:
+ {
+ int remaining=_chunkLength - _chunkPosition;
+ if (remaining == 0)
+ {
+ _state=STATE_CHUNKED_CONTENT;
+ break;
+ }
+ else if (length > remaining)
+ length=remaining;
+ chunk=_buffer.get(length);
+ _contentPosition += chunk.length();
+ _chunkPosition += chunk.length();
+ _contentView.update(chunk);
+ _handler.content(chunk); // May recurse here
+ // TODO adjust the _buffer to keep unconsumed content
+ return 1;
+ }
+
+ case STATE_SEEKING_EOF:
+ {
+ // Close if there is more data than CRLF
+ if (_buffer.length()>2)
+ {
+ _state=STATE_END;
+ _endp.close();
+ }
+ else
+ {
+ // or if the data is not white space
+ while (_buffer.length()>0)
+ if (!Character.isWhitespace(_buffer.get()))
+ {
+ _state=STATE_END;
+ _endp.close();
+ _buffer.clear();
+ }
+ }
+
+ _buffer.clear();
+ break;
+ }
+ }
+
+ length=_buffer.length();
+ }
+
+ return progress;
+ }
+ catch(HttpException e)
+ {
+ _persistent=false;
+ _state=STATE_SEEKING_EOF;
+ throw e;
+ }
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /** fill the buffers from the endpoint
+ *
+ */
+ protected int fill() throws IOException
+ {
+ // Do we have a buffer?
+ if (_buffer==null)
+ _buffer=getHeaderBuffer();
+
+ // Is there unconsumed content in body buffer
+ if (_state>STATE_END && _buffer==_header && _header!=null && !_header.hasContent() && _body!=null && _body.hasContent())
+ {
+ _buffer=_body;
+ return _buffer.length();
+ }
+
+ // Shall we switch to a body buffer?
+ if (_buffer==_header && _state>STATE_END && _header.length()==0 && (_forceContentBuffer || (_contentLength-_contentPosition)>_header.capacity()) && (_body!=null||_buffers!=null))
+ {
+ if (_body==null)
+ _body=_buffers.getBuffer();
+ _buffer=_body;
+ }
+
+ // Do we have somewhere to fill from?
+ if (_endp != null )
+ {
+ // Shall we compact the body?
+ if (_buffer==_body || _state>STATE_END)
+ {
+ _buffer.compact();
+ }
+
+ // Are we full?
+ if (_buffer.space() == 0)
+ {
+ LOG.warn("HttpParser Full for {} ",_endp);
+ _buffer.clear();
+ throw new HttpException(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large: "+(_buffer==_body?"body":"head"));
+ }
+
+ try
+ {
+ int filled = _endp.fill(_buffer);
+ return filled;
+ }
+ catch(IOException e)
+ {
+ LOG.debug(e);
+ throw (e instanceof EofException) ? e:new EofException(e);
+ }
+ }
+
+ return -1;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return String.format(Locale.getDefault(), "%s{s=%d,l=%d,c=%d}",
+ getClass().getSimpleName(),
+ _state,
+ _length,
+ _contentLength);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Buffer getHeaderBuffer()
+ {
+ if (_header == null)
+ {
+ _header=_buffers.getHeader();
+ _tok0.update(_header);
+ _tok1.update(_header);
+ }
+ return _header;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static abstract class EventHandler
+ {
+ public abstract void content(Buffer ref) throws IOException;
+
+ public void headerComplete() throws IOException
+ {
+ }
+
+ public void messageComplete(long contentLength) throws IOException
+ {
+ }
+
+ /**
+ * This is the method called by parser when a HTTP Header name and value is found
+ */
+ public void parsedHeader(Buffer name, Buffer value) throws IOException
+ {
+ }
+
+ /**
+ * This is the method called by parser when the HTTP request line is parsed
+ */
+ public abstract void startRequest(Buffer method, Buffer url, Buffer version)
+ throws IOException;
+
+ /**
+ * This is the method called by parser when the HTTP request line is parsed
+ */
+ public abstract void startResponse(Buffer version, int status, Buffer reason)
+ throws IOException;
+
+ public void earlyEOF()
+ {}
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpVersions.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpVersions.java
new file mode 100644
index 00000000..95d21ceb
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/HttpVersions.java
@@ -0,0 +1,47 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ *
+ *
+ */
+public class HttpVersions
+{
+ public final static String
+ HTTP_0_9 = "",
+ HTTP_1_0 = "HTTP/1.0",
+ HTTP_1_1 = "HTTP/1.1";
+
+ public final static int
+ HTTP_0_9_ORDINAL=9,
+ HTTP_1_0_ORDINAL=10,
+ HTTP_1_1_ORDINAL=11;
+
+ public final static BufferCache CACHE = new BufferCache();
+
+ public final static Buffer
+ HTTP_0_9_BUFFER=CACHE.add(HTTP_0_9,HTTP_0_9_ORDINAL),
+ HTTP_1_0_BUFFER=CACHE.add(HTTP_1_0,HTTP_1_0_ORDINAL),
+ HTTP_1_1_BUFFER=CACHE.add(HTTP_1_1,HTTP_1_1_ORDINAL);
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/Parser.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/Parser.java
new file mode 100644
index 00000000..68b433b9
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/http/Parser.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+
+/**
+ * Abstract interface for a connection Parser for use by Jetty.
+ */
+public interface Parser
+{
+ boolean isComplete();
+
+ /**
+ * @return True if progress made
+ * @throws IOException
+ */
+ boolean parseAvailable() throws IOException;
+
+ boolean isIdle();
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractBuffer.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractBuffer.java
new file mode 100644
index 00000000..400c088e
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractBuffer.java
@@ -0,0 +1,557 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.charset.Charset;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ *
+ *
+ */
+public abstract class AbstractBuffer implements Buffer
+{
+ private static final Logger LOG = Log.getLogger(AbstractBuffer.class);
+
+ protected final static String
+ __IMMUTABLE = "IMMUTABLE",
+ __READONLY = "READONLY",
+ __READWRITE = "READWRITE",
+ __VOLATILE = "VOLATILE";
+
+ protected int _access;
+ protected boolean _volatile;
+
+ protected int _get;
+ protected int _put;
+ protected int _hash;
+ protected int _hashGet;
+ protected int _hashPut;
+ protected int _mark;
+ protected String _string;
+ protected View _view;
+
+ /**
+ * Constructor for BufferView
+ *
+ * @param access 0==IMMUTABLE, 1==READONLY, 2==READWRITE
+ */
+ public AbstractBuffer(int access, boolean isVolatile)
+ {
+ if (access == IMMUTABLE && isVolatile)
+ throw new IllegalArgumentException("IMMUTABLE && VOLATILE");
+ setMarkIndex(-1);
+ _access = access;
+ _volatile = isVolatile;
+ }
+
+ /*
+ * @see org.eclipse.io.Buffer#toArray()
+ */
+ public byte[] asArray()
+ {
+ byte[] bytes = new byte[length()];
+ byte[] array = array();
+ if (array != null)
+ System.arraycopy(array, getIndex(), bytes, 0, bytes.length);
+ else
+ peek(getIndex(), bytes, 0, length());
+ return bytes;
+ }
+
+ public ByteArrayBuffer duplicate(int access)
+ {
+ Buffer b=this.buffer();
+ if (this instanceof Buffer.CaseInsensitve || b instanceof Buffer.CaseInsensitve)
+ return new ByteArrayBuffer.CaseInsensitive(asArray(), 0, length(),access);
+ else
+ return new ByteArrayBuffer(asArray(), 0, length(), access);
+ }
+
+ public Buffer asMutableBuffer()
+ {
+ if (!isImmutable()) return this;
+
+ Buffer b=this.buffer();
+ if (b.isReadOnly())
+ {
+ return duplicate(READWRITE);
+ }
+ return new View(b, markIndex(), getIndex(), putIndex(), _access);
+ }
+
+ public Buffer buffer()
+ {
+ return this;
+ }
+
+ public void clear()
+ {
+ setMarkIndex(-1);
+ setGetIndex(0);
+ setPutIndex(0);
+ }
+
+ public void compact()
+ {
+ if (isReadOnly()) throw new IllegalStateException(__READONLY);
+ int s = markIndex() >= 0 ? markIndex() : getIndex();
+ if (s > 0)
+ {
+ byte array[] = array();
+ int length = putIndex() - s;
+ if (length > 0)
+ {
+ if (array != null)
+ System.arraycopy(array(), s, array(), 0, length);
+ else
+ poke(0, peek(s, length));
+ }
+ if (markIndex() > 0) setMarkIndex(markIndex() - s);
+ setGetIndex(getIndex() - s);
+ setPutIndex(putIndex() - s);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj==this)
+ return true;
+
+ // reject non buffers;
+ if (obj == null || !(obj instanceof Buffer)) return false;
+ Buffer b = (Buffer) obj;
+
+ if (this instanceof Buffer.CaseInsensitve || b instanceof Buffer.CaseInsensitve)
+ return equalsIgnoreCase(b);
+
+ // reject different lengths
+ if (b.length() != length()) return false;
+
+ // reject AbstractBuffer with different hash value
+ if (_hash != 0 && obj instanceof AbstractBuffer)
+ {
+ AbstractBuffer ab = (AbstractBuffer) obj;
+ if (ab._hash != 0 && _hash != ab._hash) return false;
+ }
+
+ // Nothing for it but to do the hard grind.
+ int get=getIndex();
+ int bi=b.putIndex();
+ for (int i = putIndex(); i-->get;)
+ {
+ byte b1 = peek(i);
+ byte b2 = b.peek(--bi);
+ if (b1 != b2) return false;
+ }
+ return true;
+ }
+
+ public boolean equalsIgnoreCase(Buffer b)
+ {
+ if (b==this)
+ return true;
+
+ // reject different lengths
+ if (b.length() != length()) return false;
+
+ // reject AbstractBuffer with different hash value
+ if (_hash != 0 && b instanceof AbstractBuffer)
+ {
+ AbstractBuffer ab = (AbstractBuffer) b;
+ if (ab._hash != 0 && _hash != ab._hash) return false;
+ }
+
+ // Nothing for it but to do the hard grind.
+ int get=getIndex();
+ int bi=b.putIndex();
+
+ byte[] array = array();
+ byte[] barray= b.array();
+ if (array!=null && barray!=null)
+ {
+ for (int i = putIndex(); i-->get;)
+ {
+ byte b1 = array[i];
+ byte b2 = barray[--bi];
+ if (b1 != b2)
+ {
+ if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
+ if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
+ if (b1 != b2) return false;
+ }
+ }
+ }
+ else
+ {
+ for (int i = putIndex(); i-->get;)
+ {
+ byte b1 = peek(i);
+ byte b2 = b.peek(--bi);
+ if (b1 != b2)
+ {
+ if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
+ if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
+ if (b1 != b2) return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public byte get()
+ {
+ return peek(_get++);
+ }
+
+ public int get(byte[] b, int offset, int length)
+ {
+ int gi = getIndex();
+ int l=length();
+ if (l==0)
+ return -1;
+
+ if (length>l)
+ length=l;
+
+ length = peek(gi, b, offset, length);
+ if (length>0)
+ setGetIndex(gi + length);
+ return length;
+ }
+
+ public Buffer get(int length)
+ {
+ int gi = getIndex();
+ Buffer view = peek(gi, length);
+ setGetIndex(gi + length);
+ return view;
+ }
+
+ public final int getIndex()
+ {
+ return _get;
+ }
+
+ public boolean hasContent()
+ {
+ return _put > _get;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ if (_hash == 0 || _hashGet!=_get || _hashPut!=_put)
+ {
+ int get=getIndex();
+ byte[] array = array();
+ if (array==null)
+ {
+ for (int i = putIndex(); i-- >get;)
+ {
+ byte b = peek(i);
+ if ('a' <= b && b <= 'z')
+ b = (byte) (b - 'a' + 'A');
+ _hash = 31 * _hash + b;
+ }
+ }
+ else
+ {
+ for (int i = putIndex(); i-- >get;)
+ {
+ byte b = array[i];
+ if ('a' <= b && b <= 'z')
+ b = (byte) (b - 'a' + 'A');
+ _hash = 31 * _hash + b;
+ }
+ }
+ if (_hash == 0)
+ _hash = -1;
+ _hashGet=_get;
+ _hashPut=_put;
+
+ }
+ return _hash;
+ }
+
+ public boolean isImmutable()
+ {
+ return _access <= IMMUTABLE;
+ }
+
+ public boolean isReadOnly()
+ {
+ return _access <= READONLY;
+ }
+
+ public boolean isVolatile()
+ {
+ return _volatile;
+ }
+
+ public int length()
+ {
+ return _put - _get;
+ }
+
+ public void mark()
+ {
+ setMarkIndex(_get - 1);
+ }
+
+ public int markIndex()
+ {
+ return _mark;
+ }
+
+ public byte peek()
+ {
+ return peek(_get);
+ }
+
+ public Buffer peek(int index, int length)
+ {
+ if (_view == null)
+ {
+ _view = new View(this, -1, index, index + length, isReadOnly() ? READONLY : READWRITE);
+ }
+ else
+ {
+ _view.update(this.buffer());
+ _view.setMarkIndex(-1);
+ _view.setGetIndex(0);
+ _view.setPutIndex(index + length);
+ _view.setGetIndex(index);
+
+ }
+ return _view;
+ }
+
+ public int poke(int index, Buffer src)
+ {
+ _hash=0;
+
+ int length=src.length();
+ if (index + length > capacity())
+ {
+ length=capacity()-index;
+ }
+
+ byte[] src_array = src.array();
+ byte[] dst_array = array();
+ if (src_array != null && dst_array != null)
+ System.arraycopy(src_array, src.getIndex(), dst_array, index, length);
+ else if (src_array != null)
+ {
+ int s=src.getIndex();
+ for (int i=0;i<length;i++)
+ poke(index++,src_array[s++]);
+ }
+ else if (dst_array != null)
+ {
+ int s=src.getIndex();
+ for (int i=0;i<length;i++)
+ dst_array[index++]=src.peek(s++);
+ }
+ else
+ {
+ int s=src.getIndex();
+ for (int i=0;i<length;i++)
+ poke(index++,src.peek(s++));
+ }
+
+ return length;
+ }
+
+
+ public int poke(int index, byte[] b, int offset, int length)
+ {
+ _hash=0;
+ if (index + length > capacity())
+ {
+ length=capacity()-index;
+ }
+
+ byte[] dst_array = array();
+ if (dst_array != null)
+ System.arraycopy(b, offset, dst_array, index, length);
+ else
+ {
+ int s=offset;
+ for (int i=0;i<length;i++)
+ poke(index++,b[s++]);
+ }
+ return length;
+ }
+
+ public int put(Buffer src)
+ {
+ int pi = putIndex();
+ int l=poke(pi, src);
+ setPutIndex(pi + l);
+ return l;
+ }
+
+ public void put(byte b)
+ {
+ int pi = putIndex();
+ poke(pi, b);
+ setPutIndex(pi + 1);
+ }
+
+ public int put(byte[] b, int offset, int length)
+ {
+ int pi = putIndex();
+ int l = poke(pi, b, offset, length);
+ setPutIndex(pi + l);
+ return l;
+ }
+
+ public int put(byte[] b)
+ {
+ int pi = putIndex();
+ int l = poke(pi, b, 0, b.length);
+ setPutIndex(pi + l);
+ return l;
+ }
+
+ public final int putIndex()
+ {
+ return _put;
+ }
+
+ public void setGetIndex(int getIndex)
+ {
+ _get = getIndex;
+ _hash=0;
+ }
+
+ public void setMarkIndex(int index)
+ {
+ _mark = index;
+ }
+
+ public void setPutIndex(int putIndex)
+ {
+ _put = putIndex;
+ _hash=0;
+ }
+
+ public int skip(int n)
+ {
+ if (length() < n) n = length();
+ setGetIndex(getIndex() + n);
+ return n;
+ }
+
+ public Buffer sliceFromMark()
+ {
+ return sliceFromMark(getIndex() - markIndex() - 1);
+ }
+
+ public Buffer sliceFromMark(int length)
+ {
+ if (markIndex() < 0) return null;
+ Buffer view = peek(markIndex(), length);
+ setMarkIndex(-1);
+ return view;
+ }
+
+ public int space()
+ {
+ return capacity() - _put;
+ }
+
+ public String toDetailString()
+ {
+ StringBuilder buf = new StringBuilder();
+ buf.append("[");
+ buf.append(super.hashCode());
+ buf.append(",");
+ buf.append(this.buffer().hashCode());
+ buf.append(",m=");
+ buf.append(markIndex());
+ buf.append(",g=");
+ buf.append(getIndex());
+ buf.append(",p=");
+ buf.append(putIndex());
+ buf.append(",c=");
+ buf.append(capacity());
+ buf.append("]={");
+ if (markIndex() >= 0)
+ {
+ for (int i = markIndex(); i < getIndex(); i++)
+ {
+ byte b = peek(i);
+ TypeUtil.toHex(b,buf);
+ }
+ buf.append("}{");
+ }
+ int count = 0;
+ for (int i = getIndex(); i < putIndex(); i++)
+ {
+ byte b = peek(i);
+ TypeUtil.toHex(b,buf);
+ if (count++ == 50)
+ {
+ if (putIndex() - i > 20)
+ {
+ buf.append(" ... ");
+ i = putIndex() - 20;
+ }
+ }
+ }
+ buf.append('}');
+ return buf.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ if (isImmutable())
+ {
+ if (_string == null)
+ _string = new String(asArray(), 0, length());
+ return _string;
+ }
+ return new String(asArray(), 0, length());
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString(Charset charset)
+ {
+ try
+ {
+ byte[] bytes=array();
+ if (bytes!=null)
+ return new String(bytes,getIndex(),length(),charset);
+ return new String(asArray(), 0, length(),charset);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ return new String(asArray(), 0, length());
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractBuffers.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractBuffers.java
new file mode 100644
index 00000000..ebbd13d4
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractBuffers.java
@@ -0,0 +1,148 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import org.eclipse.jetty.io.nio.DirectNIOBuffer;
+import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+
+public abstract class AbstractBuffers implements Buffers
+{
+ protected final Buffers.Type _headerType;
+ protected final int _headerSize;
+ protected final Buffers.Type _bufferType;
+ protected final int _bufferSize;
+ protected final Buffers.Type _otherType;
+
+ /* ------------------------------------------------------------ */
+ public AbstractBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType)
+ {
+ _headerType=headerType;
+ _headerSize=headerSize;
+ _bufferType=bufferType;
+ _bufferSize=bufferSize;
+ _otherType=otherType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the buffer size in bytes.
+ */
+ public int getBufferSize()
+ {
+ return _bufferSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the header size in bytes.
+ */
+ public int getHeaderSize()
+ {
+ return _headerSize;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new header Buffer
+ * @return new Buffer
+ */
+ final protected Buffer newHeader()
+ {
+ switch(_headerType)
+ {
+ case BYTE_ARRAY:
+ return new ByteArrayBuffer(_headerSize);
+ case DIRECT:
+ return new DirectNIOBuffer(_headerSize);
+ case INDIRECT:
+ return new IndirectNIOBuffer(_headerSize);
+ }
+ throw new IllegalStateException();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new content Buffer
+ * @return new Buffer
+ */
+ final protected Buffer newBuffer()
+ {
+ switch(_bufferType)
+ {
+ case BYTE_ARRAY:
+ return new ByteArrayBuffer(_bufferSize);
+ case DIRECT:
+ return new DirectNIOBuffer(_bufferSize);
+ case INDIRECT:
+ return new IndirectNIOBuffer(_bufferSize);
+ }
+ throw new IllegalStateException();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param buffer
+ * @return True if the buffer is the correct type to be a Header buffer
+ */
+ public final boolean isHeader(Buffer buffer)
+ {
+ if (buffer.capacity()==_headerSize)
+ {
+ switch(_headerType)
+ {
+ case BYTE_ARRAY:
+ return buffer instanceof ByteArrayBuffer && !(buffer instanceof IndirectNIOBuffer);
+ case DIRECT:
+ return buffer instanceof DirectNIOBuffer;
+ case INDIRECT:
+ return buffer instanceof IndirectNIOBuffer;
+ }
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param buffer
+ * @return True if the buffer is the correct type to be a Header buffer
+ */
+ public final boolean isBuffer(Buffer buffer)
+ {
+ if (buffer.capacity()==_bufferSize)
+ {
+ switch(_bufferType)
+ {
+ case BYTE_ARRAY:
+ return buffer instanceof ByteArrayBuffer && !(buffer instanceof IndirectNIOBuffer);
+ case DIRECT:
+ return buffer instanceof DirectNIOBuffer;
+ case INDIRECT:
+ return buffer instanceof IndirectNIOBuffer;
+ }
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return String.format("%s [%d,%d]", getClass().getSimpleName(), _headerSize, _bufferSize);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractConnection.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractConnection.java
new file mode 100644
index 00000000..2acbffb7
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AbstractConnection.java
@@ -0,0 +1,67 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+public abstract class AbstractConnection implements Connection
+{
+ private static final Logger LOG = Log.getLogger(AbstractConnection.class);
+
+ protected final EndPoint _endp;
+
+ public AbstractConnection(EndPoint endp,long timestamp)
+ {
+ _endp=(EndPoint)endp;
+ }
+
+ public void onIdleExpired(long idleForMs)
+ {
+ try
+ {
+ LOG.debug("onIdleExpired {}ms {} {}",idleForMs,this,_endp);
+ if (_endp.isInputShutdown() || _endp.isOutputShutdown())
+ _endp.close();
+ else
+ _endp.shutdownOutput();
+ }
+ catch(IOException e)
+ {
+ LOG.ignore(e);
+
+ try
+ {
+ _endp.close();
+ }
+ catch(IOException e2)
+ {
+ LOG.ignore(e2);
+ }
+ }
+ }
+
+ public String toString()
+ {
+ return String.format("%s@%x", getClass().getSimpleName(), hashCode());
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AsyncEndPoint.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AsyncEndPoint.java
new file mode 100644
index 00000000..904d9cbe
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/AsyncEndPoint.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+public interface AsyncEndPoint extends ConnectedEndPoint
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Dispatch the endpoint if it is not already dispatched
+ *
+ */
+ public void dispatch();
+
+ /* ------------------------------------------------------------ */
+ /** Schedule a write dispatch.
+ * Set the endpoint to not be writable and schedule a dispatch when
+ * it becomes writable.
+ */
+ public void scheduleWrite();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if IO has been successfully performed since the last call to {@link #hasProgressed()}
+ */
+ public boolean hasProgressed();
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Buffer.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Buffer.java
new file mode 100644
index 00000000..d5aa765f
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Buffer.java
@@ -0,0 +1,313 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.charset.Charset;
+
+
+/**
+ * Byte Buffer interface.
+ *
+ * This is a byte buffer that is designed to work like a FIFO for bytes. Puts and Gets operate on different
+ * pointers into the buffer and the valid _content of the buffer is always between the getIndex and the putIndex.
+ *
+ * This buffer interface is designed to be similar, but not dependent on the java.nio buffers, which may
+ * be used to back an implementation of this Buffer. The main difference is that NIO buffer after a put have
+ * their valid _content before the position and a flip is required to access that data.
+ *
+ * For this buffer it is always true that:
+ * markValue <= getIndex <= putIndex <= capacity
+ *
+ *
+ * @version 1.0
+ */
+public interface Buffer extends Cloneable
+{
+ public final static int
+ IMMUTABLE=0, // neither indexes or contexts can be changed
+ READONLY=1, // indexes may be changed, but not content
+ READWRITE=2; // anything can be changed
+ public final boolean VOLATILE=true; // The buffer may change outside of current scope.
+ public final boolean NON_VOLATILE=false;
+
+ /**
+ * Get the underlying array, if one exists.
+ * @return a <code>byte[]</code> backing this buffer or null if none exists.
+ */
+ byte[] array();
+
+ /**
+ *
+ * @return a <code>byte[]</code> value of the bytes from the getIndex to the putIndex.
+ */
+ byte[] asArray();
+
+ /**
+ * Get the underlying buffer. If this buffer wraps a backing buffer.
+ * @return The root backing buffer or this if there is no backing buffer;
+ */
+ Buffer buffer();
+
+ /**
+ *
+ * @return an immutable version of this <code>Buffer</code>.
+ */
+ Buffer asMutableBuffer();
+
+ /**
+ *
+ * The capacity of the buffer. This is the maximum putIndex that may be set.
+ * @return an <code>int</code> value
+ */
+ int capacity();
+
+ /**
+ * the space remaining in the buffer.
+ * @return capacity - putIndex
+ */
+ int space();
+
+ /**
+ * Clear the buffer. getIndex=0, putIndex=0.
+ */
+ void clear();
+
+ /**
+ * Compact the buffer by discarding bytes before the postion (or mark if set).
+ * Bytes from the getIndex (or mark) to the putIndex are moved to the beginning of
+ * the buffer and the values adjusted accordingly.
+ */
+ void compact();
+
+ /**
+ * Get the byte at the current getIndex and increment it.
+ * @return The <code>byte</code> value from the current getIndex.
+ */
+ byte get();
+
+ /**
+ * Get bytes from the current postion and put them into the passed byte array.
+ * The getIndex is incremented by the number of bytes copied into the array.
+ * @param b The byte array to fill.
+ * @param offset Offset in the array.
+ * @param length The max number of bytes to read.
+ * @return The number of bytes actually read.
+ */
+ int get(byte[] b, int offset, int length);
+
+ /**
+ *
+ * @param length an <code>int</code> value
+ * @return a <code>Buffer</code> value
+ */
+ Buffer get(int length);
+
+ /**
+ * The index within the buffer that will next be read or written.
+ * @return an <code>int</code> value >=0 <= putIndex()
+ */
+ int getIndex();
+
+ /**
+ * @return true of putIndex > getIndex
+ */
+ boolean hasContent();
+
+ /**
+ *
+ * @return a <code>boolean</code> value true if case sensitive comparison on this buffer
+ */
+ boolean equalsIgnoreCase(Buffer buffer);
+
+ /**
+ *
+ * @return a <code>boolean</code> value true if the buffer is immutable and that neither
+ * the buffer contents nor the indexes may be changed.
+ */
+ boolean isImmutable();
+
+ /**
+ *
+ * @return a <code>boolean</code> value true if the buffer is readonly. The buffer indexes may
+ * be modified, but the buffer contents may not. For example a View onto an immutable Buffer will be
+ * read only.
+ */
+ boolean isReadOnly();
+
+ /**
+ *
+ * @return a <code>boolean</code> value true if the buffer contents may change
+ * via alternate paths than this buffer. If the contents of this buffer are to be used outside of the
+ * current context, then a copy must be made.
+ */
+ boolean isVolatile();
+
+ /**
+ * The number of bytes from the getIndex to the putIndex
+ * @return an <code>int</code> == putIndex()-getIndex()
+ */
+ int length();
+
+ /**
+ * Set the mark to the current getIndex.
+ */
+ void mark();
+
+ /**
+ * The current index of the mark.
+ * @return an <code>int</code> index in the buffer or -1 if the mark is not set.
+ */
+ int markIndex();
+
+ /**
+ * Get the byte at the current getIndex without incrementing the getIndex.
+ * @return The <code>byte</code> value from the current getIndex.
+ */
+ byte peek();
+
+ /**
+ * Get the byte at a specific index in the buffer.
+ * @param index an <code>int</code> value
+ * @return a <code>byte</code> value
+ */
+ byte peek(int index);
+
+ /**
+ *
+ * @param index an <code>int</code> value
+ * @param b The byte array to peek into
+ * @param offset The offset into the array to start peeking
+ * @param length an <code>int</code> value
+ * @return The number of bytes actually peeked
+ */
+ int peek(int index, byte[] b, int offset, int length);
+
+ /**
+ * Put the contents of the buffer at the specific index.
+ * @param index an <code>int</code> value
+ * @param src a <code>Buffer</code>. If the source buffer is not modified
+
+ * @return The number of bytes actually poked
+ */
+ int poke(int index, Buffer src);
+
+ /**
+ * Put a specific byte to a specific getIndex.
+ * @param index an <code>int</code> value
+ * @param b a <code>byte</code> value
+ */
+ void poke(int index, byte b);
+
+ /**
+ * Put a specific byte to a specific getIndex.
+ * @param index an <code>int</code> value
+ * @param b a <code>byte array</code> value
+ * @return The number of bytes actually poked
+ */
+ int poke(int index, byte b[], int offset, int length);
+
+ /**
+ * Write the bytes from the source buffer to the current getIndex.
+ * @param src The source <code>Buffer</code> it is not modified.
+ * @return The number of bytes actually poked
+ */
+ int put(Buffer src);
+
+ /**
+ * Put a byte to the current getIndex and increment the getIndex.
+ * @param b a <code>byte</code> value
+ */
+ void put(byte b);
+
+ /**
+ * Put a byte to the current getIndex and increment the getIndex.
+ * @param b a <code>byte</code> value
+ * @return The number of bytes actually poked
+ */
+ int put(byte[] b,int offset, int length);
+
+ /**
+ * Put a byte to the current getIndex and increment the getIndex.
+ * @param b a <code>byte</code> value
+ * @return The number of bytes actually poked
+ */
+ int put(byte[] b);
+
+ /**
+ * The index of the first element that should not be read.
+ * @return an <code>int</code> value >= getIndex()
+ */
+ int putIndex();
+
+ /**
+ * Set the buffers start getIndex.
+ * @param newStart an <code>int</code> value
+ */
+ void setGetIndex(int newStart);
+
+ /**
+ * Set a specific value for the mark.
+ * @param newMark an <code>int</code> value
+ */
+ void setMarkIndex(int newMark);
+
+ /**
+ *
+ * @param newLimit an <code>int</code> value
+ */
+ void setPutIndex(int newLimit);
+
+ /**
+ * Skip _content. The getIndex is updated by min(remaining(), n)
+ * @param n The number of bytes to skip
+ * @return the number of bytes skipped.
+ */
+ int skip(int n);
+
+ /**
+ *
+ *
+ * @return a volitile <code>Buffer</code> value from the mark to the putIndex
+ */
+ Buffer sliceFromMark();
+
+ /**
+ *
+ *
+ * @param length an <code>int</code> value
+ * @return a valitile <code>Buffer</code> value from the mark of the length requested.
+ */
+ Buffer sliceFromMark(int length);
+
+ /**
+ *
+ * @return a <code>String</code> value describing the state and contents of the buffer.
+ */
+ String toDetailString();
+
+ /* ------------------------------------------------------------ */
+ String toString(Charset charset);
+
+ /*
+ * Buffers implementing this interface should be compared with case insensitive equals
+ *
+ */
+ public interface CaseInsensitve
+ {}
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/BufferCache.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/BufferCache.java
new file mode 100644
index 00000000..03e81a8d
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/BufferCache.java
@@ -0,0 +1,130 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+import org.eclipse.jetty.util.StringMap;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ * Stores a collection of {@link Buffer} objects.
+ * Buffers are stored in an ordered collection and can retreived by index or value
+ *
+ */
+public class BufferCache
+{
+ private final HashMap<CachedBuffer, CachedBuffer> _bufferMap=new HashMap<CachedBuffer, CachedBuffer>();
+ private final StringMap _stringMap=new StringMap(StringMap.CASE_INSENSTIVE);
+ private final ArrayList<CachedBuffer> _index= new ArrayList<CachedBuffer>();
+
+ /* ------------------------------------------------------------------------------- */
+ /** Add a buffer to the cache at the specified index.
+ * @param value The content of the buffer.
+ */
+ public CachedBuffer add(String value, int ordinal)
+ {
+ CachedBuffer buffer= new CachedBuffer(value, ordinal);
+ _bufferMap.put(buffer, buffer);
+ _stringMap.put(value, buffer);
+ while ((ordinal - _index.size()) >= 0)
+ _index.add(null);
+ if (_index.get(ordinal)==null)
+ _index.add(ordinal, buffer);
+ return buffer;
+ }
+
+ public CachedBuffer get(Buffer buffer)
+ {
+ return (CachedBuffer)_bufferMap.get(buffer);
+ }
+
+ public CachedBuffer get(String value)
+ {
+ return (CachedBuffer)_stringMap.get(value);
+ }
+
+ public Buffer lookup(Buffer buffer)
+ {
+ if (buffer instanceof CachedBuffer)
+ return buffer;
+
+ Buffer b= get(buffer);
+ if (b == null)
+ {
+ if (buffer instanceof Buffer.CaseInsensitve)
+ return buffer;
+ return new ByteArrayBuffer.CaseInsensitive(buffer.asArray(),0,buffer.length(),Buffer.IMMUTABLE);
+ }
+
+ return b;
+ }
+
+ public CachedBuffer getBest(byte[] value, int offset, int maxLength)
+ {
+ Entry<?, ?> entry = _stringMap.getBestEntry(value, offset, maxLength);
+ if (entry!=null)
+ return (CachedBuffer)entry.getValue();
+ return null;
+ }
+
+ public int getOrdinal(String value)
+ {
+ CachedBuffer buffer = (CachedBuffer)_stringMap.get(value);
+ return buffer==null?-1:buffer.getOrdinal();
+ }
+
+ public int getOrdinal(Buffer buffer)
+ {
+ if (buffer instanceof CachedBuffer)
+ return ((CachedBuffer)buffer).getOrdinal();
+ buffer=lookup(buffer);
+ if (buffer!=null && buffer instanceof CachedBuffer)
+ return ((CachedBuffer)buffer).getOrdinal();
+ return -1;
+ }
+
+ public static class CachedBuffer extends ByteArrayBuffer.CaseInsensitive
+ {
+ private final int _ordinal;
+
+ public CachedBuffer(String value, int ordinal)
+ {
+ super(value);
+ _ordinal= ordinal;
+ }
+
+ public int getOrdinal()
+ {
+ return _ordinal;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return "CACHE["+
+ "bufferMap="+_bufferMap+
+ ",stringMap="+_stringMap+
+ ",index="+_index+
+ "]";
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/BufferUtil.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/BufferUtil.java
new file mode 100644
index 00000000..e213c225
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/BufferUtil.java
@@ -0,0 +1,68 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+/* ------------------------------------------------------------------------------- */
+/** Buffer utility methods.
+ *
+ *
+ */
+public class BufferUtil
+{
+ static final byte SPACE= 0x20;
+ static final byte MINUS= '-';
+
+ /**
+ * Convert buffer to an long.
+ * Parses up to the first non-numeric character. If no number is found an
+ * IllegalArgumentException is thrown
+ * @param buffer A buffer containing an integer. The position is not changed.
+ * @return an int
+ */
+ public static long toLong(Buffer buffer)
+ {
+ long val= 0;
+ boolean started= false;
+ boolean minus= false;
+ for (int i= buffer.getIndex(); i < buffer.putIndex(); i++)
+ {
+ byte b= buffer.peek(i);
+ if (b <= SPACE)
+ {
+ if (started)
+ break;
+ }
+ else if (b >= '0' && b <= '9')
+ {
+ val= val * 10L + (b - '0');
+ started= true;
+ }
+ else if (b == MINUS && !started)
+ {
+ minus= true;
+ }
+ else
+ break;
+ }
+
+ if (started)
+ return minus ? (-val) : val;
+ throw new NumberFormatException(buffer.toString());
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Buffers.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Buffers.java
new file mode 100644
index 00000000..85170540
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Buffers.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+
+/* ------------------------------------------------------------ */
+/** BufferSource.
+ * Represents a pool or other source of buffers and abstracts the creation
+ * of specific types of buffers (eg NIO). The concept of big and little buffers
+ * is supported, but these terms have no absolute meaning and must be determined by context.
+ *
+ */
+public interface Buffers
+{
+ enum Type { BYTE_ARRAY, DIRECT, INDIRECT } ;
+
+ Buffer getHeader();
+ Buffer getBuffer();
+
+ void returnBuffer(Buffer buffer);
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ByteArrayBuffer.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ByteArrayBuffer.java
new file mode 100644
index 00000000..da4bf4f5
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ByteArrayBuffer.java
@@ -0,0 +1,325 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.charset.Charset;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ *
+ */
+public class ByteArrayBuffer extends AbstractBuffer
+{
+ static final Charset __ISO_8859_1 = Charset.forName("ISO-8859-1");
+ final protected byte[] _bytes;
+
+ protected ByteArrayBuffer(int size, int access, boolean isVolatile)
+ {
+ this(new byte[size],0,0,access, isVolatile);
+ }
+
+ public ByteArrayBuffer(byte[] bytes, int index, int length, int access)
+ {
+ super(READWRITE, NON_VOLATILE);
+ _bytes = bytes;
+ setPutIndex(index + length);
+ setGetIndex(index);
+ _access = access;
+ }
+
+ public ByteArrayBuffer(byte[] bytes, int index, int length, int access, boolean isVolatile)
+ {
+ super(READWRITE, isVolatile);
+ _bytes = bytes;
+ setPutIndex(index + length);
+ setGetIndex(index);
+ _access = access;
+ }
+
+ public ByteArrayBuffer(int size)
+ {
+ this(new byte[size], 0, 0, READWRITE);
+ setPutIndex(0);
+ }
+
+ public ByteArrayBuffer(String value)
+ {
+ super(READWRITE,NON_VOLATILE);
+ _bytes = value.getBytes(__ISO_8859_1);
+ setGetIndex(0);
+ setPutIndex(_bytes.length);
+ _access=IMMUTABLE;
+ _string = value;
+ }
+
+ public ByteArrayBuffer(String value,boolean immutable)
+ {
+ super(READWRITE,NON_VOLATILE);
+ _bytes = value.getBytes(__ISO_8859_1);
+ setGetIndex(0);
+ setPutIndex(_bytes.length);
+ if (immutable)
+ {
+ _access=IMMUTABLE;
+ _string = value;
+ }
+ }
+
+ public byte[] array()
+ {
+ return _bytes;
+ }
+
+ public int capacity()
+ {
+ return _bytes.length;
+ }
+
+ @Override
+ public void compact()
+ {
+ if (isReadOnly())
+ throw new IllegalStateException(__READONLY);
+ int s = markIndex() >= 0 ? markIndex() : getIndex();
+ if (s > 0)
+ {
+ int length = putIndex() - s;
+ if (length > 0)
+ {
+ System.arraycopy(_bytes, s,_bytes, 0, length);
+ }
+ if (markIndex() > 0) setMarkIndex(markIndex() - s);
+ setGetIndex(getIndex() - s);
+ setPutIndex(putIndex() - s);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj==this)
+ return true;
+
+ if (obj == null || !(obj instanceof Buffer))
+ return false;
+
+ if (obj instanceof Buffer.CaseInsensitve)
+ return equalsIgnoreCase((Buffer)obj);
+
+
+ Buffer b = (Buffer) obj;
+
+ // reject different lengths
+ if (b.length() != length())
+ return false;
+
+ // reject AbstractBuffer with different hash value
+ if (_hash != 0 && obj instanceof AbstractBuffer)
+ {
+ AbstractBuffer ab = (AbstractBuffer) obj;
+ if (ab._hash != 0 && _hash != ab._hash)
+ return false;
+ }
+
+ // Nothing for it but to do the hard grind.
+ int get=getIndex();
+ int bi=b.putIndex();
+ for (int i = putIndex(); i-->get;)
+ {
+ byte b1 = _bytes[i];
+ byte b2 = b.peek(--bi);
+ if (b1 != b2) return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equalsIgnoreCase(Buffer b)
+ {
+ if (b==this)
+ return true;
+
+ // reject different lengths
+ if (b==null || b.length() != length())
+ return false;
+
+ // reject AbstractBuffer with different hash value
+ if (_hash != 0 && b instanceof AbstractBuffer)
+ {
+ AbstractBuffer ab = (AbstractBuffer) b;
+ if (ab._hash != 0 && _hash != ab._hash) return false;
+ }
+
+ // Nothing for it but to do the hard grind.
+ int get=getIndex();
+ int bi=b.putIndex();
+ byte[] barray=b.array();
+ if (barray==null)
+ {
+ for (int i = putIndex(); i-->get;)
+ {
+ byte b1 = _bytes[i];
+ byte b2 = b.peek(--bi);
+ if (b1 != b2)
+ {
+ if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
+ if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
+ if (b1 != b2) return false;
+ }
+ }
+ }
+ else
+ {
+ for (int i = putIndex(); i-->get;)
+ {
+ byte b1 = _bytes[i];
+ byte b2 = barray[--bi];
+ if (b1 != b2)
+ {
+ if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
+ if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
+ if (b1 != b2) return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public byte get()
+ {
+ return _bytes[_get++];
+ }
+
+ @Override
+ public int hashCode()
+ {
+ if (_hash == 0 || _hashGet!=_get || _hashPut!=_put)
+ {
+ int get=getIndex();
+ for (int i = putIndex(); i-- >get;)
+ {
+ byte b = _bytes[i];
+ if ('a' <= b && b <= 'z')
+ b = (byte) (b - 'a' + 'A');
+ _hash = 31 * _hash + b;
+ }
+ if (_hash == 0)
+ _hash = -1;
+ _hashGet=_get;
+ _hashPut=_put;
+ }
+ return _hash;
+ }
+
+ public byte peek(int index)
+ {
+ return _bytes[index];
+ }
+
+ public int peek(int index, byte[] b, int offset, int length)
+ {
+ int l = length;
+ if (index + l > capacity())
+ {
+ l = capacity() - index;
+ if (l==0)
+ return -1;
+ }
+
+ if (l < 0)
+ return -1;
+
+ System.arraycopy(_bytes, index, b, offset, l);
+ return l;
+ }
+
+ public void poke(int index, byte b)
+ {
+ _bytes[index] = b;
+ }
+
+ @Override
+ public int poke(int index, Buffer src)
+ {
+ _hash=0;
+
+ int length=src.length();
+ if (index + length > capacity())
+ {
+ length=capacity()-index;
+ }
+
+ byte[] src_array = src.array();
+ if (src_array != null)
+ System.arraycopy(src_array, src.getIndex(), _bytes, index, length);
+ else
+ {
+ int s=src.getIndex();
+ for (int i=0;i<length;i++)
+ _bytes[index++]=src.peek(s++);
+ }
+
+ return length;
+ }
+
+ @Override
+ public int poke(int index, byte[] b, int offset, int length)
+ {
+ _hash=0;
+
+ if (index + length > capacity())
+ {
+ length=capacity()-index;
+ }
+
+ System.arraycopy(b, offset, _bytes, index, length);
+
+ return length;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int space()
+ {
+ return _bytes.length - _put;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class CaseInsensitive extends ByteArrayBuffer implements Buffer.CaseInsensitve
+ {
+ public CaseInsensitive(String s)
+ {
+ super(s);
+ }
+
+ public CaseInsensitive(byte[] b, int o, int l, int rw)
+ {
+ super(b,o,l,rw);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ return obj instanceof Buffer && equalsIgnoreCase((Buffer)obj);
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ConnectedEndPoint.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ConnectedEndPoint.java
new file mode 100644
index 00000000..8959c0b7
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ConnectedEndPoint.java
@@ -0,0 +1,25 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+public interface ConnectedEndPoint extends EndPoint
+{
+ Connection getConnection();
+ void setConnection(Connection connection);
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Connection.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Connection.java
new file mode 100644
index 00000000..8795f5e3
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/Connection.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+
+/* ------------------------------------------------------------ */
+/** Abstract Connection used by Jetty Connectors.
+ * <p>
+ * Jetty will call the handle method of a connection when there is work
+ * to be done on the connection. For blocking connections, this is soon
+ * as the connection is open and handle will keep being called until the
+ * connection is closed. For non-blocking connections, handle will only
+ * be called if there are bytes to be read or the connection becomes writable
+ * after being write blocked.
+ *
+ * @see org.eclipse.jetty.io.nio.SelectorManager
+ */
+public interface Connection
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Handle the connection.
+ * @return The Connection to use for the next handling of the connection.
+ * This allows protocol upgrades and support for CONNECT.
+ * @throws IOException if the handling of I/O operations fail
+ */
+ Connection handle() throws IOException;
+
+ /**
+ * <p>The semantic of this method is to return true to indicate interest in further reads,
+ * or false otherwise, but it is misnamed and should be really called <code>isReadInterested()</code>.</p>
+ *
+ * @return true to indicate interest in further reads, false otherwise
+ */
+ // TODO: rename to isReadInterested() in the next release
+ boolean isSuspended();
+
+ /**
+ * Called after the connection is closed
+ */
+ void onClose();
+
+ /**
+ * Called when the connection idle timeout expires
+ * @param idleForMs how long the connection has been idle
+ * @see #isIdle()
+ */
+ void onIdleExpired(long idleForMs);
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/EndPoint.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/EndPoint.java
new file mode 100644
index 00000000..5c183fdc
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/EndPoint.java
@@ -0,0 +1,131 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+
+/**
+ *
+ * A transport EndPoint
+ */
+public interface EndPoint
+{
+ /**
+ * Shutdown any backing output stream associated with the endpoint
+ */
+ void shutdownOutput() throws IOException;
+
+ boolean isOutputShutdown();
+
+ /**
+ * Shutdown any backing input stream associated with the endpoint
+ */
+ void shutdownInput() throws IOException;
+
+ boolean isInputShutdown();
+
+ /**
+ * Close any backing stream associated with the endpoint
+ */
+ void close() throws IOException;
+
+ /**
+ * Fill the buffer from the current putIndex to it's capacity from whatever
+ * byte source is backing the buffer. The putIndex is increased if bytes filled.
+ * The buffer may chose to do a compact before filling.
+ * @return an <code>int</code> value indicating the number of bytes
+ * filled or -1 if EOF is reached.
+ * @throws EofException If input is shutdown or the endpoint is closed.
+ */
+ int fill(Buffer buffer) throws IOException;
+
+ /**
+ * Flush the buffer from the current getIndex to it's putIndex using whatever byte
+ * sink is backing the buffer. The getIndex is updated with the number of bytes flushed.
+ * Any mark set is cleared.
+ * If the entire contents of the buffer are flushed, then an implicit empty() is done.
+ *
+ * @param buffer The buffer to flush. This buffers getIndex is updated.
+ * @return the number of bytes written
+ * @throws EofException If the endpoint is closed or output is shutdown.
+ */
+ int flush(Buffer buffer) throws IOException;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The local IP address to which this <code>EndPoint</code> is bound, or <code>null</code>
+ * if this <code>EndPoint</code> does not represent a network connection.
+ */
+ public String getLocalAddr();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The local port number on which this <code>EndPoint</code> is listening, or <code>0</code>
+ * if this <code>EndPoint</code> does not represent a network connection.
+ */
+ public int getLocalPort();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The remote IP address to which this <code>EndPoint</code> is connected, or <code>null</code>
+ * if this <code>EndPoint</code> does not represent a network connection.
+ */
+ public String getRemoteAddr();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The remote port number to which this <code>EndPoint</code> is connected, or <code>0</code>
+ * if this <code>EndPoint</code> does not represent a network connection.
+ */
+ public int getRemotePort();
+
+ /* ------------------------------------------------------------ */
+ public boolean isBlocking();
+
+ /* ------------------------------------------------------------ */
+ public boolean blockWritable(long millisecs) throws IOException;
+
+ /* ------------------------------------------------------------ */
+ public boolean isOpen();
+
+ /* ------------------------------------------------------------ */
+ /** Flush any buffered output.
+ * May fail to write all data if endpoint is non-blocking
+ * @throws EofException If the endpoint is closed or output is shutdown.
+ */
+ public void flush() throws IOException;
+
+ /* ------------------------------------------------------------ */
+ /** Get the max idle time in ms.
+ * <p>The max idle time is the time the endpoint can be idle before
+ * extraordinary handling takes place. This loosely corresponds to
+ * the {@link java.net.Socket#getSoTimeout()} for blocking connections,
+ * but {@link AsyncEndPoint} implementations must use other mechanisms
+ * to implement the max idle time.
+ * @return the max idle time in ms or if ms <= 0 implies an infinite timeout
+ */
+ public int getMaxIdleTime();
+
+ /* ------------------------------------------------------------ */
+ /** Set the max idle time.
+ * @param timeMs the max idle time in MS. Timeout <= 0 implies an infinite timeout
+ * @throws IOException if the timeout cannot be set.
+ */
+ public void setMaxIdleTime(int timeMs) throws IOException;
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/EofException.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/EofException.java
new file mode 100644
index 00000000..0e6722a1
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/EofException.java
@@ -0,0 +1,47 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.EOFException;
+
+
+/* ------------------------------------------------------------ */
+/** A Jetty specialization of EOFException.
+ * <p> This is thrown by Jetty to distinguish between EOF received from
+ * the connection, vs and EOF thrown by some application talking to some other file/socket etc.
+ * The only difference in handling is that Jetty EOFs are logged less verbosely.
+ */
+@SuppressWarnings("serial")
+public class EofException extends EOFException
+{
+ public EofException()
+ {
+ }
+
+ public EofException(String reason)
+ {
+ super(reason);
+ }
+
+ public EofException(Throwable th)
+ {
+ if (th!=null)
+ initCause(th);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/SimpleBuffers.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/SimpleBuffers.java
new file mode 100644
index 00000000..7d95d473
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/SimpleBuffers.java
@@ -0,0 +1,102 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+/* ------------------------------------------------------------ */
+/** SimpleBuffers.
+ * Simple implementation of Buffers holder.
+ *
+ *
+ */
+public class SimpleBuffers implements Buffers
+{
+ final Buffer _header;
+ final Buffer _buffer;
+ boolean _headerOut;
+ boolean _bufferOut;
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public SimpleBuffers(Buffer header, Buffer buffer)
+ {
+ _header=header;
+ _buffer=buffer;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Buffer getBuffer()
+ {
+ synchronized(this)
+ {
+ if (_buffer!=null && !_bufferOut)
+ {
+ _bufferOut=true;
+ return _buffer;
+ }
+
+ if (_buffer!=null && _header!=null && _header.capacity()==_buffer.capacity() && !_headerOut)
+ {
+ _headerOut=true;
+ return _header;
+ }
+
+ if (_buffer!=null)
+ return new ByteArrayBuffer(_buffer.capacity());
+ return new ByteArrayBuffer(4096);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Buffer getHeader()
+ {
+ synchronized(this)
+ {
+ if (_header!=null && !_headerOut)
+ {
+ _headerOut=true;
+ return _header;
+ }
+
+ if (_buffer!=null && _header!=null && _header.capacity()==_buffer.capacity() && !_bufferOut)
+ {
+ _bufferOut=true;
+ return _buffer;
+ }
+
+ if (_header!=null)
+ return new ByteArrayBuffer(_header.capacity());
+ return new ByteArrayBuffer(4096);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void returnBuffer(Buffer buffer)
+ {
+ synchronized(this)
+ {
+ buffer.clear();
+ if (buffer==_header)
+ _headerOut=false;
+ if (buffer==_buffer)
+ _bufferOut=false;
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ThreadLocalBuffers.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ThreadLocalBuffers.java
new file mode 100644
index 00000000..c5e518b1
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/ThreadLocalBuffers.java
@@ -0,0 +1,120 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Buffer pool.
+ * simple unbounded pool of buffers for header, request and response sizes.
+ *
+ */
+public class ThreadLocalBuffers extends AbstractBuffers
+{
+ /* ------------------------------------------------------------ */
+ private final ThreadLocal<ThreadBuffers> _buffers=new ThreadLocal<ThreadBuffers>()
+ {
+ @Override
+ protected ThreadBuffers initialValue()
+ {
+ return new ThreadBuffers();
+ }
+ };
+
+ /* ------------------------------------------------------------ */
+ public ThreadLocalBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType)
+ {
+ super(headerType,headerSize,bufferType,bufferSize,otherType);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Buffer getBuffer()
+ {
+ ThreadBuffers buffers = _buffers.get();
+ if (buffers._buffer!=null)
+ {
+ Buffer b=buffers._buffer;
+ buffers._buffer=null;
+ return b;
+ }
+
+ if (buffers._other!=null && isBuffer(buffers._other))
+ {
+ Buffer b=buffers._other;
+ buffers._other=null;
+ return b;
+ }
+
+ return newBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public Buffer getHeader()
+ {
+ ThreadBuffers buffers = _buffers.get();
+ if (buffers._header!=null)
+ {
+ Buffer b=buffers._header;
+ buffers._header=null;
+ return b;
+ }
+
+ if (buffers._other!=null && isHeader(buffers._other))
+ {
+ Buffer b=buffers._other;
+ buffers._other=null;
+ return b;
+ }
+
+ return newHeader();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void returnBuffer(Buffer buffer)
+ {
+ buffer.clear();
+ if (buffer.isVolatile() || buffer.isImmutable())
+ return;
+
+ ThreadBuffers buffers = _buffers.get();
+
+ if (buffers._header==null && isHeader(buffer))
+ buffers._header=buffer;
+ else if (buffers._buffer==null && isBuffer(buffer))
+ buffers._buffer=buffer;
+ else
+ buffers._other=buffer;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return "{{"+getHeaderSize()+","+getBufferSize()+"}}";
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ protected static class ThreadBuffers
+ {
+ Buffer _buffer;
+ Buffer _header;
+ Buffer _other;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/View.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/View.java
new file mode 100644
index 00000000..0ad7200f
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/View.java
@@ -0,0 +1,232 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+/**
+ * A View on another buffer. Allows operations that do not change the _content or
+ * indexes of the backing buffer.
+ *
+ *
+ *
+ */
+public class View extends AbstractBuffer
+{
+ Buffer _buffer;
+
+ /**
+ * @param buffer The <code>Buffer</code> on which we are presenting a <code>View</code>.
+ * @param mark The initial value of the {@link Buffer#markIndex mark index}
+ * @param get The initial value of the {@link Buffer#getIndex get index}
+ * @param put The initial value of the {@link Buffer#putIndex put index}
+ * @param access The access level - one of the constants from {@link Buffer}.
+ */
+ public View(Buffer buffer, int mark, int get, int put,int access)
+ {
+ super(READWRITE,!buffer.isImmutable());
+ _buffer=buffer.buffer();
+ setPutIndex(put);
+ setGetIndex(get);
+ setMarkIndex(mark);
+ _access=access;
+ }
+
+ public View(Buffer buffer)
+ {
+ super(READWRITE,!buffer.isImmutable());
+ _buffer=buffer.buffer();
+ setPutIndex(buffer.putIndex());
+ setGetIndex(buffer.getIndex());
+ setMarkIndex(buffer.markIndex());
+ _access=buffer.isReadOnly()?READONLY:READWRITE;
+ }
+
+ public View()
+ {
+ super(READWRITE,true);
+ }
+
+ /**
+ * Update view to buffer
+ */
+ public void update(Buffer buffer)
+ {
+ _access=READWRITE;
+ _buffer=buffer.buffer();
+ setGetIndex(0);
+ setPutIndex(buffer.putIndex());
+ setGetIndex(buffer.getIndex());
+ setMarkIndex(buffer.markIndex());
+ _access=buffer.isReadOnly()?READONLY:READWRITE;
+ }
+
+ public void update(int get, int put)
+ {
+ int a=_access;
+ _access=READWRITE;
+ setGetIndex(0);
+ setPutIndex(put);
+ setGetIndex(get);
+ setMarkIndex(-1);
+ _access=a;
+ }
+
+ /**
+ * @return The {@link Buffer#array()} from the underlying buffer.
+ */
+ public byte[] array()
+ {
+ return _buffer.array();
+ }
+
+ /**
+ * @return The {@link Buffer#buffer()} from the underlying buffer.
+ */
+ @Override
+ public Buffer buffer()
+ {
+ return _buffer.buffer();
+ }
+
+ /**
+ * @return The {@link Buffer#capacity} of the underlying buffer.
+ */
+ public int capacity()
+ {
+ return _buffer.capacity();
+ }
+
+ /**
+ *
+ */
+ @Override
+ public void clear()
+ {
+ setMarkIndex(-1);
+ setGetIndex(0);
+ setPutIndex(_buffer.getIndex());
+ setGetIndex(_buffer.getIndex());
+ }
+
+ /**
+ *
+ */
+ @Override
+ public void compact()
+ {
+ // TODO
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj)
+ {
+ return this==obj ||((obj instanceof Buffer)&& obj.equals(this)) || super.equals(obj);
+ }
+
+ /**
+ * @return Whether the underlying buffer is {@link Buffer#isReadOnly read only}
+ */
+ @Override
+ public boolean isReadOnly()
+ {
+ return _buffer.isReadOnly();
+ }
+
+ /**
+ * @return Whether the underlying buffer is {@link Buffer#isVolatile volatile}
+ */
+ @Override
+ public boolean isVolatile()
+ {
+ return true;
+ }
+
+ /**
+ * @return The result of calling {@link Buffer#peek(int)} on the underlying buffer
+ */
+ public byte peek(int index)
+ {
+ return _buffer.peek(index);
+ }
+
+ /**
+ * @return The result of calling {@link Buffer#peek(int, byte[], int, int)} on the underlying buffer
+ */
+ public int peek(int index, byte[] b, int offset, int length)
+ {
+ return _buffer.peek(index,b,offset,length);
+ }
+
+ /**
+ * @param index
+ * @param src
+ */
+ @Override
+ public int poke(int index, Buffer src)
+ {
+ return _buffer.poke(index,src);
+ }
+
+ /**
+ * @param index
+ * @param b
+ */
+ public void poke(int index, byte b)
+ {
+ _buffer.poke(index,b);
+ }
+
+ /**
+ * @param index
+ * @param b
+ * @param offset
+ * @param length
+ */
+ @Override
+ public int poke(int index, byte[] b, int offset, int length)
+ {
+ return _buffer.poke(index,b,offset,length);
+ }
+
+ @Override
+ public String toString()
+ {
+ if (_buffer==null)
+ return "INVALID";
+ return super.toString();
+ }
+
+ public static class CaseInsensitive extends View implements Buffer.CaseInsensitve
+ {
+ public CaseInsensitive()
+ {
+ super();
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ return this==obj ||((obj instanceof Buffer)&&((Buffer)obj).equalsIgnoreCase(this)) || super.equals(obj);
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/AsyncConnection.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/AsyncConnection.java
new file mode 100644
index 00000000..3282fe51
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/AsyncConnection.java
@@ -0,0 +1,28 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Connection;
+
+public interface AsyncConnection extends Connection
+{
+ void onInputShutdown() throws IOException;
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/ChannelEndPoint.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/ChannelEndPoint.java
new file mode 100644
index 00000000..baf456d5
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/ChannelEndPoint.java
@@ -0,0 +1,478 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SocketChannel;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Channel End Point.
+ * <p>Holds the channel and socket for an NIO endpoint.
+ *
+ */
+public class ChannelEndPoint implements EndPoint
+{
+ private static final Logger LOG = Log.getLogger(ChannelEndPoint.class);
+
+ private static final String ALL_INTERFACES = "0.0.0.0";
+
+ protected final ByteChannel _channel;
+ protected final ByteBuffer[] _gather2=new ByteBuffer[2];
+ protected final Socket _socket;
+ protected final InetSocketAddress _local;
+ protected final InetSocketAddress _remote;
+ protected volatile int _maxIdleTime;
+ private volatile boolean _ishut;
+ private volatile boolean _oshut;
+
+ protected ChannelEndPoint(ByteChannel channel, int maxIdleTime) throws IOException
+ {
+ this._channel = channel;
+ _maxIdleTime=maxIdleTime;
+ _socket=(channel instanceof SocketChannel)?((SocketChannel)channel).socket():null;
+ if (_socket!=null)
+ {
+ _local=(InetSocketAddress)_socket.getLocalSocketAddress();
+ _remote=(InetSocketAddress)_socket.getRemoteSocketAddress();
+ _socket.setSoTimeout(_maxIdleTime);
+ }
+ else
+ {
+ _local=_remote=null;
+ }
+ }
+
+ public boolean isBlocking()
+ {
+ return !(_channel instanceof SelectableChannel) || ((SelectableChannel)_channel).isBlocking();
+ }
+
+ public boolean blockReadable(long millisecs) throws IOException
+ {
+ return true;
+ }
+
+ public boolean blockWritable(long millisecs) throws IOException
+ {
+ return true;
+ }
+
+ /*
+ * @see org.eclipse.io.EndPoint#isOpen()
+ */
+ public boolean isOpen()
+ {
+ return _channel.isOpen();
+ }
+
+ /** Shutdown the channel Input.
+ * Cannot be overridden. To override, see {@link #shutdownInput()}
+ * @throws IOException
+ */
+ protected final void shutdownChannelInput() throws IOException
+ {
+ LOG.debug("ishut {}", this);
+ _ishut = true;
+ if (_channel.isOpen())
+ {
+ if (_socket != null)
+ {
+ try
+ {
+ if (!_socket.isInputShutdown())
+ {
+ _socket.shutdownInput();
+ }
+ }
+ catch (SocketException e)
+ {
+ LOG.debug(e.toString());
+ LOG.ignore(e);
+ }
+ finally
+ {
+ if (_oshut)
+ {
+ close();
+ }
+ }
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.io.EndPoint#close()
+ */
+ public void shutdownInput() throws IOException
+ {
+ shutdownChannelInput();
+ }
+
+ protected final void shutdownChannelOutput() throws IOException
+ {
+ LOG.debug("oshut {}",this);
+ _oshut = true;
+ if (_channel.isOpen())
+ {
+ if (_socket != null)
+ {
+ try
+ {
+ if (!_socket.isOutputShutdown())
+ {
+ _socket.shutdownOutput();
+ }
+ }
+ catch (SocketException e)
+ {
+ LOG.debug(e.toString());
+ LOG.ignore(e);
+ }
+ finally
+ {
+ if (_ishut)
+ {
+ close();
+ }
+ }
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.io.EndPoint#close()
+ */
+ public void shutdownOutput() throws IOException
+ {
+ shutdownChannelOutput();
+ }
+
+ public boolean isOutputShutdown()
+ {
+ return _oshut || !_channel.isOpen() || _socket != null && _socket.isOutputShutdown();
+ }
+
+ public boolean isInputShutdown()
+ {
+ return _ishut || !_channel.isOpen() || _socket != null && _socket.isInputShutdown();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.io.EndPoint#close()
+ */
+ public void close() throws IOException
+ {
+ LOG.debug("close {}",this);
+ _channel.close();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer)
+ */
+ public int fill(Buffer buffer) throws IOException
+ {
+ if (_ishut)
+ return -1;
+ Buffer buf = buffer.buffer();
+ int len=0;
+ if (buf instanceof NIOBuffer)
+ {
+ final NIOBuffer nbuf = (NIOBuffer)buf;
+ final ByteBuffer bbuf=nbuf.getByteBuffer();
+
+ //noinspection SynchronizationOnLocalVariableOrMethodParameter
+ try
+ {
+ synchronized(bbuf)
+ {
+ try
+ {
+ bbuf.position(buffer.putIndex());
+ len=_channel.read(bbuf);
+ }
+ finally
+ {
+ buffer.setPutIndex(bbuf.position());
+ bbuf.position(0);
+ }
+ }
+
+ if (len<0 && isOpen())
+ {
+ if (!isInputShutdown())
+ shutdownInput();
+ if (isOutputShutdown())
+ _channel.close();
+ }
+ }
+ catch (IOException x)
+ {
+ LOG.debug("Exception while filling", x);
+ try
+ {
+ if (_channel.isOpen())
+ _channel.close();
+ }
+ catch (Exception xx)
+ {
+ LOG.ignore(xx);
+ }
+
+ if (len>0)
+ throw x;
+ len=-1;
+ }
+ }
+ else
+ {
+ throw new IOException("Not Implemented");
+ }
+
+ return len;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer)
+ */
+ public int flush(Buffer buffer) throws IOException
+ {
+ Buffer buf = buffer.buffer();
+ int len=0;
+ if (buf instanceof NIOBuffer)
+ {
+ final NIOBuffer nbuf = (NIOBuffer)buf;
+ final ByteBuffer bbuf=nbuf.getByteBuffer().asReadOnlyBuffer();
+ try
+ {
+ bbuf.position(buffer.getIndex());
+ bbuf.limit(buffer.putIndex());
+ len=_channel.write(bbuf);
+ }
+ finally
+ {
+ if (len>0)
+ buffer.skip(len);
+ }
+ }
+ else if (buffer.array()!=null)
+ {
+ ByteBuffer b = ByteBuffer.wrap(buffer.array(), buffer.getIndex(), buffer.length());
+ len=_channel.write(b);
+ if (len>0)
+ buffer.skip(len);
+ }
+ else
+ {
+ throw new IOException("Not Implemented");
+ }
+ return len;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
+ */
+ public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
+ {
+ int length=0;
+
+ Buffer buf0 = header==null?null:header.buffer();
+ Buffer buf1 = buffer==null?null:buffer.buffer();
+
+ if (_channel instanceof GatheringByteChannel &&
+ header!=null && header.length()!=0 && buf0 instanceof NIOBuffer &&
+ buffer!=null && buffer.length()!=0 && buf1 instanceof NIOBuffer)
+ {
+ length = gatheringFlush(header,((NIOBuffer)buf0).getByteBuffer(),buffer,((NIOBuffer)buf1).getByteBuffer());
+ }
+ else
+ {
+ // flush header
+ if (header!=null && header.length()>0)
+ length=flush(header);
+
+ // flush buffer
+ if ((header==null || header.length()==0) &&
+ buffer!=null && buffer.length()>0)
+ length+=flush(buffer);
+
+ // flush trailer
+ if ((header==null || header.length()==0) &&
+ (buffer==null || buffer.length()==0) &&
+ trailer!=null && trailer.length()>0)
+ length+=flush(trailer);
+ }
+
+ return length;
+ }
+
+ protected int gatheringFlush(Buffer header, ByteBuffer bbuf0, Buffer buffer, ByteBuffer bbuf1) throws IOException
+ {
+ int length;
+
+ synchronized(this)
+ {
+ // Adjust position indexs of buf0 and buf1
+ bbuf0=bbuf0.asReadOnlyBuffer();
+ bbuf0.position(header.getIndex());
+ bbuf0.limit(header.putIndex());
+ bbuf1=bbuf1.asReadOnlyBuffer();
+ bbuf1.position(buffer.getIndex());
+ bbuf1.limit(buffer.putIndex());
+
+ _gather2[0]=bbuf0;
+ _gather2[1]=bbuf1;
+
+ // do the gathering write.
+ length=(int)((GatheringByteChannel)_channel).write(_gather2);
+
+ int hl=header.length();
+ if (length>hl)
+ {
+ header.clear();
+ buffer.skip(length-hl);
+ }
+ else if (length>0)
+ {
+ header.skip(length);
+ }
+ }
+ return length;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the channel.
+ */
+ public ByteChannel getChannel()
+ {
+ return _channel;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#getLocalAddr()
+ */
+ public String getLocalAddr()
+ {
+ if (_socket==null)
+ return null;
+ if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress())
+ return ALL_INTERFACES;
+ return _local.getAddress().getHostAddress();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#getLocalHost()
+ */
+ public String getLocalHost()
+ {
+ if (_socket==null)
+ return null;
+ if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress())
+ return ALL_INTERFACES;
+ return _local.getAddress().getCanonicalHostName();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#getLocalPort()
+ */
+ public int getLocalPort()
+ {
+ if (_socket==null)
+ return 0;
+ if (_local==null)
+ return -1;
+ return _local.getPort();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#getRemoteAddr()
+ */
+ public String getRemoteAddr()
+ {
+ if (_socket==null)
+ return null;
+ if (_remote==null)
+ return null;
+ return _remote.getAddress().getHostAddress();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#getRemoteHost()
+ */
+ public String getRemoteHost()
+ {
+ if (_socket==null)
+ return null;
+ if (_remote==null)
+ return null;
+ return _remote.getAddress().getCanonicalHostName();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#getRemotePort()
+ */
+ public int getRemotePort()
+ {
+ if (_socket==null)
+ return 0;
+ return _remote==null?-1:_remote.getPort();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void flush()
+ throws IOException
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxIdleTime()
+ {
+ return _maxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.io.bio.StreamEndPoint#setMaxIdleTime(int)
+ */
+ public void setMaxIdleTime(int timeMs) throws IOException
+ {
+ if (_socket!=null && timeMs!=_maxIdleTime)
+ _socket.setSoTimeout(timeMs>0?timeMs:0);
+ _maxIdleTime=timeMs;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/DirectNIOBuffer.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/DirectNIOBuffer.java
new file mode 100644
index 00000000..4bd61b2e
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/DirectNIOBuffer.java
@@ -0,0 +1,226 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+import org.eclipse.jetty.io.AbstractBuffer;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ *
+ *
+ */
+public class DirectNIOBuffer extends AbstractBuffer implements NIOBuffer
+{
+ private static final Logger LOG = Log.getLogger(DirectNIOBuffer.class);
+
+ protected final ByteBuffer _buf;
+
+ public DirectNIOBuffer(int size)
+ {
+ super(READWRITE,NON_VOLATILE);
+ _buf = ByteBuffer.allocateDirect(size);
+ _buf.position(0);
+ _buf.limit(_buf.capacity());
+ }
+
+ public DirectNIOBuffer(ByteBuffer buffer,boolean immutable)
+ {
+ super(immutable?IMMUTABLE:READWRITE,NON_VOLATILE);
+ if (!buffer.isDirect())
+ throw new IllegalArgumentException();
+ _buf = buffer;
+ setGetIndex(buffer.position());
+ setPutIndex(buffer.limit());
+ }
+
+ /**
+ * @param file
+ */
+ public DirectNIOBuffer(File file) throws IOException
+ {
+ super(READONLY,NON_VOLATILE);
+ FileInputStream fis = null;
+ FileChannel fc = null;
+ try
+ {
+ fis = new FileInputStream(file);
+ fc = fis.getChannel();
+ _buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
+ setGetIndex(0);
+ setPutIndex((int)file.length());
+ _access=IMMUTABLE;
+ }
+ finally
+ {
+ if (fc != null) try {fc.close();} catch (IOException e){LOG.ignore(e);}
+ IO.close(fis);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isDirect()
+ {
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] array()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int capacity()
+ {
+ return _buf.capacity();
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte peek(int position)
+ {
+ return _buf.get(position);
+ }
+
+ public int peek(int index, byte[] b, int offset, int length)
+ {
+ int l = length;
+ if (index+l > capacity())
+ {
+ l=capacity()-index;
+ if (l==0)
+ return -1;
+ }
+
+ if (l < 0)
+ return -1;
+ try
+ {
+ _buf.position(index);
+ _buf.get(b,offset,l);
+ }
+ finally
+ {
+ _buf.position(0);
+ }
+
+ return l;
+ }
+
+ public void poke(int index, byte b)
+ {
+ if (isReadOnly()) throw new IllegalStateException(__READONLY);
+ if (index < 0) throw new IllegalArgumentException("index<0: " + index + "<0");
+ if (index > capacity())
+ throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
+ _buf.put(index,b);
+ }
+
+ @Override
+ public int poke(int index, Buffer src)
+ {
+ if (isReadOnly()) throw new IllegalStateException(__READONLY);
+
+ byte[] array=src.array();
+ if (array!=null)
+ {
+ return poke(index,array,src.getIndex(),src.length());
+ }
+ else
+ {
+ Buffer src_buf=src.buffer();
+ if (src_buf instanceof DirectNIOBuffer)
+ {
+ ByteBuffer src_bytebuf = ((DirectNIOBuffer)src_buf)._buf;
+ if (src_bytebuf==_buf)
+ src_bytebuf=_buf.duplicate();
+ try
+ {
+ _buf.position(index);
+ int space = _buf.remaining();
+
+ int length=src.length();
+ if (length>space)
+ length=space;
+
+ src_bytebuf.position(src.getIndex());
+ src_bytebuf.limit(src.getIndex()+length);
+
+ _buf.put(src_bytebuf);
+ return length;
+ }
+ finally
+ {
+ _buf.position(0);
+ src_bytebuf.limit(src_bytebuf.capacity());
+ src_bytebuf.position(0);
+ }
+ }
+ else
+ return super.poke(index,src);
+ }
+ }
+
+ @Override
+ public int poke(int index, byte[] b, int offset, int length)
+ {
+ if (isReadOnly()) throw new IllegalStateException(__READONLY);
+
+ if (index < 0) throw new IllegalArgumentException("index<0: " + index + "<0");
+
+ if (index + length > capacity())
+ {
+ length=capacity()-index;
+ if (length<0)
+ throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
+ }
+
+ try
+ {
+ _buf.position(index);
+
+ int space=_buf.remaining();
+
+ if (length>space)
+ length=space;
+ if (length>0)
+ _buf.put(b,offset,length);
+ return length;
+ }
+ finally
+ {
+ _buf.position(0);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer getByteBuffer()
+ {
+ return _buf;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java
new file mode 100644
index 00000000..e9500f06
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java
@@ -0,0 +1,43 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.ByteArrayBuffer;
+
+public class IndirectNIOBuffer extends ByteArrayBuffer implements NIOBuffer
+{
+ protected final ByteBuffer _buf;
+
+ /* ------------------------------------------------------------ */
+ public IndirectNIOBuffer(int size)
+ {
+ super(size,READWRITE,NON_VOLATILE);
+ _buf = ByteBuffer.wrap(_bytes);
+ _buf.position(0);
+ _buf.limit(_buf.capacity());
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer getByteBuffer()
+ {
+ return _buf;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/NIOBuffer.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/NIOBuffer.java
new file mode 100644
index 00000000..ba4f4b60
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/NIOBuffer.java
@@ -0,0 +1,34 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.Buffer;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ *
+ *
+ */
+public interface NIOBuffer extends Buffer
+{
+ /* ------------------------------------------------------------ */
+ public ByteBuffer getByteBuffer();
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
new file mode 100644
index 00000000..88d7ff7c
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
@@ -0,0 +1,802 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.Locale;
+
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.nio.SelectorManager.SelectSet;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Timeout.Task;
+
+/* ------------------------------------------------------------ */
+/**
+ * An Endpoint that can be scheduled by {@link SelectorManager}.
+ */
+public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPoint, ConnectedEndPoint
+{
+ public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio");
+
+ private final boolean WORK_AROUND_JVM_BUG_6346658 = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win");
+ private final SelectorManager.SelectSet _selectSet;
+ private final SelectorManager _manager;
+ private SelectionKey _key;
+ private final Runnable _handler = new Runnable()
+ {
+ public void run() { handle(); }
+ };
+
+ /** The desired value for {@link SelectionKey#interestOps()} */
+ private int _interestOps;
+
+ /**
+ * The connection instance is the handler for any IO activity on the endpoint.
+ * There is a different type of connection for HTTP, AJP, WebSocket and
+ * ProxyConnect. The connection may change for an SCEP as it is upgraded
+ * from HTTP to proxy connect or websocket.
+ */
+ private volatile AsyncConnection _connection;
+
+ private static final int STATE_NEEDS_DISPATCH=-1;
+ private static final int STATE_UNDISPATCHED=0;
+ private static final int STATE_DISPATCHED=1;
+ private static final int STATE_ASYNC=2;
+ private int _state;
+
+ private boolean _onIdle;
+
+ /** true if the last write operation succeed and wrote all offered bytes */
+ private volatile boolean _writable = true;
+
+
+ /** True if a thread has is blocked in {@link #blockReadable(long)} */
+ private boolean _readBlocked;
+
+ /** True if a thread has is blocked in {@link #blockWritable(long)} */
+ private boolean _writeBlocked;
+
+ /** true if {@link SelectSet#destroyEndPoint(SelectChannelEndPoint)} has not been called */
+ private boolean _open;
+
+ private volatile long _idleTimestamp;
+ private volatile boolean _checkIdle;
+
+ private boolean _interruptable;
+
+ private boolean _ishut;
+
+ /* ------------------------------------------------------------ */
+ public SelectChannelEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key, int maxIdleTime)
+ throws IOException
+ {
+ super(channel, maxIdleTime);
+
+ _manager = selectSet.getManager();
+ _selectSet = selectSet;
+ _state=STATE_UNDISPATCHED;
+ _onIdle=false;
+ _open=true;
+ _key = key;
+
+ setCheckForIdle(true);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Connection getConnection()
+ {
+ return _connection;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setConnection(Connection connection)
+ {
+ Connection old=_connection;
+ _connection=(AsyncConnection)connection;
+ if (old!=null && old!=_connection)
+ _manager.endPointUpgraded(this,old);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Called by selectSet to schedule handling
+ *
+ */
+ public void schedule()
+ {
+ synchronized (this)
+ {
+ // If there is no key, then do nothing
+ if (_key == null || !_key.isValid())
+ {
+ _readBlocked=false;
+ _writeBlocked=false;
+ this.notifyAll();
+ return;
+ }
+
+ // If there are threads dispatched reading and writing
+ if (_readBlocked || _writeBlocked)
+ {
+ // assert _dispatched;
+ if (_readBlocked && _key.isReadable())
+ _readBlocked=false;
+ if (_writeBlocked && _key.isWritable())
+ _writeBlocked=false;
+
+ // wake them up is as good as a dispatched.
+ this.notifyAll();
+
+ // we are not interested in further selecting
+ _key.interestOps(0);
+ if (_state<STATE_DISPATCHED)
+ updateKey();
+ return;
+ }
+
+ // Remove writeable op
+ if ((_key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE && (_key.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE)
+ {
+ // Remove writeable op
+ _interestOps = _key.interestOps() & ~SelectionKey.OP_WRITE;
+ _key.interestOps(_interestOps);
+ _writable = true; // Once writable is in ops, only removed with dispatch.
+ }
+
+ // If dispatched, then deregister interest
+ if (_state>=STATE_DISPATCHED)
+ _key.interestOps(0);
+ else
+ {
+ // other wise do the dispatch
+ dispatch();
+ if (_state>=STATE_DISPATCHED && !_selectSet.getManager().isDeferringInterestedOps0())
+ {
+ _key.interestOps(0);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void asyncDispatch()
+ {
+ synchronized(this)
+ {
+ switch(_state)
+ {
+ case STATE_NEEDS_DISPATCH:
+ case STATE_UNDISPATCHED:
+ dispatch();
+ break;
+
+ case STATE_DISPATCHED:
+ case STATE_ASYNC:
+ _state=STATE_ASYNC;
+ break;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dispatch()
+ {
+ synchronized(this)
+ {
+ if (_state<=STATE_UNDISPATCHED)
+ {
+ if (_onIdle)
+ _state = STATE_NEEDS_DISPATCH;
+ else
+ {
+ _state = STATE_DISPATCHED;
+ boolean dispatched = _manager.dispatch(_handler);
+ if(!dispatched)
+ {
+ _state = STATE_NEEDS_DISPATCH;
+ LOG.warn("Dispatched Failed! "+this+" to "+_manager);
+ updateKey();
+ }
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Called when a dispatched thread is no longer handling the endpoint.
+ * The selection key operations are updated.
+ * @return If false is returned, the endpoint has been redispatched and
+ * thread must keep handling the endpoint.
+ */
+ protected boolean undispatch()
+ {
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case STATE_ASYNC:
+ _state=STATE_DISPATCHED;
+ return false;
+
+ default:
+ _state=STATE_UNDISPATCHED;
+ updateKey();
+ return true;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void cancelTimeout(Task task)
+ {
+ getSelectSet().cancelTimeout(task);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void scheduleTimeout(Task task, long timeoutMs)
+ {
+ getSelectSet().scheduleTimeout(task,timeoutMs);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setCheckForIdle(boolean check)
+ {
+ if (check)
+ {
+ _idleTimestamp=System.currentTimeMillis();
+ _checkIdle=true;
+ }
+ else
+ _checkIdle=false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isCheckForIdle()
+ {
+ return _checkIdle;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void notIdle()
+ {
+ _idleTimestamp=System.currentTimeMillis();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void checkIdleTimestamp(long now)
+ {
+ if (isCheckForIdle() && _maxIdleTime>0)
+ {
+ final long idleForMs=now-_idleTimestamp;
+
+ if (idleForMs>_maxIdleTime)
+ {
+ // Don't idle out again until onIdleExpired task completes.
+ setCheckForIdle(false);
+ _manager.dispatch(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ onIdleExpired(idleForMs);
+ }
+ finally
+ {
+ setCheckForIdle(true);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void onIdleExpired(long idleForMs)
+ {
+ try
+ {
+ synchronized (this)
+ {
+ _onIdle=true;
+ }
+
+ _connection.onIdleExpired(idleForMs);
+ }
+ finally
+ {
+ synchronized (this)
+ {
+ _onIdle=false;
+ if (_state==STATE_NEEDS_DISPATCH)
+ dispatch();
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int fill(Buffer buffer) throws IOException
+ {
+ int fill=super.fill(buffer);
+ if (fill>0)
+ notIdle();
+ return fill;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ @Override
+ public int flush(Buffer buffer) throws IOException
+ {
+ int l = super.flush(buffer);
+
+ // If there was something to write and it wasn't written, then we are not writable.
+ if (l==0 && buffer!=null && buffer.hasContent())
+ {
+ synchronized (this)
+ {
+ _writable=false;
+ if (_state<STATE_DISPATCHED)
+ updateKey();
+ }
+ }
+ else if (l>0)
+ {
+ _writable=true;
+ notIdle();
+ }
+
+ return l;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Allows thread to block waiting for further events.
+ */
+ @SuppressWarnings("serial")
+ @Override
+ public boolean blockReadable(long timeoutMs) throws IOException
+ {
+ synchronized (this)
+ {
+ if (isInputShutdown())
+ throw new EofException();
+
+ long now=_selectSet.getNow();
+ long end=now+timeoutMs;
+ boolean check=isCheckForIdle();
+ setCheckForIdle(true);
+ try
+ {
+ _readBlocked=true;
+ while (!isInputShutdown() && _readBlocked)
+ {
+ try
+ {
+ updateKey();
+ this.wait(timeoutMs>0?(end-now):10000);
+ }
+ catch (final InterruptedException e)
+ {
+ LOG.warn(e);
+ if (_interruptable)
+ throw new InterruptedIOException(){{this.initCause(e);}};
+ }
+ finally
+ {
+ now=_selectSet.getNow();
+ }
+
+ if (_readBlocked && timeoutMs>0 && now>=end)
+ return false;
+ }
+ }
+ finally
+ {
+ _readBlocked=false;
+ setCheckForIdle(check);
+ }
+ }
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Allows thread to block waiting for further events.
+ */
+ @SuppressWarnings("serial")
+ @Override
+ public boolean blockWritable(long timeoutMs) throws IOException
+ {
+ synchronized (this)
+ {
+ if (isOutputShutdown())
+ throw new EofException();
+
+ long now=_selectSet.getNow();
+ long end=now+timeoutMs;
+ boolean check=isCheckForIdle();
+ setCheckForIdle(true);
+ try
+ {
+ _writeBlocked=true;
+ while (_writeBlocked && !isOutputShutdown())
+ {
+ try
+ {
+ updateKey();
+ this.wait(timeoutMs>0?(end-now):10000);
+ }
+ catch (final InterruptedException e)
+ {
+ LOG.warn(e);
+ if (_interruptable)
+ throw new InterruptedIOException(){{this.initCause(e);}};
+ }
+ finally
+ {
+ now=_selectSet.getNow();
+ }
+ if (_writeBlocked && timeoutMs>0 && now>=end)
+ return false;
+ }
+ }
+ finally
+ {
+ _writeBlocked=false;
+ setCheckForIdle(check);
+ }
+ }
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.io.AsyncEndPoint#scheduleWrite()
+ */
+ public void scheduleWrite()
+ {
+ if (_writable)
+ LOG.debug("Required scheduleWrite {}",this);
+
+ _writable=false;
+ updateKey();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isWritable()
+ {
+ return _writable;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean hasProgressed()
+ {
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Updates selection key. Adds operations types to the selection key as needed. No operations
+ * are removed as this is only done during dispatch. This method records the new key and
+ * schedules a call to doUpdateKey to do the keyChange
+ */
+ private void updateKey()
+ {
+ final boolean changed;
+ synchronized (this)
+ {
+ int current_ops=-1;
+ if (getChannel().isOpen())
+ {
+ boolean read_interest = _readBlocked || (_state<STATE_DISPATCHED && !_connection.isSuspended());
+ boolean write_interest= _writeBlocked || (_state<STATE_DISPATCHED && !_writable);
+
+ _interestOps =
+ ((!_socket.isInputShutdown() && read_interest ) ? SelectionKey.OP_READ : 0)
+ | ((!_socket.isOutputShutdown()&& write_interest) ? SelectionKey.OP_WRITE : 0);
+ try
+ {
+ current_ops = ((_key!=null && _key.isValid())?_key.interestOps():-1);
+ }
+ catch(Exception e)
+ {
+ _key=null;
+ LOG.ignore(e);
+ }
+ }
+ changed=_interestOps!=current_ops;
+ }
+
+ if(changed)
+ {
+ _selectSet.addChange(this);
+ _selectSet.wakeup();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Synchronize the interestOps with the actual key. Call is scheduled by a call to updateKey
+ */
+ void doUpdateKey()
+ {
+ synchronized (this)
+ {
+ if (getChannel().isOpen())
+ {
+ if (_interestOps>0)
+ {
+ if (_key==null || !_key.isValid())
+ {
+ SelectableChannel sc = (SelectableChannel)getChannel();
+ if (sc.isRegistered())
+ {
+ updateKey();
+ }
+ else
+ {
+ try
+ {
+ _key=((SelectableChannel)getChannel()).register(_selectSet.getSelector(),_interestOps,this);
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ if (_key!=null && _key.isValid())
+ {
+ _key.cancel();
+ }
+
+ if (_open)
+ {
+ _selectSet.destroyEndPoint(this);
+ }
+ _open=false;
+ _key = null;
+ }
+ }
+ }
+ else
+ {
+ _key.interestOps(_interestOps);
+ }
+ }
+ else
+ {
+ if (_key!=null && _key.isValid())
+ _key.interestOps(0);
+ else
+ _key=null;
+ }
+ }
+ else
+ {
+ if (_key!=null && _key.isValid())
+ _key.cancel();
+
+ if (_open)
+ {
+ _open=false;
+ _selectSet.destroyEndPoint(this);
+ }
+ _key = null;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ protected void handle()
+ {
+ boolean dispatched=true;
+ try
+ {
+ while(dispatched)
+ {
+ try
+ {
+ while(true)
+ {
+ final AsyncConnection next = (AsyncConnection)_connection.handle();
+ if (next!=_connection)
+ {
+ LOG.debug("{} replaced {}",next,_connection);
+ Connection old=_connection;
+ _connection=next;
+ _manager.endPointUpgraded(this,old);
+ continue;
+ }
+ break;
+ }
+ }
+ catch (ClosedChannelException e)
+ {
+ LOG.ignore(e);
+ }
+ catch (EofException e)
+ {
+ LOG.debug("EOF", e);
+ try{close();}
+ catch(IOException e2){LOG.ignore(e2);}
+ }
+ catch (IOException e)
+ {
+ LOG.warn(e.toString());
+ try{close();}
+ catch(IOException e2){LOG.ignore(e2);}
+ }
+ catch (Throwable e)
+ {
+ LOG.warn("handle failed", e);
+ try{close();}
+ catch(IOException e2){LOG.ignore(e2);}
+ }
+ finally
+ {
+ if (!_ishut && isInputShutdown() && isOpen())
+ {
+ _ishut=true;
+ try
+ {
+ _connection.onInputShutdown();
+ }
+ catch(Throwable x)
+ {
+ LOG.warn("onInputShutdown failed", x);
+ try{close();}
+ catch(IOException e2){LOG.ignore(e2);}
+ }
+ finally
+ {
+ updateKey();
+ }
+ }
+ dispatched=!undispatch();
+ }
+ }
+ }
+ finally
+ {
+ if (dispatched)
+ {
+ dispatched=!undispatch();
+ while (dispatched)
+ {
+ LOG.warn("SCEP.run() finally DISPATCHED");
+ dispatched=!undispatch();
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.nio.ChannelEndPoint#close()
+ */
+ @Override
+ public void close() throws IOException
+ {
+ // On unix systems there is a JVM issue that if you cancel before closing, it can
+ // cause the selector to block waiting for a channel to close and that channel can
+ // block waiting for the remote end. But on windows, if you don't cancel before a
+ // close, then the selector can block anyway!
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=357318
+ if (WORK_AROUND_JVM_BUG_6346658)
+ {
+ try
+ {
+ SelectionKey key = _key;
+ if (key!=null)
+ key.cancel();
+ }
+ catch (Throwable e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+ try
+ {
+ super.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ finally
+ {
+ updateKey();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ // Do NOT use synchronized (this)
+ // because it's very easy to deadlock when debugging is enabled.
+ // We do a best effort to print the right toString() and that's it.
+ SelectionKey key = _key;
+ String keyString = "";
+ if (key != null)
+ {
+ if (key.isValid())
+ {
+ if (key.isReadable())
+ keyString += "r";
+ if (key.isWritable())
+ keyString += "w";
+ }
+ else
+ {
+ keyString += "!";
+ }
+ }
+ else
+ {
+ keyString += "-";
+ }
+ return String.format(Locale.getDefault(),
+ "SCEP@%x{l(%s)<->r(%s),s=%d,open=%b,ishut=%b,oshut=%b,rb=%b,wb=%b,w=%b,i=%d%s}-{%s}",
+ hashCode(),
+ _socket.getRemoteSocketAddress(),
+ _socket.getLocalSocketAddress(),
+ _state,
+ isOpen(),
+ isInputShutdown(),
+ isOutputShutdown(),
+ _readBlocked,
+ _writeBlocked,
+ _writable,
+ _interestOps,
+ keyString,
+ _connection);
+ }
+
+ /* ------------------------------------------------------------ */
+ public SelectSet getSelectSet()
+ {
+ return _selectSet;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Don't set the SoTimeout
+ * @see org.eclipse.jetty.io.nio.ChannelEndPoint#setMaxIdleTime(int)
+ */
+ @Override
+ public void setMaxIdleTime(int timeMs) throws IOException
+ {
+ _maxIdleTime=timeMs;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SelectorManager.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SelectorManager.java
new file mode 100644
index 00000000..dd4e6816
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SelectorManager.java
@@ -0,0 +1,891 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.IOException;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.Channel;
+import java.nio.channels.ClosedSelectorException;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Timeout;
+import org.eclipse.jetty.util.thread.Timeout.Task;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * The Selector Manager manages and number of SelectSets to allow
+ * NIO scheduling to scale to large numbers of connections.
+ * <p>
+ */
+public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
+{
+ public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio");
+
+ private static final int __MONITOR_PERIOD=Integer.getInteger("org.eclipse.jetty.io.nio.MONITOR_PERIOD",1000).intValue();
+ private static final int __MAX_SELECTS=Integer.getInteger("org.eclipse.jetty.io.nio.MAX_SELECTS",100000).intValue();
+ private static final int __BUSY_PAUSE=Integer.getInteger("org.eclipse.jetty.io.nio.BUSY_PAUSE",50).intValue();
+ private static final int __IDLE_TICK=Integer.getInteger("org.eclipse.jetty.io.nio.IDLE_TICK",400).intValue();
+
+ private int _maxIdleTime;
+ private int _lowResourcesMaxIdleTime;
+ private long _lowResourcesConnections;
+ private SelectSet[] _selectSet;
+ private int _selectSets=1;
+ private volatile int _set=0;
+ private boolean _deferringInterestedOps0=true;
+ private int _selectorPriorityDelta=0;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the max idle time
+ */
+ public long getMaxIdleTime()
+ {
+ return _maxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the number of select sets in use
+ */
+ public int getSelectSets()
+ {
+ return _selectSets;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Register a channel
+ * @param channel
+ * @param att Attached Object
+ */
+ public void register(SocketChannel channel, Object att)
+ {
+ // The ++ increment here is not atomic, but it does not matter.
+ // so long as the value changes sometimes, then connections will
+ // be distributed over the available sets.
+
+ int s=_set++;
+ if (s<0)
+ s=-s;
+ s=s%_selectSets;
+ SelectSet[] sets=_selectSet;
+ if (sets!=null)
+ {
+ SelectSet set=sets[s];
+ set.addChange(channel,att);
+ set.wakeup();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return delta The value to add to the selector thread priority.
+ */
+ public int getSelectorPriorityDelta()
+ {
+ return _selectorPriorityDelta;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public abstract boolean dispatch(Runnable task);
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see org.eclipse.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ _selectSet = new SelectSet[_selectSets];
+ for (int i=0;i<_selectSet.length;i++)
+ _selectSet[i]= new SelectSet(i);
+
+ super.doStart();
+
+ // start a thread to Select
+ for (int i=0;i<getSelectSets();i++)
+ {
+ final int id=i;
+ boolean selecting=dispatch(new Runnable()
+ {
+ public void run()
+ {
+ String name=Thread.currentThread().getName();
+ int priority=Thread.currentThread().getPriority();
+ try
+ {
+ SelectSet[] sets=_selectSet;
+ if (sets==null)
+ return;
+ SelectSet set=sets[id];
+
+ Thread.currentThread().setName(name+" Selector"+id);
+ if (getSelectorPriorityDelta()!=0)
+ Thread.currentThread().setPriority(Thread.currentThread().getPriority()+getSelectorPriorityDelta());
+ LOG.debug("Starting {} on {}",Thread.currentThread(),this);
+ while (isRunning())
+ {
+ try
+ {
+ set.doSelect();
+ }
+ catch(IOException e)
+ {
+ LOG.ignore(e);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+ finally
+ {
+ LOG.debug("Stopped {} on {}",Thread.currentThread(),this);
+ Thread.currentThread().setName(name);
+ if (getSelectorPriorityDelta()!=0)
+ Thread.currentThread().setPriority(priority);
+ }
+ }
+
+ });
+
+ if (!selecting)
+ throw new IllegalStateException("!Selecting");
+ }
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ protected void doStop() throws Exception
+ {
+ SelectSet[] sets= _selectSet;
+ _selectSet=null;
+ if (sets!=null)
+ {
+ for (SelectSet set : sets)
+ {
+ if (set!=null)
+ set.stop();
+ }
+ }
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param endpoint
+ */
+ protected abstract void endPointClosed(SelectChannelEndPoint endpoint);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param endpoint
+ */
+ protected abstract void endPointOpened(SelectChannelEndPoint endpoint);
+
+ /* ------------------------------------------------------------ */
+ protected abstract void endPointUpgraded(ConnectedEndPoint endpoint,Connection oldConnection);
+
+ /* ------------------------------------------------------------------------------- */
+ public abstract AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new end point
+ * @param channel
+ * @param selectSet
+ * @param sKey the selection key
+ * @return the new endpoint {@link SelectChannelEndPoint}
+ * @throws IOException
+ */
+ protected abstract SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey sKey) throws IOException;
+
+ /* ------------------------------------------------------------------------------- */
+ protected void connectionFailed(SocketChannel channel,Throwable ex,Object attachment)
+ {
+ LOG.warn(ex+","+channel+","+attachment);
+ LOG.debug(ex);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ AggregateLifeCycle.dumpObject(out,this);
+ AggregateLifeCycle.dump(out,indent,Arrays.asList(_selectSet));
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ public class SelectSet implements Dumpable
+ {
+ private final int _setID;
+ private final Timeout _timeout;
+
+ private final ConcurrentLinkedQueue<Object> _changes = new ConcurrentLinkedQueue<Object>();
+
+ private volatile Selector _selector;
+
+ private volatile Thread _selecting;
+ private int _busySelects;
+ private long _monitorNext;
+ private boolean _pausing;
+ private boolean _paused;
+ private volatile long _idleTick;
+ private ConcurrentMap<SelectChannelEndPoint,Object> _endPoints = new ConcurrentHashMap<SelectChannelEndPoint, Object>();
+
+ /* ------------------------------------------------------------ */
+ SelectSet(int acceptorID) throws Exception
+ {
+ _setID=acceptorID;
+
+ _idleTick = System.currentTimeMillis();
+ _timeout = new Timeout(this);
+ _timeout.setDuration(0L);
+
+ // create a selector;
+ _selector = Selector.open();
+ _monitorNext=System.currentTimeMillis()+__MONITOR_PERIOD;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addChange(Object change)
+ {
+ _changes.add(change);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addChange(SelectableChannel channel, Object att)
+ {
+ if (att==null)
+ addChange(channel);
+ else if (att instanceof EndPoint)
+ addChange(att);
+ else
+ addChange(new ChannelAndAttachment(channel,att));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Select and dispatch tasks found from changes and the selector.
+ *
+ * @throws IOException
+ */
+ public void doSelect() throws IOException
+ {
+ try
+ {
+ _selecting=Thread.currentThread();
+ final Selector selector=_selector;
+ // Stopped concurrently ?
+ if (selector == null)
+ return;
+
+ // Make any key changes required
+ Object change;
+ int changes=_changes.size();
+ while (changes-->0 && (change=_changes.poll())!=null)
+ {
+ Channel ch=null;
+ SelectionKey key=null;
+
+ try
+ {
+ if (change instanceof EndPoint)
+ {
+ // Update the operations for a key.
+ SelectChannelEndPoint endpoint = (SelectChannelEndPoint)change;
+ ch=endpoint.getChannel();
+ endpoint.doUpdateKey();
+ }
+ else if (change instanceof ChannelAndAttachment)
+ {
+ // finish accepting/connecting this connection
+ final ChannelAndAttachment asc = (ChannelAndAttachment)change;
+ final SelectableChannel channel=asc._channel;
+ ch=channel;
+ final Object att = asc._attachment;
+
+ if ((channel instanceof SocketChannel) && ((SocketChannel)channel).isConnected())
+ {
+ key = channel.register(selector,SelectionKey.OP_READ,att);
+ SelectChannelEndPoint endpoint = createEndPoint((SocketChannel)channel,key);
+ key.attach(endpoint);
+ endpoint.schedule();
+ }
+ else if (channel.isOpen())
+ {
+ key = channel.register(selector,SelectionKey.OP_CONNECT,att);
+ }
+ }
+ else if (change instanceof SocketChannel)
+ {
+ // Newly registered channel
+ final SocketChannel channel=(SocketChannel)change;
+ ch=channel;
+ key = channel.register(selector,SelectionKey.OP_READ,null);
+ SelectChannelEndPoint endpoint = createEndPoint(channel,key);
+ key.attach(endpoint);
+ endpoint.schedule();
+ }
+ else if (change instanceof ChangeTask)
+ {
+ ((Runnable)change).run();
+ }
+ else if (change instanceof Runnable)
+ {
+ dispatch((Runnable)change);
+ }
+ else
+ throw new IllegalArgumentException(change.toString());
+ }
+ catch (CancelledKeyException e)
+ {
+ LOG.ignore(e);
+ }
+ catch (Throwable e)
+ {
+ if (isRunning())
+ LOG.warn(e);
+ else
+ LOG.debug(e);
+
+ try
+ {
+ if (ch!=null)
+ ch.close();
+ }
+ catch(IOException e2)
+ {
+ LOG.debug(e2);
+ }
+ }
+ }
+
+
+ // Do and instant select to see if any connections can be handled.
+ int selected=selector.selectNow();
+
+ long now=System.currentTimeMillis();
+
+ // if no immediate things to do
+ if (selected==0 && selector.selectedKeys().isEmpty())
+ {
+ // If we are in pausing mode
+ if (_pausing)
+ {
+ try
+ {
+ Thread.sleep(__BUSY_PAUSE); // pause to reduce impact of busy loop
+ }
+ catch(InterruptedException e)
+ {
+ LOG.ignore(e);
+ }
+ now=System.currentTimeMillis();
+ }
+
+ // workout how long to wait in select
+ _timeout.setNow(now);
+ long to_next_timeout=_timeout.getTimeToNext();
+
+ long wait = _changes.size()==0?__IDLE_TICK:0L;
+ if (wait > 0 && to_next_timeout >= 0 && wait > to_next_timeout)
+ wait = to_next_timeout;
+
+ // If we should wait with a select
+ if (wait>0)
+ {
+ long before=now;
+ selector.select(wait);
+ now = System.currentTimeMillis();
+ _timeout.setNow(now);
+
+ // If we are monitoring for busy selector
+ // and this select did not wait more than 1ms
+ if (__MONITOR_PERIOD>0 && now-before <=1)
+ {
+ // count this as a busy select and if there have been too many this monitor cycle
+ if (++_busySelects>__MAX_SELECTS)
+ {
+ // Start injecting pauses
+ _pausing=true;
+
+ // if this is the first pause
+ if (!_paused)
+ {
+ // Log and dump some status
+ _paused=true;
+ LOG.warn("Selector {} is too busy, pausing!",this);
+ }
+ }
+ }
+ }
+ }
+
+ // have we been destroyed while sleeping
+ if (_selector==null || !selector.isOpen())
+ return;
+
+ // Look for things to do
+ for (SelectionKey key: selector.selectedKeys())
+ {
+ SocketChannel channel=null;
+
+ try
+ {
+ if (!key.isValid())
+ {
+ key.cancel();
+ SelectChannelEndPoint endpoint = (SelectChannelEndPoint)key.attachment();
+ if (endpoint != null)
+ endpoint.doUpdateKey();
+ continue;
+ }
+
+ Object att = key.attachment();
+ if (att instanceof SelectChannelEndPoint)
+ {
+ if (key.isReadable()||key.isWritable())
+ ((SelectChannelEndPoint)att).schedule();
+ }
+ else if (key.isConnectable())
+ {
+ // Complete a connection of a registered channel
+ channel = (SocketChannel)key.channel();
+ boolean connected=false;
+ try
+ {
+ connected=channel.finishConnect();
+ }
+ catch(Exception e)
+ {
+ connectionFailed(channel,e,att);
+ }
+ finally
+ {
+ if (connected)
+ {
+ key.interestOps(SelectionKey.OP_READ);
+ SelectChannelEndPoint endpoint = createEndPoint(channel,key);
+ key.attach(endpoint);
+ endpoint.schedule();
+ }
+ else
+ {
+ key.cancel();
+ channel.close();
+ }
+ }
+ }
+ else
+ {
+ // Wrap readable registered channel in an endpoint
+ channel = (SocketChannel)key.channel();
+ SelectChannelEndPoint endpoint = createEndPoint(channel,key);
+ key.attach(endpoint);
+ if (key.isReadable())
+ endpoint.schedule();
+ }
+ key = null;
+ }
+ catch (CancelledKeyException e)
+ {
+ LOG.ignore(e);
+ }
+ catch (Exception e)
+ {
+ if (isRunning())
+ LOG.warn(e);
+ else
+ LOG.ignore(e);
+
+ try
+ {
+ if (channel!=null)
+ channel.close();
+ }
+ catch(IOException e2)
+ {
+ LOG.debug(e2);
+ }
+
+ if (key != null && !(key.channel() instanceof ServerSocketChannel) && key.isValid())
+ key.cancel();
+ }
+ }
+
+ // Everything always handled
+ selector.selectedKeys().clear();
+
+ now=System.currentTimeMillis();
+ _timeout.setNow(now);
+ Task task = _timeout.expired();
+ while (task!=null)
+ {
+ if (task instanceof Runnable)
+ dispatch((Runnable)task);
+ task = _timeout.expired();
+ }
+
+ // Idle tick
+ if (now-_idleTick>__IDLE_TICK)
+ {
+ _idleTick=now;
+
+ final long idle_now=((_lowResourcesConnections>0 && selector.keys().size()>_lowResourcesConnections))
+ ?(now+_maxIdleTime-_lowResourcesMaxIdleTime)
+ :now;
+
+ dispatch(new Runnable()
+ {
+ public void run()
+ {
+ for (SelectChannelEndPoint endp:_endPoints.keySet())
+ {
+ endp.checkIdleTimestamp(idle_now);
+ }
+ }
+ public String toString() {return "Idle-"+super.toString();}
+ });
+
+ }
+
+ // Reset busy select monitor counts
+ if (__MONITOR_PERIOD>0 && now>_monitorNext)
+ {
+ _busySelects=0;
+ _pausing=false;
+ _monitorNext=now+__MONITOR_PERIOD;
+
+ }
+ }
+ catch (ClosedSelectorException e)
+ {
+ if (isRunning())
+ LOG.warn(e);
+ else
+ LOG.ignore(e);
+ }
+ catch (CancelledKeyException e)
+ {
+ LOG.ignore(e);
+ }
+ finally
+ {
+ _selecting=null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void renewSelector()
+ {
+ try
+ {
+ synchronized (this)
+ {
+ Selector selector=_selector;
+ if (selector==null)
+ return;
+ final Selector new_selector = Selector.open();
+ for (SelectionKey k: selector.keys())
+ {
+ if (!k.isValid() || k.interestOps()==0)
+ continue;
+
+ final SelectableChannel channel = k.channel();
+ final Object attachment = k.attachment();
+
+ if (attachment==null)
+ addChange(channel);
+ else
+ addChange(channel,attachment);
+ }
+ _selector.close();
+ _selector=new_selector;
+ }
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException("recreating selector",e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public SelectorManager getManager()
+ {
+ return SelectorManager.this;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getNow()
+ {
+ return _timeout.getNow();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param task The task to timeout. If it implements Runnable, then
+ * expired will be called from a dispatched thread.
+ *
+ * @param timeoutMs
+ */
+ public void scheduleTimeout(Timeout.Task task, long timeoutMs)
+ {
+ if (!(task instanceof Runnable))
+ throw new IllegalArgumentException("!Runnable");
+ _timeout.schedule(task, timeoutMs);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void cancelTimeout(Timeout.Task task)
+ {
+ task.cancel();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void wakeup()
+ {
+ try
+ {
+ Selector selector = _selector;
+ if (selector!=null)
+ selector.wakeup();
+ }
+ catch(Exception e)
+ {
+ addChange(new ChangeTask()
+ {
+ public void run()
+ {
+ renewSelector();
+ }
+ });
+
+ renewSelector();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private SelectChannelEndPoint createEndPoint(SocketChannel channel, SelectionKey sKey) throws IOException
+ {
+ SelectChannelEndPoint endp = newEndPoint(channel,this,sKey);
+ LOG.debug("created {}",endp);
+ endPointOpened(endp);
+ _endPoints.put(endp,this);
+ return endp;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void destroyEndPoint(SelectChannelEndPoint endp)
+ {
+ LOG.debug("destroyEndPoint {}",endp);
+ _endPoints.remove(endp);
+ endPointClosed(endp);
+ }
+
+ /* ------------------------------------------------------------ */
+ Selector getSelector()
+ {
+ return _selector;
+ }
+
+ /* ------------------------------------------------------------ */
+ void stop() throws Exception
+ {
+ // Spin for a while waiting for selector to complete
+ // to avoid unneccessary closed channel exceptions
+ try
+ {
+ for (int i=0;i<100 && _selecting!=null;i++)
+ {
+ wakeup();
+ Thread.sleep(10);
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.ignore(e);
+ }
+
+ // close endpoints and selector
+ synchronized (this)
+ {
+ Selector selector=_selector;
+ for (SelectionKey key:selector.keys())
+ {
+ if (key==null)
+ continue;
+ Object att=key.attachment();
+ if (att instanceof EndPoint)
+ {
+ EndPoint endpoint = (EndPoint)att;
+ try
+ {
+ endpoint.close();
+ }
+ catch(IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ }
+
+
+ _timeout.cancelAll();
+ try
+ {
+ selector=_selector;
+ if (selector != null)
+ selector.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ _selector=null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_setID)).append("\n");
+
+ Thread selecting = _selecting;
+
+ Object where = "not selecting";
+ StackTraceElement[] trace =selecting==null?null:selecting.getStackTrace();
+ if (trace!=null)
+ {
+ for (StackTraceElement t:trace)
+ if (t.getClassName().startsWith("org.eclipse.jetty."))
+ {
+ where=t;
+ break;
+ }
+ }
+
+ Selector selector=_selector;
+ if (selector!=null)
+ {
+ final ArrayList<Object> dump = new ArrayList<Object>(selector.keys().size()*2);
+ dump.add(where);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ addChange(new ChangeTask()
+ {
+ public void run()
+ {
+ dumpKeyState(dump);
+ latch.countDown();
+ }
+ });
+
+ try
+ {
+ latch.await(5,TimeUnit.SECONDS);
+ }
+ catch(InterruptedException e)
+ {
+ LOG.ignore(e);
+ }
+
+ AggregateLifeCycle.dump(out,indent,dump);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dumpKeyState(List<Object> dumpto)
+ {
+ Selector selector=_selector;
+ Set<SelectionKey> keys = selector.keys();
+ dumpto.add(selector + " keys=" + keys.size());
+ for (SelectionKey key: keys)
+ {
+ if (key.isValid())
+ dumpto.add(key.attachment()+" iOps="+key.interestOps()+" rOps="+key.readyOps());
+ else
+ dumpto.add(key.attachment()+" iOps=-1 rOps=-1");
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ Selector selector=_selector;
+ return String.format(Locale.getDefault(), "%s keys=%d selected=%d",
+ super.toString(),
+ selector != null && selector.isOpen() ? selector.keys().size() : -1,
+ selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private static class ChannelAndAttachment
+ {
+ final SelectableChannel _channel;
+ final Object _attachment;
+
+ public ChannelAndAttachment(SelectableChannel channel, Object attachment)
+ {
+ super();
+ _channel = channel;
+ _attachment = attachment;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isDeferringInterestedOps0()
+ {
+ return _deferringInterestedOps0;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private interface ChangeTask extends Runnable
+ {}
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SslConnection.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SslConnection.java
new file mode 100755
index 00000000..711716ed
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/io/nio/SslConnection.java
@@ -0,0 +1,775 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import android.annotation.SuppressLint;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** SSL Connection.
+ * An AysyncConnection that acts as an interceptor between and EndPoint and another
+ * Connection, that implements TLS encryption using an {@link SSLEngine}.
+ * <p>
+ * The connector uses an {@link AsyncEndPoint} (like {@link SelectChannelEndPoint}) as
+ * it's source/sink of encrypted data. It then provides {@link #getSslEndPoint()} to
+ * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
+ */
+public class SslConnection extends AbstractConnection implements AsyncConnection
+{
+ private final Logger _logger = Log.getLogger("org.eclipse.jetty.io.nio.ssl");
+
+ private static final NIOBuffer __ZERO_BUFFER=new IndirectNIOBuffer(0);
+
+ private static final ThreadLocal<SslBuffers> __buffers = new ThreadLocal<SslBuffers>();
+ private final SSLEngine _engine;
+ private final SSLSession _session;
+ private AsyncConnection _connection;
+ private final SslEndPoint _sslEndPoint;
+ private int _allocations;
+ private SslBuffers _buffers;
+ private NIOBuffer _inbound;
+ private NIOBuffer _unwrapBuf;
+ private NIOBuffer _outbound;
+ private AsyncEndPoint _aEndp;
+ private boolean _allowRenegotiate=true;
+ private boolean _handshook;
+ private boolean _ishut;
+ private boolean _oshut;
+ private final AtomicBoolean _progressed = new AtomicBoolean();
+
+ /* ------------------------------------------------------------ */
+ /* this is a half baked buffer pool
+ */
+ private static class SslBuffers
+ {
+ final NIOBuffer _in;
+ final NIOBuffer _out;
+ final NIOBuffer _unwrap;
+
+ SslBuffers(int packetSize, int appSize)
+ {
+ _in=new IndirectNIOBuffer(packetSize);
+ _out=new IndirectNIOBuffer(packetSize);
+ _unwrap=new IndirectNIOBuffer(appSize);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public SslConnection(SSLEngine engine,EndPoint endp)
+ {
+ this(engine,endp,System.currentTimeMillis());
+ }
+
+ /* ------------------------------------------------------------ */
+ public SslConnection(SSLEngine engine,EndPoint endp, long timeStamp)
+ {
+ super(endp,timeStamp);
+ _engine=engine;
+ _session=_engine.getSession();
+ _aEndp=(AsyncEndPoint)endp;
+ _sslEndPoint = newSslEndPoint();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected SslEndPoint newSslEndPoint()
+ {
+ return new SslEndPoint();
+ }
+
+ /* ------------------------------------------------------------ */
+ private void allocateBuffers()
+ {
+ synchronized (this)
+ {
+ if (_allocations++==0)
+ {
+ if (_buffers==null)
+ {
+ _buffers=__buffers.get();
+ if (_buffers==null)
+ _buffers=new SslBuffers(_session.getPacketBufferSize()*2,_session.getApplicationBufferSize()*2);
+ _inbound=_buffers._in;
+ _outbound=_buffers._out;
+ _unwrapBuf=_buffers._unwrap;
+ __buffers.set(null);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void releaseBuffers()
+ {
+ synchronized (this)
+ {
+ if (--_allocations==0)
+ {
+ if (_buffers!=null &&
+ _inbound.length()==0 &&
+ _outbound.length()==0 &&
+ _unwrapBuf.length()==0)
+ {
+ _inbound=null;
+ _outbound=null;
+ _unwrapBuf=null;
+ __buffers.set(_buffers);
+ _buffers=null;
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Connection handle() throws IOException
+ {
+ try
+ {
+ allocateBuffers();
+
+ boolean progress=true;
+
+ while (progress)
+ {
+ progress=false;
+
+ // If we are handshook let the delegate connection
+ if (_engine.getHandshakeStatus()!=HandshakeStatus.NOT_HANDSHAKING)
+ progress=process(null,null);
+
+ // handle the delegate connection
+ AsyncConnection next = (AsyncConnection)_connection.handle();
+ if (next!=_connection && next!=null)
+ {
+ _connection=next;
+ progress=true;
+ }
+
+ _logger.debug("{} handle {} progress={}", _session, this, progress);
+ }
+ }
+ finally
+ {
+ releaseBuffers();
+
+ if (!_ishut && _sslEndPoint.isInputShutdown() && _sslEndPoint.isOpen())
+ {
+ _ishut=true;
+ try
+ {
+ _connection.onInputShutdown();
+ }
+ catch(Throwable x)
+ {
+ _logger.warn("onInputShutdown failed", x);
+ try{_sslEndPoint.close();}
+ catch(IOException e2){
+ _logger.ignore(e2);}
+ }
+ }
+ }
+
+ return this;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isSuspended()
+ {
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void onClose()
+ {
+ Connection connection = _sslEndPoint.getConnection();
+ if (connection != null && connection != this)
+ connection.onClose();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void onIdleExpired(long idleForMs)
+ {
+ try
+ {
+ _logger.debug("onIdleExpired {}ms on {}",idleForMs,this);
+ if (_endp.isOutputShutdown())
+ _sslEndPoint.close();
+ else
+ _sslEndPoint.shutdownOutput();
+ }
+ catch (IOException e)
+ {
+ _logger.warn(e);
+ super.onIdleExpired(idleForMs);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void onInputShutdown() throws IOException
+ {
+
+ }
+
+ /* ------------------------------------------------------------ */
+ private synchronized boolean process(Buffer toFill, Buffer toFlush) throws IOException
+ {
+ boolean some_progress=false;
+ try
+ {
+ // We need buffers to progress
+ allocateBuffers();
+
+ // if we don't have a buffer to put received data into
+ if (toFill==null)
+ {
+ // use the unwrapbuffer to hold received data.
+ _unwrapBuf.compact();
+ toFill=_unwrapBuf;
+ }
+ // Else if the fill buffer is too small for the SSL session
+ else if (toFill.capacity()<_session.getApplicationBufferSize())
+ {
+ // fill to the temporary unwrapBuffer
+ boolean progress=process(null,toFlush);
+
+ // if we received any data,
+ if (_unwrapBuf!=null && _unwrapBuf.hasContent())
+ {
+ // transfer from temp buffer to fill buffer
+ _unwrapBuf.skip(toFill.put(_unwrapBuf));
+ return true;
+ }
+ else
+ // return progress from recursive call
+ return progress;
+ }
+ // Else if there is some temporary data
+ else if (_unwrapBuf!=null && _unwrapBuf.hasContent())
+ {
+ // transfer from temp buffer to fill buffer
+ _unwrapBuf.skip(toFill.put(_unwrapBuf));
+ return true;
+ }
+
+ // If we are here, we have a buffer ready into which we can put some read data.
+
+ // If we have no data to flush, flush the empty buffer
+ if (toFlush==null)
+ toFlush=__ZERO_BUFFER;
+
+ // While we are making progress processing SSL engine
+ boolean progress=true;
+ while (progress)
+ {
+ progress=false;
+
+ // Do any real IO
+ int filled=0,flushed=0;
+ try
+ {
+ // Read any available data
+ if (_inbound.space() > 0 && (filled = _endp.fill(_inbound)) > 0)
+ progress = true;
+
+ // flush any output data
+ if (_outbound.hasContent() && (flushed=_endp.flush(_outbound)) > 0)
+ progress = true;
+ }
+ catch (IOException e)
+ {
+ _endp.close();
+ throw e;
+ }
+ finally
+ {
+ _logger.debug("{} {} {} filled={}/{} flushed={}/{}",_session,this,_engine.getHandshakeStatus(),filled,_inbound.length(),flushed,_outbound.length());
+ }
+
+ // handle the current hand share status
+ switch(_engine.getHandshakeStatus())
+ {
+ case FINISHED:
+ throw new IllegalStateException();
+
+ case NOT_HANDSHAKING:
+ {
+ // Try unwrapping some application data
+ if (toFill.space()>0 && _inbound.hasContent() && unwrap(toFill))
+ progress=true;
+
+ // Try wrapping some application data
+ if (toFlush.hasContent() && _outbound.space()>0 && wrap(toFlush))
+ progress=true;
+ }
+ break;
+
+ case NEED_TASK:
+ {
+ // A task needs to be run, so run it!
+ Runnable task;
+ while ((task=_engine.getDelegatedTask())!=null)
+ {
+ progress=true;
+ task.run();
+ }
+
+ }
+ break;
+
+ case NEED_WRAP:
+ {
+ // The SSL needs to send some handshake data to the other side
+ if (_handshook && !_allowRenegotiate)
+ _endp.close();
+ else if (wrap(toFlush))
+ progress=true;
+ }
+ break;
+
+ case NEED_UNWRAP:
+ {
+ // The SSL needs to receive some handshake data from the other side
+ if (_handshook && !_allowRenegotiate)
+ _endp.close();
+ else if (!_inbound.hasContent()&&filled==-1)
+ {
+ // No more input coming
+ _endp.shutdownInput();
+ }
+ else if (unwrap(toFill))
+ progress=true;
+ }
+ break;
+ }
+
+ // pass on ishut/oshut state
+ if (_endp.isOpen() && _endp.isInputShutdown() && !_inbound.hasContent())
+ closeInbound();
+
+ if (_endp.isOpen() && _engine.isOutboundDone() && !_outbound.hasContent())
+ _endp.shutdownOutput();
+
+ // remember if any progress has been made
+ some_progress|=progress;
+ }
+
+ // If we are reading into the temp buffer and it has some content, then we should be dispatched.
+ if (toFill==_unwrapBuf && _unwrapBuf.hasContent() && !_connection.isSuspended())
+ _aEndp.dispatch();
+ }
+ finally
+ {
+ releaseBuffers();
+ if (some_progress)
+ _progressed.set(true);
+ }
+ return some_progress;
+ }
+
+ private void closeInbound()
+ {
+ try
+ {
+ _engine.closeInbound();
+ }
+ catch (SSLException x)
+ {
+ _logger.debug(x);
+ }
+ }
+
+ @SuppressLint("TrulyRandom")
+ private synchronized boolean wrap(final Buffer buffer) throws IOException
+ {
+ ByteBuffer bbuf=extractByteBuffer(buffer);
+ final SSLEngineResult result;
+ int encrypted_produced = 0;
+ int decrypted_consumed = 0;
+ synchronized(bbuf)
+ {
+ _outbound.compact();
+ ByteBuffer out_buffer=_outbound.getByteBuffer();
+ synchronized(out_buffer)
+ {
+ try
+ {
+ bbuf.position(buffer.getIndex());
+ bbuf.limit(buffer.putIndex());
+ int decrypted_position = bbuf.position();
+
+ out_buffer.position(_outbound.putIndex());
+ out_buffer.limit(out_buffer.capacity());
+ int encrypted_position = out_buffer.position();
+
+ result=_engine.wrap(bbuf,out_buffer);
+ if (_logger.isDebugEnabled())
+ _logger.debug("{} wrap {} {} consumed={} produced={}",
+ _session,
+ result.getStatus(),
+ result.getHandshakeStatus(),
+ result.bytesConsumed(),
+ result.bytesProduced());
+
+ decrypted_consumed = bbuf.position() - decrypted_position;
+ buffer.skip(decrypted_consumed);
+
+ encrypted_produced = out_buffer.position() - encrypted_position;
+ _outbound.setPutIndex(_outbound.putIndex() + encrypted_produced);
+ }
+ catch(SSLException e)
+ {
+ _logger.debug(String.valueOf(_endp), e);
+ _endp.close();
+ throw e;
+ }
+ catch (Exception x)
+ {
+ throw new IOException(x);
+ }
+ finally
+ {
+ out_buffer.position(0);
+ out_buffer.limit(out_buffer.capacity());
+ bbuf.position(0);
+ bbuf.limit(bbuf.capacity());
+ }
+ }
+ }
+
+ switch(result.getStatus())
+ {
+ case BUFFER_UNDERFLOW:
+ throw new IllegalStateException();
+
+ case BUFFER_OVERFLOW:
+ break;
+
+ case OK:
+ if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
+ _handshook=true;
+ break;
+
+ case CLOSED:
+ _logger.debug("wrap CLOSE {} {}",this,result);
+ if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
+ _endp.close();
+ break;
+
+ default:
+ _logger.debug("{} wrap default {}",_session,result);
+ throw new IOException(result.toString());
+ }
+
+ return decrypted_consumed > 0 || encrypted_produced > 0;
+ }
+
+ private synchronized boolean unwrap(final Buffer buffer) throws IOException
+ {
+ if (!_inbound.hasContent())
+ return false;
+
+ ByteBuffer bbuf=extractByteBuffer(buffer);
+ final SSLEngineResult result;
+ int encrypted_consumed = 0;
+ int decrypted_produced = 0;
+ synchronized(bbuf)
+ {
+ ByteBuffer in_buffer=_inbound.getByteBuffer();
+ synchronized(in_buffer)
+ {
+ try
+ {
+ bbuf.position(buffer.putIndex());
+ bbuf.limit(buffer.capacity());
+ int decrypted_position = bbuf.position();
+
+ in_buffer.position(_inbound.getIndex());
+ in_buffer.limit(_inbound.putIndex());
+ int encrypted_position = in_buffer.position();
+
+ result=_engine.unwrap(in_buffer,bbuf);
+ if (_logger.isDebugEnabled())
+ _logger.debug("{} unwrap {} {} consumed={} produced={}",
+ _session,
+ result.getStatus(),
+ result.getHandshakeStatus(),
+ result.bytesConsumed(),
+ result.bytesProduced());
+
+ encrypted_consumed = in_buffer.position() - encrypted_position;
+ _inbound.skip(encrypted_consumed);
+ _inbound.compact();
+
+ decrypted_produced = bbuf.position() - decrypted_position;
+ buffer.setPutIndex(buffer.putIndex() + decrypted_produced);
+ }
+ catch(SSLException e)
+ {
+ _logger.debug(String.valueOf(_endp), e);
+ _endp.close();
+ throw e;
+ }
+ catch (Exception x)
+ {
+ throw new IOException(x);
+ }
+ finally
+ {
+ in_buffer.position(0);
+ in_buffer.limit(in_buffer.capacity());
+ bbuf.position(0);
+ bbuf.limit(bbuf.capacity());
+ }
+ }
+ }
+
+ switch(result.getStatus())
+ {
+ case BUFFER_UNDERFLOW:
+ if (_endp.isInputShutdown())
+ _inbound.clear();
+ break;
+
+ case BUFFER_OVERFLOW:
+ if (_logger.isDebugEnabled()) _logger.debug("{} unwrap {} {}->{}",_session,result.getStatus(),_inbound.toDetailString(),buffer.toDetailString());
+ break;
+
+ case OK:
+ if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
+ _handshook=true;
+ break;
+
+ case CLOSED:
+ _logger.debug("unwrap CLOSE {} {}",this,result);
+ if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
+ _endp.close();
+ break;
+
+ default:
+ _logger.debug("{} wrap default {}",_session,result);
+ throw new IOException(result.toString());
+ }
+
+ return encrypted_consumed > 0 || decrypted_produced > 0;
+ }
+
+ /* ------------------------------------------------------------ */
+ private ByteBuffer extractByteBuffer(Buffer buffer)
+ {
+ if (buffer.buffer() instanceof NIOBuffer)
+ return ((NIOBuffer)buffer.buffer()).getByteBuffer();
+ return ByteBuffer.wrap(buffer.array());
+ }
+
+ /* ------------------------------------------------------------ */
+ public AsyncEndPoint getSslEndPoint()
+ {
+ return _sslEndPoint;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return String.format("%s %s", super.toString(), _sslEndPoint);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public class SslEndPoint implements AsyncEndPoint
+ {
+ public void shutdownOutput() throws IOException
+ {
+ synchronized (SslConnection.this)
+ {
+ try
+ {
+ _logger.debug("{} ssl endp.oshut {}",_session,this);
+ _oshut=true;
+ _engine.closeOutbound();
+ }
+ catch (Exception x)
+ {
+ throw new IOException(x);
+ }
+ }
+ flush();
+ }
+
+ public boolean isOutputShutdown()
+ {
+ synchronized (SslConnection.this)
+ {
+ return _oshut||!isOpen()||_engine.isOutboundDone();
+ }
+ }
+
+ public void shutdownInput() throws IOException
+ {
+ _logger.debug("{} ssl endp.ishut!",_session);
+ // We do not do a closeInput here, as SSL does not support half close.
+ // isInputShutdown works it out itself from buffer state and underlying endpoint state.
+ }
+
+ public boolean isInputShutdown()
+ {
+ synchronized (SslConnection.this)
+ {
+ return _endp.isInputShutdown() &&
+ !(_unwrapBuf!=null&&_unwrapBuf.hasContent()) &&
+ !(_inbound!=null&&_inbound.hasContent());
+ }
+ }
+
+ public void close() throws IOException
+ {
+ _logger.debug("{} ssl endp.close",_session);
+ _endp.close();
+ }
+
+ public int fill(Buffer buffer) throws IOException
+ {
+ int size=buffer.length();
+ process(buffer, null);
+
+ int filled=buffer.length()-size;
+
+ if (filled==0 && isInputShutdown())
+ return -1;
+ return filled;
+ }
+
+ public int flush(Buffer buffer) throws IOException
+ {
+ int size = buffer.length();
+ process(null, buffer);
+ return size-buffer.length();
+ }
+
+ public boolean blockWritable(long millisecs) throws IOException
+ {
+ return _endp.blockWritable(millisecs);
+ }
+
+ public boolean isOpen()
+ {
+ return _endp.isOpen();
+ }
+
+ public void flush() throws IOException
+ {
+ process(null, null);
+ }
+
+ public void dispatch()
+ {
+ _aEndp.dispatch();
+ }
+
+ public void scheduleWrite()
+ {
+ _aEndp.scheduleWrite();
+ }
+
+ public boolean hasProgressed()
+ {
+ return _progressed.getAndSet(false);
+ }
+
+ public String getLocalAddr()
+ {
+ return _aEndp.getLocalAddr();
+ }
+
+ public int getLocalPort()
+ {
+ return _aEndp.getLocalPort();
+ }
+
+ public String getRemoteAddr()
+ {
+ return _aEndp.getRemoteAddr();
+ }
+
+ public int getRemotePort()
+ {
+ return _aEndp.getRemotePort();
+ }
+
+ public boolean isBlocking()
+ {
+ return false;
+ }
+
+ public int getMaxIdleTime()
+ {
+ return _aEndp.getMaxIdleTime();
+ }
+
+ public void setMaxIdleTime(int timeMs) throws IOException
+ {
+ _aEndp.setMaxIdleTime(timeMs);
+ }
+
+ public Connection getConnection()
+ {
+ return _connection;
+ }
+
+ public void setConnection(Connection connection)
+ {
+ _connection=(AsyncConnection)connection;
+ }
+
+ public String toString()
+ {
+ // Do NOT use synchronized (SslConnection.this)
+ // because it's very easy to deadlock when debugging is enabled.
+ // We do a best effort to print the right toString() and that's it.
+ Buffer inbound = _inbound;
+ Buffer outbound = _outbound;
+ Buffer unwrap = _unwrapBuf;
+ int i = inbound == null? -1 : inbound.length();
+ int o = outbound == null ? -1 : outbound.length();
+ int u = unwrap == null ? -1 : unwrap.length();
+ return String.format(Locale.getDefault(), "SSL %s i/o/u=%d/%d/%d ishut=%b oshut=%b {%s}",
+ _engine.getHandshakeStatus(),
+ i, o, u,
+ _ishut, _oshut,
+ _connection);
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/BlockingArrayQueue.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/BlockingArrayQueue.java
new file mode 100644
index 00000000..110192b1
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/BlockingArrayQueue.java
@@ -0,0 +1,482 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractList;
+import java.util.Collection;
+import java.util.NoSuchElementException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+
+/* ------------------------------------------------------------ */
+/** Queue backed by a circular array.
+ *
+ * This queue is uses a variant of the two lock queue algorithm to
+ * provide an efficient queue or list backed by a growable circular
+ * array. This queue also has a partial implementation of
+ * {@link java.util.concurrent.BlockingQueue}, specifically the {@link #take()} and
+ * {@link #poll(long, TimeUnit)} methods.
+ * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is
+ * able to grow and provides a blocking put call.
+ * <p>
+ * The queue has both a capacity (the size of the array currently allocated)
+ * and a limit (the maximum size that may be allocated), which defaults to
+ * {@link Integer#MAX_VALUE}.
+ *
+ * @param <E> The element type
+ */
+public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQueue<E>
+{
+ public final int DEFAULT_CAPACITY=128;
+ public final int DEFAULT_GROWTH=64;
+ private final int _limit;
+ private final AtomicInteger _size=new AtomicInteger();
+ private final int _growCapacity;
+
+ private volatile int _capacity;
+ private Object[] _elements;
+
+ private final ReentrantLock _headLock = new ReentrantLock();
+ private final Condition _notEmpty = _headLock.newCondition();
+ private int _head;
+
+ private final ReentrantLock _tailLock = new ReentrantLock();
+ private int _tail;
+
+ /* ------------------------------------------------------------ */
+ /** Create a growing partially blocking Queue.
+ * @param capacity Initial capacity
+ * @param growBy Incremental capacity.
+ */
+ public BlockingArrayQueue(int capacity,int growBy)
+ {
+ _elements=new Object[capacity];
+ _capacity=_elements.length;
+ _growCapacity=growBy;
+ _limit=Integer.MAX_VALUE;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getCapacity()
+ {
+ return _capacity;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean add(E e)
+ {
+ return offer(e);
+ }
+
+ /* ------------------------------------------------------------ */
+ public E element()
+ {
+ E e = peek();
+ if (e==null)
+ throw new NoSuchElementException();
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ @SuppressWarnings("unchecked")
+ public E peek()
+ {
+ if (_size.get() == 0)
+ return null;
+
+ E e = null;
+ _headLock.lock(); // Size cannot shrink
+ try
+ {
+ if (_size.get() > 0)
+ e = (E)_elements[_head];
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean offer(E e)
+ {
+ if (e == null)
+ throw new NullPointerException();
+
+ boolean not_empty=false;
+ _tailLock.lock(); // size cannot grow... only shrink
+ try
+ {
+ if (_size.get() >= _limit)
+ return false;
+
+ // should we expand array?
+ if (_size.get()==_capacity)
+ {
+ _headLock.lock(); // Need to grow array
+ try
+ {
+ if (!grow())
+ return false;
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+
+ // add the element
+ _elements[_tail]=e;
+ _tail=(_tail+1)%_capacity;
+
+ not_empty=0==_size.getAndIncrement();
+
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+
+ if (not_empty)
+ {
+ _headLock.lock();
+ try
+ {
+ _notEmpty.signal();
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ @SuppressWarnings("unchecked")
+ public E poll()
+ {
+ if (_size.get() == 0)
+ return null;
+
+ E e = null;
+ _headLock.lock(); // Size cannot shrink
+ try
+ {
+ if (_size.get() > 0)
+ {
+ final int head=_head;
+ e = (E)_elements[head];
+ _elements[head]=null;
+ _head=(head+1)%_capacity;
+
+ if (_size.decrementAndGet()>0)
+ _notEmpty.signal();
+ }
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieves and removes the head of this queue, waiting
+ * if no elements are present on this queue.
+ * @return the head of this queue
+ * @throws InterruptedException if interrupted while waiting.
+ */
+ @SuppressWarnings("unchecked")
+ public E take() throws InterruptedException
+ {
+ E e = null;
+ _headLock.lockInterruptibly(); // Size cannot shrink
+ try
+ {
+ try
+ {
+ while (_size.get() == 0)
+ {
+ _notEmpty.await();
+ }
+ }
+ catch (InterruptedException ie)
+ {
+ _notEmpty.signal();
+ throw ie;
+ }
+
+ final int head=_head;
+ e = (E)_elements[head];
+ _elements[head]=null;
+ _head=(head+1)%_capacity;
+
+ if (_size.decrementAndGet()>0)
+ _notEmpty.signal();
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieves and removes the head of this queue, waiting
+ * if necessary up to the specified wait time if no elements are
+ * present on this queue.
+ * @param time how long to wait before giving up, in units of
+ * <tt>unit</tt>
+ * @param unit a <tt>TimeUnit</tt> determining how to interpret the
+ * <tt>timeout</tt> parameter
+ * @return the head of this queue, or <tt>null</tt> if the
+ * specified waiting time elapses before an element is present.
+ * @throws InterruptedException if interrupted while waiting.
+ */
+ @SuppressWarnings("unchecked")
+ public E poll(long time, TimeUnit unit) throws InterruptedException
+ {
+
+ E e = null;
+
+ long nanos = unit.toNanos(time);
+
+ _headLock.lockInterruptibly(); // Size cannot shrink
+ try
+ {
+ try
+ {
+ while (_size.get() == 0)
+ {
+ if (nanos<=0)
+ return null;
+ nanos = _notEmpty.awaitNanos(nanos);
+ }
+ }
+ catch (InterruptedException ie)
+ {
+ _notEmpty.signal();
+ throw ie;
+ }
+
+ e = (E)_elements[_head];
+ _elements[_head]=null;
+ _head=(_head+1)%_capacity;
+
+ if (_size.decrementAndGet()>0)
+ _notEmpty.signal();
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ public E remove()
+ {
+ E e=poll();
+ if (e==null)
+ throw new NoSuchElementException();
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void clear()
+ {
+ _tailLock.lock();
+ try
+ {
+ _headLock.lock();
+ try
+ {
+ _head=0;
+ _tail=0;
+ _size.set(0);
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isEmpty()
+ {
+ return _size.get()==0;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int size()
+ {
+ return _size.get();
+ }
+
+ /* ------------------------------------------------------------ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public E get(int index)
+ {
+ _tailLock.lock();
+ try
+ {
+ _headLock.lock();
+ try
+ {
+ if (index<0 || index>=_size.get())
+ throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")");
+ int i = _head+index;
+ if (i>=_capacity)
+ i-=_capacity;
+ return (E)_elements[i];
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private boolean grow()
+ {
+ if (_growCapacity<=0)
+ return false;
+
+ _tailLock.lock();
+ try
+ {
+ _headLock.lock();
+ try
+ {
+ final int head=_head;
+ final int tail=_tail;
+ final int new_tail;
+
+ Object[] elements=new Object[_capacity+_growCapacity];
+
+ if (head<tail)
+ {
+ new_tail=tail-head;
+ System.arraycopy(_elements,head,elements,0,new_tail);
+ }
+ else if (head>tail || _size.get()>0)
+ {
+ new_tail=_capacity+tail-head;
+ int cut=_capacity-head;
+ System.arraycopy(_elements,head,elements,0,cut);
+ System.arraycopy(_elements,0,elements,cut,tail);
+ }
+ else
+ {
+ new_tail=0;
+ }
+
+ _elements=elements;
+ _capacity=_elements.length;
+ _head=0;
+ _tail=new_tail;
+ return true;
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ public int drainTo(Collection<? super E> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int drainTo(Collection<? super E> c, int maxElements)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void put(E o) throws InterruptedException
+ {
+ if (!add(o))
+ throw new IllegalStateException("full");
+ }
+
+ /* ------------------------------------------------------------ */
+ public int remainingCapacity()
+ {
+ _tailLock.lock();
+ try
+ {
+ _headLock.lock();
+ try
+ {
+ return getCapacity()-size();
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ConcurrentHashSet.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ConcurrentHashSet.java
new file mode 100644
index 00000000..9f88ebc9
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ConcurrentHashSet.java
@@ -0,0 +1,126 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>
+{
+ private final Map<E, Boolean> _map = new ConcurrentHashMap<E, Boolean>();
+ private transient Set<E> _keys = _map.keySet();
+
+ public ConcurrentHashSet()
+ {
+ }
+
+ @Override
+ public boolean add(E e)
+ {
+ return _map.put(e,Boolean.TRUE) == null;
+ }
+
+ @Override
+ public void clear()
+ {
+ _map.clear();
+ }
+
+ @Override
+ public boolean contains(Object o)
+ {
+ return _map.containsKey(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c)
+ {
+ return _keys.containsAll(c);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return o == this || _keys.equals(o);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return _keys.hashCode();
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return _map.isEmpty();
+ }
+
+ @Override
+ public Iterator<E> iterator()
+ {
+ return _keys.iterator();
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ return _map.remove(o) != null;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c)
+ {
+ return _keys.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c)
+ {
+ return _keys.retainAll(c);
+ }
+
+ @Override
+ public int size()
+ {
+ return _map.size();
+ }
+
+ @Override
+ public Object[] toArray()
+ {
+ return _keys.toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a)
+ {
+ return _keys.toArray(a);
+ }
+
+ @Override
+ public String toString()
+ {
+ return _keys.toString();
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/IO.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/IO.java
new file mode 100644
index 00000000..ea1b0887
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/IO.java
@@ -0,0 +1,122 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ======================================================================== */
+/** IO Utilities.
+ * Provides stream handling utilities in
+ * singleton Threadpool implementation accessed by static members.
+ */
+public class IO
+{
+ private static final Logger LOG = Log.getLogger(IO.class);
+
+ /* ------------------------------------------------------------------- */
+ public static int bufferSize = 64*1024;
+
+ /* ------------------------------------------------------------------- */
+ /** Copy Stream in to Stream out until EOF or exception.
+ */
+ public static void copy(InputStream in, OutputStream out)
+ throws IOException
+ {
+ copy(in,out,-1);
+ }
+
+ /* ------------------------------------------------------------------- */
+ /** Copy Stream in to Stream for byteCount bytes or until EOF or exception.
+ */
+ public static void copy(InputStream in,
+ OutputStream out,
+ long byteCount)
+ throws IOException
+ {
+ byte buffer[] = new byte[bufferSize];
+ int len=bufferSize;
+
+ if (byteCount>=0)
+ {
+ while (byteCount>0)
+ {
+ int max = byteCount<bufferSize?(int)byteCount:bufferSize;
+ len=in.read(buffer,0,max);
+
+ if (len==-1)
+ break;
+
+ byteCount -= len;
+ out.write(buffer,0,len);
+ }
+ }
+ else
+ {
+ while (true)
+ {
+ len=in.read(buffer,0,bufferSize);
+ if (len<0 )
+ break;
+ out.write(buffer,0,len);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * closes any {@link Closeable}
+ *
+ * @param c the closeable to close
+ */
+ public static void close(Closeable c)
+ {
+ try
+ {
+ if (c != null)
+ c.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+ /**
+ * closes an input stream, and logs exceptions
+ *
+ * @param is the input stream to close
+ */
+ public static void close(InputStream is)
+ {
+ try
+ {
+ if (is != null)
+ is.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/QuotedStringTokenizer.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/QuotedStringTokenizer.java
new file mode 100644
index 00000000..2c6e2460
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/QuotedStringTokenizer.java
@@ -0,0 +1,312 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+/* ------------------------------------------------------------ */
+/** StringTokenizer with Quoting support.
+ *
+ * This class is a copy of the java.util.StringTokenizer API and
+ * the behaviour is the same, except that single and double quoted
+ * string values are recognised.
+ * Delimiters within quotes are not considered delimiters.
+ * Quotes can be escaped with '\'.
+ *
+ * @see java.util.StringTokenizer
+ *
+ */
+public class QuotedStringTokenizer
+ extends StringTokenizer
+{
+ private final static String __delim="\t\n\r";
+ private String _string;
+ private String _delim = __delim;
+ private boolean _returnQuotes=false;
+ private boolean _returnDelimiters=false;
+ private StringBuffer _token;
+ private boolean _hasToken=false;
+ private int _i=0;
+ private boolean _double=true;
+ private boolean _single=true;
+
+ /* ------------------------------------------------------------ */
+ public QuotedStringTokenizer(String str,
+ String delim,
+ boolean returnDelimiters,
+ boolean returnQuotes)
+ {
+ super("");
+ _string=str;
+ if (delim!=null)
+ _delim=delim;
+ _returnDelimiters=returnDelimiters;
+ _returnQuotes=returnQuotes;
+
+ if (_delim.indexOf('\'')>=0 ||
+ _delim.indexOf('"')>=0)
+ throw new Error("Can't use quotes as delimiters: "+_delim);
+
+ _token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean hasMoreTokens()
+ {
+ // Already found a token
+ if (_hasToken)
+ return true;
+
+ int state=0;
+ boolean escape=false;
+ while (_i<_string.length())
+ {
+ char c=_string.charAt(_i++);
+
+ switch (state)
+ {
+ case 0: // Start
+ if(_delim.indexOf(c)>=0)
+ {
+ if (_returnDelimiters)
+ {
+ _token.append(c);
+ return _hasToken=true;
+ }
+ }
+ else if (c=='\'' && _single)
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=2;
+ }
+ else if (c=='\"' && _double)
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=3;
+ }
+ else
+ {
+ _token.append(c);
+ _hasToken=true;
+ state=1;
+ }
+ break;
+
+ case 1: // Token
+ _hasToken=true;
+ if(_delim.indexOf(c)>=0)
+ {
+ if (_returnDelimiters)
+ _i--;
+ return _hasToken;
+ }
+ else if (c=='\'' && _single)
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=2;
+ }
+ else if (c=='\"' && _double)
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=3;
+ }
+ else
+ {
+ _token.append(c);
+ }
+ break;
+
+ case 2: // Single Quote
+ _hasToken=true;
+ if (escape)
+ {
+ escape=false;
+ _token.append(c);
+ }
+ else if (c=='\'')
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=1;
+ }
+ else if (c=='\\')
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ escape=true;
+ }
+ else
+ {
+ _token.append(c);
+ }
+ break;
+
+ case 3: // Double Quote
+ _hasToken=true;
+ if (escape)
+ {
+ escape=false;
+ _token.append(c);
+ }
+ else if (c=='\"')
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=1;
+ }
+ else if (c=='\\')
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ escape=true;
+ }
+ else
+ {
+ _token.append(c);
+ }
+ break;
+ }
+ }
+
+ return _hasToken;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String nextToken()
+ throws NoSuchElementException
+ {
+ if (!hasMoreTokens() || _token==null)
+ throw new NoSuchElementException();
+ String t=_token.toString();
+ _token.setLength(0);
+ _hasToken=false;
+ return t;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean hasMoreElements()
+ {
+ return hasMoreTokens();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Object nextElement()
+ throws NoSuchElementException
+ {
+ return nextToken();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Quote a string.
+ * The string is quoted only if quoting is required due to
+ * embedded delimiters, quote characters or the
+ * empty string.
+ * @param s The string to quote.
+ * @param delim the delimiter to use to quote the string
+ * @return quoted string
+ */
+ public static String quoteIfNeeded(String s, String delim)
+ {
+ if (s==null)
+ return null;
+ if (s.length()==0)
+ return "\"\"";
+
+
+ for (int i=0;i<s.length();i++)
+ {
+ char c = s.charAt(i);
+ if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0)
+ {
+ StringBuffer b=new StringBuffer(s.length()+8);
+ quote(b,s);
+ return b.toString();
+ }
+ }
+
+ return s;
+ }
+
+ private static final char[] escapes = new char[32];
+ static
+ {
+ Arrays.fill(escapes, (char)0xFFFF);
+ escapes['\b'] = 'b';
+ escapes['\t'] = 't';
+ escapes['\n'] = 'n';
+ escapes['\f'] = 'f';
+ escapes['\r'] = 'r';
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Quote a string into an Appendable.
+ * The characters ", \, \n, \r, \t, \f and \b are escaped
+ * @param buffer The Appendable
+ * @param input The String to quote.
+ */
+ public static void quote(Appendable buffer, String input)
+ {
+ try
+ {
+ buffer.append('"');
+ for (int i = 0; i < input.length(); ++i)
+ {
+ char c = input.charAt(i);
+ if (c >= 32)
+ {
+ if (c == '"' || c == '\\')
+ buffer.append('\\');
+ buffer.append(c);
+ }
+ else
+ {
+ char escape = escapes[c];
+ if (escape == 0xFFFF)
+ {
+ // Unicode escape
+ buffer.append('\\').append('u').append('0').append('0');
+ if (c < 0x10)
+ buffer.append('0');
+ buffer.append(Integer.toString(c, 16));
+ }
+ else
+ {
+ buffer.append('\\').append(escape);
+ }
+ }
+ }
+ buffer.append('"');
+ }
+ catch (IOException x)
+ {
+ throw new RuntimeException(x);
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/StringMap.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/StringMap.java
new file mode 100644
index 00000000..addba083
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/StringMap.java
@@ -0,0 +1,603 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Externalizable;
+import java.util.AbstractMap;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** Map implementation Optimized for Strings keys..
+ * This String Map has been optimized for mapping small sets of
+ * Strings where the most frequently accessed Strings have been put to
+ * the map first.
+ *
+ * It also has the benefit that it can look up entries by substring or
+ * sections of char and byte arrays. This can prevent many String
+ * objects from being created just to look up in the map.
+ *
+ * This map is NOT synchronized.
+ */
+public class StringMap extends AbstractMap<Object, Object> implements Externalizable
+{
+ public static final boolean CASE_INSENSTIVE=true;
+ protected static final int __HASH_WIDTH=17;
+
+ /* ------------------------------------------------------------ */
+ protected int _width=__HASH_WIDTH;
+ protected Node _root=new Node();
+ protected boolean _ignoreCase=false;
+ protected NullEntry _nullEntry=null;
+ protected Object _nullValue=null;
+ protected HashSet<Entry<Object, Object>> _entrySet=new HashSet<Entry<Object, Object>>(3);
+ protected Set<Entry<Object, Object>> _umEntrySet=Collections.unmodifiableSet(_entrySet);
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ */
+ public StringMap()
+ {}
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ * @param ignoreCase
+ */
+ public StringMap(boolean ignoreCase)
+ {
+ this();
+ _ignoreCase=ignoreCase;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the ignoreCase attribute.
+ * @param ic If true, the map is case insensitive for keys.
+ */
+ public void setIgnoreCase(boolean ic)
+ {
+ if (_root._children!=null)
+ throw new IllegalStateException("Must be set before first put");
+ _ignoreCase=ic;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Object put(Object key, Object value)
+ {
+ if (key==null)
+ return put(null,value);
+ return put(key.toString(),value);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object put(String key, Object value)
+ {
+ if (key==null)
+ {
+ Object oldValue=_nullValue;
+ _nullValue=value;
+ if (_nullEntry==null)
+ {
+ _nullEntry=new NullEntry();
+ _entrySet.add(_nullEntry);
+ }
+ return oldValue;
+ }
+
+ Node node = _root;
+ int ni=-1;
+ Node prev = null;
+ Node parent = null;
+
+ // look for best match
+ charLoop:
+ for (int i=0;i<key.length();i++)
+ {
+ char c=key.charAt(i);
+
+ // Advance node
+ if (ni==-1)
+ {
+ parent=node;
+ prev=null;
+ ni=0;
+ node=(node._children==null)?null:node._children[c%_width];
+ }
+
+ // Loop through a node chain at the same level
+ while (node!=null)
+ {
+ // If it is a matching node, goto next char
+ if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
+ {
+ prev=null;
+ ni++;
+ if (ni==node._char.length)
+ ni=-1;
+ continue charLoop;
+ }
+
+ // no char match
+ // if the first char,
+ if (ni==0)
+ {
+ // look along the chain for a char match
+ prev=node;
+ node=node._next;
+ }
+ else
+ {
+ // Split the current node!
+ node.split(this,ni);
+ i--;
+ ni=-1;
+ continue charLoop;
+ }
+ }
+
+ // We have run out of nodes, so as this is a put, make one
+ node = new Node(_ignoreCase,key,i);
+
+ if (prev!=null) // add to end of chain
+ prev._next=node;
+ else if (parent!=null) // add new child
+ {
+ if (parent._children==null)
+ parent._children=new Node[_width];
+ parent._children[c%_width]=node;
+ int oi=node._ochar[0]%_width;
+ if (node._ochar!=null && node._char[0]%_width!=oi)
+ {
+ if (parent._children[oi]==null)
+ parent._children[oi]=node;
+ else
+ {
+ Node n=parent._children[oi];
+ while(n._next!=null)
+ n=n._next;
+ n._next=node;
+ }
+ }
+ }
+ else // this is the root.
+ _root=node;
+ break;
+ }
+
+ // Do we have a node
+ if (node!=null)
+ {
+ // Split it if we are in the middle
+ if(ni>0)
+ node.split(this,ni);
+
+ Object old = node._value;
+ node._key=key;
+ node._value=value;
+ _entrySet.add(node);
+ return old;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Object get(Object key)
+ {
+ if (key==null)
+ return _nullValue;
+ if (key instanceof String)
+ return get((String)key);
+ return get(key.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object get(String key)
+ {
+ if (key==null)
+ return _nullValue;
+
+ Map.Entry<Object, Object> entry = getEntry(key,0,key.length());
+ if (entry==null)
+ return null;
+ return entry.getValue();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get a map entry by substring key.
+ * @param key String containing the key
+ * @param offset Offset of the key within the String.
+ * @param length The length of the key
+ * @return The Map.Entry for the key or null if the key is not in
+ * the map.
+ */
+ public Map.Entry<Object, Object> getEntry(String key,int offset, int length)
+ {
+ if (key==null)
+ return _nullEntry;
+
+ Node node = _root;
+ int ni=-1;
+
+ // look for best match
+ charLoop:
+ for (int i=0;i<length;i++)
+ {
+ char c=key.charAt(offset+i);
+
+ // Advance node
+ if (ni==-1)
+ {
+ ni=0;
+ node=(node._children==null)?null:node._children[c%_width];
+ }
+
+ // Look through the node chain
+ while (node!=null)
+ {
+ // If it is a matching node, goto next char
+ if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
+ {
+ ni++;
+ if (ni==node._char.length)
+ ni=-1;
+ continue charLoop;
+ }
+
+ // No char match, so if mid node then no match at all.
+ if (ni>0) return null;
+
+ // try next in chain
+ node=node._next;
+ }
+ return null;
+ }
+
+ if (ni>0) return null;
+ if (node!=null && node._key==null)
+ return null;
+ return node;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get a map entry by byte array key, using as much of the passed key as needed for a match.
+ * A simple 8859-1 byte to char mapping is assumed.
+ * @param key char array containing the key
+ * @param offset Offset of the key within the array.
+ * @param maxLength The length of the key
+ * @return The Map.Entry for the key or null if the key is not in
+ * the map.
+ */
+ public Map.Entry<Object, Object> getBestEntry(byte[] key,int offset, int maxLength)
+ {
+ if (key==null)
+ return _nullEntry;
+
+ Node node = _root;
+ int ni=-1;
+
+ // look for best match
+ charLoop:
+ for (int i=0;i<maxLength;i++)
+ {
+ char c=(char)key[offset+i];
+
+ // Advance node
+ if (ni==-1)
+ {
+ ni=0;
+
+ Node child = (node._children==null)?null:node._children[c%_width];
+
+ if (child==null && i>0)
+ return node; // This is the best match
+ node=child;
+ }
+
+ // While we have a node to try
+ while (node!=null)
+ {
+ // If it is a matching node, goto next char
+ if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
+ {
+ ni++;
+ if (ni==node._char.length)
+ ni=-1;
+ continue charLoop;
+ }
+
+ // No char match, so if mid node then no match at all.
+ if (ni>0) return null;
+
+ // try next in chain
+ node=node._next;
+ }
+ return null;
+ }
+
+ if (ni>0) return null;
+ if (node!=null && node._key==null)
+ return null;
+ return node;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Object remove(Object key)
+ {
+ if (key==null)
+ return remove(null);
+ return remove(key.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object remove(String key)
+ {
+ if (key==null)
+ {
+ Object oldValue=_nullValue;
+ if (_nullEntry!=null)
+ {
+ _entrySet.remove(_nullEntry);
+ _nullEntry=null;
+ _nullValue=null;
+ }
+ return oldValue;
+ }
+
+ Node node = _root;
+ int ni=-1;
+
+ // look for best match
+ charLoop:
+ for (int i=0;i<key.length();i++)
+ {
+ char c=key.charAt(i);
+
+ // Advance node
+ if (ni==-1)
+ {
+ ni=0;
+ node=(node._children==null)?null:node._children[c%_width];
+ }
+
+ // While we have a node to try
+ while (node!=null)
+ {
+ // If it is a matching node, goto next char
+ if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
+ {
+ ni++;
+ if (ni==node._char.length)
+ ni=-1;
+ continue charLoop;
+ }
+
+ // No char match, so if mid node then no match at all.
+ if (ni>0) return null;
+
+ // try next in chain
+ node=node._next;
+ }
+ return null;
+ }
+
+ if (ni>0) return null;
+ if (node!=null && node._key==null)
+ return null;
+
+ Object old = node._value;
+ _entrySet.remove(node);
+ node._value=null;
+ node._key=null;
+
+ return old;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Set<Map.Entry<Object, Object>> entrySet()
+ {
+ return _umEntrySet;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int size()
+ {
+ return _entrySet.size();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isEmpty()
+ {
+ return _entrySet.isEmpty();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean containsKey(Object key)
+ {
+ if (key==null)
+ return _nullEntry!=null;
+ return
+ getEntry(key.toString(),0,key==null?0:key.toString().length())!=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void clear()
+ {
+ _root=new Node();
+ _nullEntry=null;
+ _nullValue=null;
+ _entrySet.clear();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class Node implements Map.Entry<Object, Object>
+ {
+ char[] _char;
+ char[] _ochar;
+ Node _next;
+ Node[] _children;
+ String _key;
+ Object _value;
+
+ Node(){}
+
+ Node(boolean ignoreCase,String s, int offset)
+ {
+ int l=s.length()-offset;
+ _char=new char[l];
+ _ochar=new char[l];
+ for (int i=0;i<l;i++)
+ {
+ char c=s.charAt(offset+i);
+ _char[i]=c;
+ if (ignoreCase)
+ {
+ char o=c;
+ if (Character.isUpperCase(c))
+ o=Character.toLowerCase(c);
+ else if (Character.isLowerCase(c))
+ o=Character.toUpperCase(c);
+ _ochar[i]=o;
+ }
+ }
+ }
+
+ Node split(StringMap map,int offset)
+ {
+ Node split = new Node();
+ int sl=_char.length-offset;
+
+ char[] tmp=this._char;
+ this._char=new char[offset];
+ split._char = new char[sl];
+ System.arraycopy(tmp,0,this._char,0,offset);
+ System.arraycopy(tmp,offset,split._char,0,sl);
+
+ if (this._ochar!=null)
+ {
+ tmp=this._ochar;
+ this._ochar=new char[offset];
+ split._ochar = new char[sl];
+ System.arraycopy(tmp,0,this._ochar,0,offset);
+ System.arraycopy(tmp,offset,split._ochar,0,sl);
+ }
+
+ split._key=this._key;
+ split._value=this._value;
+ this._key=null;
+ this._value=null;
+ if (map._entrySet.remove(this))
+ map._entrySet.add(split);
+
+ split._children=this._children;
+ this._children=new Node[map._width];
+ this._children[split._char[0]%map._width]=split;
+ if (split._ochar!=null && this._children[split._ochar[0]%map._width]!=split)
+ this._children[split._ochar[0]%map._width]=split;
+
+ return split;
+ }
+
+ public Object getKey(){return _key;}
+ public Object getValue(){return _value;}
+ public Object setValue(Object o){Object old=_value;_value=o;return old;}
+ @Override
+ public String toString()
+ {
+ StringBuilder buf=new StringBuilder();
+ toString(buf);
+ return buf.toString();
+ }
+
+ private void toString(StringBuilder buf)
+ {
+ buf.append("{[");
+ if (_char==null)
+ buf.append('-');
+ else
+ for (int i=0;i<_char.length;i++)
+ buf.append(_char[i]);
+ buf.append(':');
+ buf.append(_key);
+ buf.append('=');
+ buf.append(_value);
+ buf.append(']');
+ if (_children!=null)
+ {
+ for (int i=0;i<_children.length;i++)
+ {
+ buf.append('|');
+ if (_children[i]!=null)
+ _children[i].toString(buf);
+ else
+ buf.append("-");
+ }
+ }
+ buf.append('}');
+ if (_next!=null)
+ {
+ buf.append(",\n");
+ _next.toString(buf);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private class NullEntry implements Map.Entry<Object, Object>
+ {
+ public Object getKey(){return null;}
+ public Object getValue(){return _nullValue;}
+ public Object setValue(Object o)
+ {Object old=_nullValue;_nullValue=o;return old;}
+ @Override
+ public String toString(){return "[:null="+_nullValue+"]";}
+ }
+
+ /* ------------------------------------------------------------ */
+ public void writeExternal(java.io.ObjectOutput out)
+ throws java.io.IOException
+ {
+ HashMap<Object, Object> map = new HashMap<Object, Object>(this);
+ out.writeBoolean(_ignoreCase);
+ out.writeObject(map);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void readExternal(java.io.ObjectInput in)
+ throws java.io.IOException, ClassNotFoundException
+ {
+ boolean ic=in.readBoolean();
+ HashMap<?, ?> map = (HashMap<?, ?>)in.readObject();
+ setIgnoreCase(ic);
+ this.putAll(map);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/TypeUtil.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/TypeUtil.java
new file mode 100644
index 00000000..48c373fd
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/TypeUtil.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+
+/* ------------------------------------------------------------ */
+/**
+ * TYPE Utilities.
+ * Provides various static utiltiy methods for manipulating types and their
+ * string representations.
+ *
+ * @since Jetty 4.1
+ */
+public class TypeUtil
+{
+ /* ------------------------------------------------------------ */
+ public static void toHex(byte b,Appendable buf)
+ {
+ try
+ {
+ int d=0xf&((0xF0&b)>>4);
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ d=0xf&b;
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/AbstractLifeCycle.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/AbstractLifeCycle.java
new file mode 100644
index 00000000..92c4996c
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/AbstractLifeCycle.java
@@ -0,0 +1,193 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Basic implementation of the life cycle interface for components.
+ *
+ *
+ */
+public abstract class AbstractLifeCycle implements LifeCycle
+{
+ private static final Logger LOG = Log.getLogger(AbstractLifeCycle.class);
+ public static final String STOPPED="STOPPED";
+ public static final String FAILED="FAILED";
+ public static final String STARTING="STARTING";
+ public static final String STARTED="STARTED";
+ public static final String STOPPING="STOPPING";
+ public static final String RUNNING="RUNNING";
+
+ private final Object _lock = new Object();
+ private final int __FAILED = -1, __STOPPED = 0, __STARTING = 1, __STARTED = 2, __STOPPING = 3;
+ private volatile int _state = __STOPPED;
+
+ protected final CopyOnWriteArrayList<LifeCycle.Listener> _listeners=new CopyOnWriteArrayList<LifeCycle.Listener>();
+
+ protected void doStart() throws Exception
+ {
+ }
+
+ protected void doStop() throws Exception
+ {
+ }
+
+ public final void start() throws Exception
+ {
+ synchronized (_lock)
+ {
+ try
+ {
+ if (_state == __STARTED || _state == __STARTING)
+ return;
+ setStarting();
+ doStart();
+ setStarted();
+ }
+ catch (Exception e)
+ {
+ setFailed(e);
+ throw e;
+ }
+ catch (Error e)
+ {
+ setFailed(e);
+ throw e;
+ }
+ }
+ }
+
+ public final void stop() throws Exception
+ {
+ synchronized (_lock)
+ {
+ try
+ {
+ if (_state == __STOPPING || _state == __STOPPED)
+ return;
+ setStopping();
+ doStop();
+ setStopped();
+ }
+ catch (Exception e)
+ {
+ setFailed(e);
+ throw e;
+ }
+ catch (Error e)
+ {
+ setFailed(e);
+ throw e;
+ }
+ }
+ }
+
+ public boolean isRunning()
+ {
+ final int state = _state;
+
+ return state == __STARTED || state == __STARTING;
+ }
+
+ public boolean isStarted()
+ {
+ return _state == __STARTED;
+ }
+
+ public boolean isStarting()
+ {
+ return _state == __STARTING;
+ }
+
+ public boolean isStopping()
+ {
+ return _state == __STOPPING;
+ }
+
+ public boolean isStopped()
+ {
+ return _state == __STOPPED;
+ }
+
+ public String getState()
+ {
+ switch(_state)
+ {
+ case __FAILED: return FAILED;
+ case __STARTING: return STARTING;
+ case __STARTED: return STARTED;
+ case __STOPPING: return STOPPING;
+ case __STOPPED: return STOPPED;
+ }
+ return null;
+ }
+
+ public static String getState(LifeCycle lc)
+ {
+ if (lc.isStarting()) return STARTING;
+ if (lc.isStarted()) return STARTED;
+ if (lc.isStopping()) return STOPPING;
+ if (lc.isStopped()) return STOPPED;
+ return FAILED;
+ }
+
+ private void setStarted()
+ {
+ _state = __STARTED;
+ LOG.debug(STARTED+" {}",this);
+ for (Listener listener : _listeners)
+ listener.lifeCycleStarted(this);
+ }
+
+ private void setStarting()
+ {
+ LOG.debug("starting {}",this);
+ _state = __STARTING;
+ for (Listener listener : _listeners)
+ listener.lifeCycleStarting(this);
+ }
+
+ private void setStopping()
+ {
+ LOG.debug("stopping {}",this);
+ _state = __STOPPING;
+ for (Listener listener : _listeners)
+ listener.lifeCycleStopping(this);
+ }
+
+ private void setStopped()
+ {
+ _state = __STOPPED;
+ LOG.debug("{} {}",STOPPED,this);
+ for (Listener listener : _listeners)
+ listener.lifeCycleStopped(this);
+ }
+
+ private void setFailed(Throwable th)
+ {
+ _state = __FAILED;
+ LOG.warn(FAILED+" " + this+": "+th,th);
+ for (Listener listener : _listeners)
+ listener.lifeCycleFailure(this,th);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/AggregateLifeCycle.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/AggregateLifeCycle.java
new file mode 100644
index 00000000..670fbfd1
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/AggregateLifeCycle.java
@@ -0,0 +1,250 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * An AggregateLifeCycle is an {@link LifeCycle} implementation for a collection of contained beans.
+ * <p>
+ * Beans can be added the AggregateLifeCycle either as managed beans or as unmanaged beans. A managed bean is started, stopped and destroyed with the aggregate.
+ * An unmanaged bean is associated with the aggregate for the purposes of {@link #dump()}, but it's lifecycle must be managed externally.
+ * <p>
+ * When a bean is added, if it is a {@link LifeCycle} and it is already started, then it is assumed to be an unmanaged bean.
+ * Otherwise the methods {@link #addBean(Object, boolean)}, {@link #manage(Object)} and {@link #unmanage(Object)} can be used to
+ * explicitly control the life cycle relationship.
+ * <p>
+ * If adding a bean that is shared between multiple {@link AggregateLifeCycle} instances, then it should be started before being added, so it is unmanaged, or
+ * the API must be used to explicitly set it as unmanaged.
+ * <p>
+ */
+public class AggregateLifeCycle extends AbstractLifeCycle implements Dumpable
+{
+ private final List<Bean> _beans=new CopyOnWriteArrayList<Bean>();
+ private boolean _started=false;
+
+ private class Bean
+ {
+ Bean(Object b)
+ {
+ _bean=b;
+ }
+ final Object _bean;
+ volatile boolean _managed=true;
+
+ public String toString()
+ {
+ return "{"+_bean+","+_managed+"}";
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Start the managed lifecycle beans in the order they were added.
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ for (Bean b:_beans)
+ {
+ if (b._managed && b._bean instanceof LifeCycle)
+ {
+ LifeCycle l=(LifeCycle)b._bean;
+ if (!l.isRunning())
+ l.start();
+ }
+ }
+ // indicate that we are started, so that addBean will start other beans added.
+ _started=true;
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Stop the joined lifecycle beans in the reverse order they were added.
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ _started=false;
+ super.doStop();
+ List<Bean> reverse = new ArrayList<Bean>(_beans);
+ Collections.reverse(reverse);
+ for (Bean b:reverse)
+ {
+ if (b._managed && b._bean instanceof LifeCycle)
+ {
+ LifeCycle l=(LifeCycle)b._bean;
+ if (l.isRunning())
+ l.stop();
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Is the bean contained in the aggregate.
+ * @param bean
+ * @return True if the aggregate contains the bean
+ */
+ public boolean contains(Object bean)
+ {
+ for (Bean b:_beans)
+ if (b._bean==bean)
+ return true;
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add an associated bean.
+ * If the bean is a {@link LifeCycle}, then it will be managed if it is not
+ * already started and umanaged if it is already started. The {@link #addBean(Object, boolean)}
+ * method should be used if this is not correct, or the {@link #manage(Object)} and {@link #unmanage(Object)}
+ * methods may be used after an add to change the status.
+ * @param o the bean object to add
+ * @return true if the bean was added or false if it has already been added.
+ */
+ public boolean addBean(Object o)
+ {
+ // beans are joined unless they are started lifecycles
+ return addBean(o,!((o instanceof LifeCycle)&&((LifeCycle)o).isStarted()));
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add an associated lifecycle.
+ * @param o The lifecycle to add
+ * @param managed True if the LifeCycle is to be joined, otherwise it will be disjoint.
+ * @return true if bean was added, false if already present.
+ */
+ public boolean addBean(Object o, boolean managed)
+ {
+ if (contains(o))
+ return false;
+
+ Bean b = new Bean(o);
+ b._managed=managed;
+ _beans.add(b);
+
+ if (o instanceof LifeCycle)
+ {
+ LifeCycle l=(LifeCycle)o;
+
+ // Start the bean if we are started
+ if (managed && _started)
+ {
+ try
+ {
+ l.start();
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException (e);
+ }
+ }
+ }
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void dumpThis(Appendable out) throws IOException
+ {
+ out.append(String.valueOf(this)).append(" - ").append(getState()).append("\n");
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void dumpObject(Appendable out,Object o) throws IOException
+ {
+ try
+ {
+ if (o instanceof LifeCycle)
+ out.append(String.valueOf(o)).append(" - ").append((AbstractLifeCycle.getState((LifeCycle)o))).append("\n");
+ else
+ out.append(String.valueOf(o)).append("\n");
+ }
+ catch(Throwable th)
+ {
+ out.append(" => ").append(th.toString()).append('\n');
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dump(Appendable out,String indent) throws IOException
+ {
+ dumpThis(out);
+ int size=_beans.size();
+ if (size==0)
+ return;
+ int i=0;
+ for (Bean b : _beans)
+ {
+ i++;
+
+ out.append(indent).append(" +- ");
+ if (b._managed)
+ {
+ if (b._bean instanceof Dumpable)
+ ((Dumpable)b._bean).dump(out,indent+(i==size?" ":" | "));
+ else
+ dumpObject(out,b._bean);
+ }
+ else
+ dumpObject(out,b._bean);
+ }
+
+ if (i!=size)
+ out.append(indent).append(" |\n");
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void dump(Appendable out,String indent,Collection<?>... collections) throws IOException
+ {
+ if (collections.length==0)
+ return;
+ int size=0;
+ for (Collection<?> c : collections)
+ size+=c.size();
+ if (size==0)
+ return;
+
+ int i=0;
+ for (Collection<?> c : collections)
+ {
+ for (Object o : c)
+ {
+ i++;
+ out.append(indent).append(" +- ");
+
+ if (o instanceof Dumpable)
+ ((Dumpable)o).dump(out,indent+(i==size?" ":" | "));
+ else
+ dumpObject(out,o);
+ }
+
+ if (i!=size)
+ out.append(indent).append(" |\n");
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/Dumpable.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/Dumpable.java
new file mode 100644
index 00000000..58d2404c
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/Dumpable.java
@@ -0,0 +1,26 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.IOException;
+
+public interface Dumpable
+{
+ void dump(Appendable out,String indent) throws IOException;
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/LifeCycle.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/LifeCycle.java
new file mode 100644
index 00000000..85fe6503
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/component/LifeCycle.java
@@ -0,0 +1,107 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.EventListener;
+
+/* ------------------------------------------------------------ */
+/**
+ * The lifecycle interface for generic components.
+ * <br />
+ * Classes implementing this interface have a defined life cycle
+ * defined by the methods of this interface.
+ *
+ *
+ */
+public interface LifeCycle
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Starts the component.
+ * @throws Exception If the component fails to start
+ * @see #isStarted()
+ * @see #stop()
+ * @see #isFailed()
+ */
+ public void start()
+ throws Exception;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Stops the component.
+ * The component may wait for current activities to complete
+ * normally, but it can be interrupted.
+ * @exception Exception If the component fails to stop
+ * @see #isStopped()
+ * @see #start()
+ * @see #isFailed()
+ */
+ public void stop()
+ throws Exception;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component is starting or has been started.
+ */
+ public boolean isRunning();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component has been started.
+ * @see #start()
+ * @see #isStarting()
+ */
+ public boolean isStarted();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component is starting.
+ * @see #isStarted()
+ */
+ public boolean isStarting();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component is stopping.
+ * @see #isStopped()
+ */
+ public boolean isStopping();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component has been stopped.
+ * @see #stop()
+ * @see #isStopping()
+ */
+ public boolean isStopped();
+
+
+ /* ------------------------------------------------------------ */
+ /** Listener.
+ * A listener for Lifecycle events.
+ */
+ public interface Listener extends EventListener
+ {
+ public void lifeCycleStarting(LifeCycle event);
+ public void lifeCycleStarted(LifeCycle event);
+ public void lifeCycleFailure(LifeCycle event,Throwable cause);
+ public void lifeCycleStopping(LifeCycle event);
+ public void lifeCycleStopped(LifeCycle event);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/AndroidLogger.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/AndroidLogger.java
new file mode 100644
index 00000000..a04d21fe
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/AndroidLogger.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.jetty.util.log;
+
+/**
+ * Logger for Android
+ *
+ * Copyright (c) 2015 KNOWLEDGECODE
+ */
+class AndroidLogger implements Logger {
+
+ private String _tag;
+
+ AndroidLogger() {
+ }
+
+ private AndroidLogger(String tag) {
+ _tag = tag.substring(tag.length() > 23 ? tag.length() - 23 : 0);
+ }
+
+ private static String format(String msg, Object... args) {
+ return String.format(msg.replaceAll("\\{\\}", "%s"), args);
+ }
+
+ @Override
+ public String getName() {
+ return AndroidLogger.class.getName();
+ }
+
+ @Override
+ public void warn(String msg, Object... args) {
+ android.util.Log.w(_tag, format(msg, args));
+ }
+
+ @Override
+ public void warn(Throwable thrown) {
+ android.util.Log.w(_tag, thrown);
+ }
+
+ @Override
+ public void warn(String msg, Throwable thrown) {
+ android.util.Log.w(_tag, msg, thrown);
+ }
+
+ @Override
+ public void info(String msg, Object... args) {
+ android.util.Log.i(_tag, format(msg, args));
+ }
+
+ @Override
+ public void info(Throwable thrown) {
+ android.util.Log.i(_tag, "", thrown);
+ }
+
+ @Override
+ public void info(String msg, Throwable thrown) {
+ android.util.Log.i(_tag, msg, thrown);
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return android.util.Log.isLoggable(_tag, android.util.Log.DEBUG);
+ }
+
+ @Override
+ public void setDebugEnabled(boolean enabled) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void debug(String msg, Object... args) {
+ android.util.Log.d(_tag, format(msg, args));
+ }
+
+ @Override
+ public void debug(Throwable thrown) {
+ android.util.Log.d(_tag, "", thrown);
+ }
+
+ @Override
+ public void debug(String msg, Throwable thrown) {
+ android.util.Log.d(_tag, msg, thrown);
+ }
+
+ @Override
+ public Logger getLogger(String tag) {
+ return new AndroidLogger(tag);
+ }
+
+ @Override
+ public void ignore(Throwable ignored) {
+ // ignore
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/Log.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/Log.java
new file mode 100644
index 00000000..0549bab3
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/Log.java
@@ -0,0 +1,151 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+/**
+ * Logging.
+ *
+ * Modified by KNOWLEDGECODE
+ */
+public class Log
+{
+ public static final String EXCEPTION = "EXCEPTION ";
+ public static final String IGNORED = "IGNORED ";
+
+ private static Logger LOG = new Logger()
+ {
+ @Override
+ public String getName()
+ {
+ return null;
+ }
+
+ @Override
+ public void warn(String msg, Object... args)
+ {
+ }
+
+ @Override
+ public void warn(Throwable thrown)
+ {
+ }
+
+ @Override
+ public void warn(String msg, Throwable thrown)
+ {
+ }
+
+ @Override
+ public void info(String msg, Object... args)
+ {
+ }
+
+ @Override
+ public void info(Throwable thrown)
+ {
+ }
+
+ @Override
+ public void info(String msg, Throwable thrown)
+ {
+ }
+
+ @Override
+ public boolean isDebugEnabled()
+ {
+ return false;
+ }
+
+ @Override
+ public void setDebugEnabled(boolean enabled)
+ {
+ }
+
+ @Override
+ public void debug(String msg, Object... args)
+ {
+ }
+
+ @Override
+ public void debug(Throwable thrown)
+ {
+ }
+
+ @Override
+ public void debug(String msg, Throwable thrown)
+ {
+ }
+
+ @Override
+ public Logger getLogger(String name)
+ {
+ return this;
+ }
+
+ @Override
+ public void ignore(Throwable ignored)
+ {
+ }
+ };
+
+ static
+ {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ try
+ {
+ @SuppressWarnings("unchecked")
+ Class<Logger> clazz = (Class<Logger>) loader.loadClass("org.eclipse.jetty.util.log.AndroidLogger");
+ LOG = clazz.newInstance();
+ }
+ catch (ClassNotFoundException e)
+ {
+ }
+ catch (IllegalAccessException e)
+ {
+ }
+ catch (IllegalArgumentException e)
+ {
+ }
+ catch (InstantiationException e)
+ {
+ }
+ }
+
+ /**
+ * Obtain a named Logger based on the fully qualified class name.
+ *
+ * @param clazz
+ * the class to base the Logger name off of
+ * @return the Logger with the given name
+ */
+ public static Logger getLogger(Class<?> clazz)
+ {
+ return getLogger(clazz.getName());
+ }
+
+ /**
+ * Obtain a named Logger or the default Logger if null is passed.
+ * @param name the Logger name
+ * @return the Logger with the given name
+ */
+ public static Logger getLogger(String name)
+ {
+ return LOG.getLogger(name);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/Logger.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/Logger.java
new file mode 100644
index 00000000..40f70ff2
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/log/Logger.java
@@ -0,0 +1,113 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+/**
+ * A simple logging facade that is intended simply to capture the style of logging as used by Jetty.
+ */
+public interface Logger
+{
+ /**
+ * @return the name of this logger
+ */
+ public String getName();
+
+ /**
+ * Formats and logs at warn level.
+ * @param msg the formatting string
+ * @param args the optional arguments
+ */
+ public void warn(String msg, Object... args);
+
+ /**
+ * Logs the given Throwable information at warn level
+ * @param thrown the Throwable to log
+ */
+ public void warn(Throwable thrown);
+
+ /**
+ * Logs the given message at warn level, with Throwable information.
+ * @param msg the message to log
+ * @param thrown the Throwable to log
+ */
+ public void warn(String msg, Throwable thrown);
+
+ /**
+ * Formats and logs at info level.
+ * @param msg the formatting string
+ * @param args the optional arguments
+ */
+ public void info(String msg, Object... args);
+
+ /**
+ * Logs the given Throwable information at info level
+ * @param thrown the Throwable to log
+ */
+ public void info(Throwable thrown);
+
+ /**
+ * Logs the given message at info level, with Throwable information.
+ * @param msg the message to log
+ * @param thrown the Throwable to log
+ */
+ public void info(String msg, Throwable thrown);
+
+ /**
+ * @return whether the debug level is enabled
+ */
+ public boolean isDebugEnabled();
+
+ /**
+ * Mutator used to turn debug on programmatically.
+ * @param enabled whether to enable the debug level
+ */
+ public void setDebugEnabled(boolean enabled);
+
+ /**
+ * Formats and logs at debug level.
+ * @param msg the formatting string
+ * @param args the optional arguments
+ */
+ public void debug(String msg, Object... args);
+
+ /**
+ * Logs the given Throwable information at debug level
+ * @param thrown the Throwable to log
+ */
+ public void debug(Throwable thrown);
+
+ /**
+ * Logs the given message at debug level, with Throwable information.
+ * @param msg the message to log
+ * @param thrown the Throwable to log
+ */
+ public void debug(String msg, Throwable thrown);
+
+ /**
+ * @param name the name of the logger
+ * @return a logger with the given name
+ */
+ public Logger getLogger(String name);
+
+ /**
+ * Ignore an exception.
+ * <p>This should be used rather than an empty catch block.
+ */
+ public void ignore(Throwable ignored);
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/resource/Resource.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/resource/Resource.java
new file mode 100644
index 00000000..90a3ba2f
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/resource/Resource.java
@@ -0,0 +1,119 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**g
+ * Abstract resource class.
+ */
+public abstract class Resource
+{
+ private static final Logger LOG = Log.getLogger(Resource.class);
+ public static boolean __defaultUseCaches = true;
+ volatile Object _associate;
+
+ /* ------------------------------------------------------------ */
+ /** Construct a resource from a url.
+ * @param url A URL.
+ * @return A Resource object.
+ * @throws IOException Problem accessing URL
+ */
+ public static Resource newResource(URL url)
+ throws IOException
+ {
+ return newResource(url, __defaultUseCaches);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Construct a resource from a url.
+ * @param url the url for which to make the resource
+ * @param useCaches true enables URLConnection caching if applicable to the type of resource
+ * @return
+ */
+ static Resource newResource(URL url, boolean useCaches)
+ {
+ if (url==null)
+ return null;
+
+ return new URLResource(url,null,useCaches);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Construct a resource from a string.
+ * @param resource A URL or filename.
+ * @return A Resource object.
+ */
+ public static Resource newResource(String resource)
+ throws MalformedURLException, IOException
+ {
+ return newResource(resource, __defaultUseCaches);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Construct a resource from a string.
+ * @param resource A URL or filename.
+ * @param useCaches controls URLConnection caching
+ * @return A Resource object.
+ */
+ public static Resource newResource (String resource, boolean useCaches)
+ throws MalformedURLException, IOException
+ {
+ URL url=null;
+ try
+ {
+ // Try to format as a URL?
+ url = new URL(resource);
+ }
+ catch(MalformedURLException e)
+ {
+ LOG.warn("Bad Resource: "+resource);
+ throw e;
+ }
+
+ return newResource(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void finalize()
+ {
+ release();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Release any temporary resources held by the resource.
+ */
+ public abstract void release();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an input stream to the resource
+ */
+ public abstract InputStream getInputStream()
+ throws java.io.IOException;
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/resource/URLResource.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/resource/URLResource.java
new file mode 100644
index 00000000..5de288ef
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/resource/URLResource.java
@@ -0,0 +1,130 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Abstract resource class.
+ */
+public class URLResource extends Resource
+{
+ private static final Logger LOG = Log.getLogger(URLResource.class);
+ protected URL _url;
+ protected String _urlString;
+
+ protected URLConnection _connection;
+ protected InputStream _in=null;
+ transient boolean _useCaches = Resource.__defaultUseCaches;
+
+ /* ------------------------------------------------------------ */
+ protected URLResource(URL url, URLConnection connection)
+ {
+ _url = url;
+ _urlString=_url.toString();
+ _connection=connection;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected URLResource (URL url, URLConnection connection, boolean useCaches)
+ {
+ this (url, connection);
+ _useCaches = useCaches;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected synchronized boolean checkConnection()
+ {
+ if (_connection==null)
+ {
+ try{
+ _connection=_url.openConnection();
+ _connection.setUseCaches(_useCaches);
+ }
+ catch(IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ return _connection!=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Release any resources held by the resource.
+ */
+ @Override
+ public synchronized void release()
+ {
+ if (_in!=null)
+ {
+ try{_in.close();}catch(IOException e){LOG.ignore(e);}
+ _in=null;
+ }
+
+ if (_connection!=null)
+ _connection=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an input stream to the resource
+ */
+ @Override
+ public synchronized InputStream getInputStream()
+ throws java.io.IOException
+ {
+ if (!checkConnection())
+ throw new IOException( "Invalid resource");
+
+ try
+ {
+ if( _in != null)
+ {
+ InputStream in = _in;
+ _in=null;
+ return in;
+ }
+ return _connection.getInputStream();
+ }
+ finally
+ {
+ _connection=null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _urlString;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean equals( Object o)
+ {
+ return o instanceof URLResource && _urlString.equals(((URLResource)o)._urlString);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/CertificateUtils.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/CertificateUtils.java
new file mode 100644
index 00000000..83828f95
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/CertificateUtils.java
@@ -0,0 +1,93 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.CRL;
+import java.security.cert.CertificateFactory;
+import java.util.Collection;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+public class CertificateUtils
+{
+ /* ------------------------------------------------------------ */
+ public static KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+ {
+ KeyStore keystore = null;
+
+ if (storeStream != null || storePath != null)
+ {
+ InputStream inStream = storeStream;
+ try
+ {
+ if (inStream == null)
+ {
+ inStream = Resource.newResource(storePath).getInputStream();
+ }
+
+ if (storeProvider != null)
+ {
+ keystore = KeyStore.getInstance(storeType, storeProvider);
+ }
+ else
+ {
+ keystore = KeyStore.getInstance(storeType);
+ }
+
+ keystore.load(inStream, storePassword == null ? null : storePassword.toCharArray());
+ }
+ finally
+ {
+ if (inStream != null)
+ {
+ inStream.close();
+ }
+ }
+ }
+
+ return keystore;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+ {
+ Collection<? extends CRL> crlList = null;
+
+ if (crlPath != null)
+ {
+ InputStream in = null;
+ try
+ {
+ in = Resource.newResource(crlPath).getInputStream();
+ crlList = CertificateFactory.getInstance("X.509").generateCRLs(in);
+ }
+ finally
+ {
+ if (in != null)
+ {
+ in.close();
+ }
+ }
+ }
+
+ return crlList;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/CertificateValidator.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/CertificateValidator.java
new file mode 100644
index 00000000..b018b83f
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/CertificateValidator.java
@@ -0,0 +1,228 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CertPathBuilder;
+import java.security.cert.CertPathBuilderResult;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Convenience class to handle validation of certificates, aliases and keystores
+ *
+ * Allows specifying Certificate Revocation List (CRL), as well as enabling
+ * CRL Distribution Points Protocol (CRLDP) certificate extension support,
+ * and also enabling On-Line Certificate Status Protocol (OCSP) support.
+ *
+ * IMPORTANT: at least one of the above mechanisms *MUST* be configured and
+ * operational, otherwise certificate validation *WILL FAIL* unconditionally.
+ */
+public class CertificateValidator
+{
+ private static final Logger LOG = Log.getLogger(CertificateValidator.class);
+ private static AtomicLong __aliasCount = new AtomicLong();
+
+ private KeyStore _trustStore;
+ private Collection<? extends CRL> _crls;
+
+ /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+ private int _maxCertPathLength = -1;
+ /** CRL Distribution Points (CRLDP) support */
+ private boolean _enableCRLDP = false;
+ /** On-Line Certificate Status Protocol (OCSP) support */
+ private boolean _enableOCSP = false;
+
+ /**
+ * creates an instance of the certificate validator
+ *
+ * @param trustStore
+ * @param crls
+ */
+ public CertificateValidator(KeyStore trustStore, Collection<? extends CRL> crls)
+ {
+ if (trustStore == null)
+ {
+ throw new InvalidParameterException("TrustStore must be specified for CertificateValidator.");
+ }
+
+ _trustStore = trustStore;
+ _crls = crls;
+ }
+
+ /**
+ * validates a specific certificate inside of the keystore being passed in
+ *
+ * @param keyStore
+ * @param cert
+ * @throws CertificateException
+ */
+ public void validate(KeyStore keyStore, Certificate cert) throws CertificateException
+ {
+ Certificate[] certChain = null;
+
+ if (cert != null && cert instanceof X509Certificate)
+ {
+ ((X509Certificate)cert).checkValidity();
+
+ String certAlias = null;
+ try
+ {
+ if (keyStore == null)
+ {
+ throw new InvalidParameterException("Keystore cannot be null");
+ }
+
+ certAlias = keyStore.getCertificateAlias((X509Certificate)cert);
+ if (certAlias == null)
+ {
+ certAlias = "JETTY" + String.format("%016X",__aliasCount.incrementAndGet());
+ keyStore.setCertificateEntry(certAlias, cert);
+ }
+
+ certChain = keyStore.getCertificateChain(certAlias);
+ if (certChain == null || certChain.length == 0)
+ {
+ throw new IllegalStateException("Unable to retrieve certificate chain");
+ }
+ }
+ catch (KeyStoreException kse)
+ {
+ LOG.debug(kse);
+ throw new CertificateException("Unable to validate certificate" +
+ (certAlias == null ? "":" for alias [" +certAlias + "]") + ": " + kse.getMessage(), kse);
+ }
+
+ validate(certChain);
+ }
+ }
+
+ public void validate(Certificate[] certChain) throws CertificateException
+ {
+ try
+ {
+ ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
+ for (Certificate item : certChain)
+ {
+ if (item == null)
+ continue;
+
+ if (!(item instanceof X509Certificate))
+ {
+ throw new IllegalStateException("Invalid certificate type in chain");
+ }
+
+ certList.add((X509Certificate)item);
+ }
+
+ if (certList.isEmpty())
+ {
+ throw new IllegalStateException("Invalid certificate chain");
+
+ }
+
+ X509CertSelector certSelect = new X509CertSelector();
+ certSelect.setCertificate(certList.get(0));
+
+ // Configure certification path builder parameters
+ PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect);
+ pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList)));
+
+ // Set maximum certification path length
+ pbParams.setMaxPathLength(_maxCertPathLength);
+
+ // Enable revocation checking
+ pbParams.setRevocationEnabled(true);
+
+ // Set static Certificate Revocation List
+ if (_crls != null && !_crls.isEmpty())
+ {
+ pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls)));
+ }
+
+ // Enable On-Line Certificate Status Protocol (OCSP) support
+ if (_enableOCSP)
+ {
+ Security.setProperty("ocsp.enable","true");
+ }
+ // Enable Certificate Revocation List Distribution Points (CRLDP) support
+ if (_enableCRLDP)
+ {
+ System.setProperty("com.sun.security.enableCRLDP","true");
+ }
+
+ // Build certification path
+ CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams);
+
+ // Validate certification path
+ CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams);
+ }
+ catch (GeneralSecurityException gse)
+ {
+ LOG.debug(gse);
+ throw new CertificateException("Unable to validate certificate: " + gse.getMessage(), gse);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param maxCertPathLength
+ * maximum number of intermediate certificates in
+ * the certification path (-1 for unlimited)
+ */
+ public void setMaxCertPathLength(int maxCertPathLength)
+ {
+ _maxCertPathLength = maxCertPathLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Enables CRL Distribution Points Support
+ * @param enableCRLDP true - turn on, false - turns off
+ */
+ public void setEnableCRLDP(boolean enableCRLDP)
+ {
+ _enableCRLDP = enableCRLDP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Enables On-Line Certificate Status Protocol support
+ * @param enableOCSP true - turn on, false - turn off
+ */
+ public void setEnableOCSP(boolean enableOCSP)
+ {
+ _enableOCSP = enableOCSP;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/Password.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/Password.java
new file mode 100644
index 00000000..255eee60
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/security/Password.java
@@ -0,0 +1,84 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+/* ------------------------------------------------------------ */
+/**
+ * Password utility class.
+ *
+ * This utility class gets a password or pass phrase either by:
+ *
+ * <PRE>
+ * + Password is set as a system property.
+ * + The password is prompted for and read from standard input
+ * + A program is run to get the password.
+ * </pre>
+ *
+ * Passwords that begin with OBF: are de obfuscated. Passwords can be obfuscated
+ * by run org.eclipse.util.Password as a main class. Obfuscated password are
+ * required if a system needs to recover the full password (eg. so that it may
+ * be passed to another system). They are not secure, but prevent casual
+ * observation.
+ * <p>
+ * Passwords that begin with CRYPT: are oneway encrypted with UnixCrypt. The
+ * real password cannot be retrieved, but comparisons can be made to other
+ * passwords. A Crypt can be generated by running org.eclipse.util.UnixCrypt as
+ * a main class, passing password and then the username. Checksum passwords are
+ * a secure(ish) way to store passwords that only need to be checked rather than
+ * recovered. Note that it is not strong security - specially if simple
+ * passwords are used.
+ *
+ *
+ */
+public class Password
+{
+ public static final String __OBFUSCATE = "OBF:";
+
+ private String _pw;
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _pw;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+
+ if (null == o)
+ return false;
+
+ if (o instanceof Password)
+ {
+ Password p = (Password) o;
+ //noinspection StringEquality
+ return p._pw == _pw || (null != _pw && _pw.equals(p._pw));
+ }
+
+ if (o instanceof String)
+ return o.equals(_pw);
+
+ return false;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java
new file mode 100644
index 00000000..4c0da301
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java
@@ -0,0 +1,107 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509KeyManager;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * KeyManager to select a key with desired alias
+ * while delegating processing to specified KeyManager
+ * Can be used both with server and client sockets
+ */
+public class AliasedX509ExtendedKeyManager extends X509ExtendedKeyManager
+{
+ private String _keyAlias;
+ private X509KeyManager _keyManager;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Construct KeyManager instance
+ * @param keyAlias Alias of the key to be selected
+ * @param keyManager Instance of KeyManager to be wrapped
+ * @throws Exception
+ */
+ public AliasedX509ExtendedKeyManager(String keyAlias, X509KeyManager keyManager) throws Exception
+ {
+ _keyAlias = keyAlias;
+ _keyManager = keyManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket)
+ */
+ public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
+ {
+ return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
+ */
+ public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
+ {
+ return _keyAlias == null ? _keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[])
+ */
+ public String[] getClientAliases(String keyType, Principal[] issuers)
+ {
+ return _keyManager.getClientAliases(keyType, issuers);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[])
+ */
+ public String[] getServerAliases(String keyType, Principal[] issuers)
+ {
+ return _keyManager.getServerAliases(keyType, issuers);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String)
+ */
+ public X509Certificate[] getCertificateChain(String alias)
+ {
+ return _keyManager.getCertificateChain(alias);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String)
+ */
+ public PrivateKey getPrivateKey(String alias)
+ {
+ return _keyManager.getPrivateKey(alias);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ssl/SslContextFactory.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ssl/SslContextFactory.java
new file mode 100644
index 00000000..4ac6ed95
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/ssl/SslContextFactory.java
@@ -0,0 +1,620 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import javax.net.ssl.CertPathTrustManagerParameters;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.CertificateUtils;
+import org.eclipse.jetty.util.security.CertificateValidator;
+import org.eclipse.jetty.util.security.Password;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * SslContextFactory is used to configure SSL connectors
+ * as well as HttpClient. It holds all SSL parameters and
+ * creates SSL context based on these parameters to be
+ * used by the SSL connectors.
+ *
+ * modified by KNOWLEDGECODE
+ */
+public class SslContextFactory extends AbstractLifeCycle
+{
+ public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509TrustManager()
+ {
+ public java.security.cert.X509Certificate[] getAcceptedIssuers()
+ {
+ return new java.security.cert.X509Certificate[]{};
+ }
+
+ public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
+ {
+ }
+
+ public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
+ {
+ /**
+ * workaround for SSL bugs in 4.0.3 and lower
+ * @see https://github.com/koush/AndroidAsync/blob/master/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java
+ */
+ if (Build.VERSION.SDK_INT <= 15)
+ {
+ for (java.security.cert.X509Certificate cert : certs)
+ {
+ if (cert != null && cert.getCriticalExtensionOIDs() != null)
+ {
+ cert.getCriticalExtensionOIDs().remove("2.5.29.15");
+ }
+ }
+ }
+ }
+ }};
+
+ private static final Logger LOG = Log.getLogger(SslContextFactory.class);
+
+ public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM =
+ (Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ?
+ "SunX509" : Security.getProperty("ssl.KeyManagerFactory.algorithm"));
+ public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM =
+ (Security.getProperty("ssl.TrustManagerFactory.algorithm") == null ?
+ "SunX509" : Security.getProperty("ssl.TrustManagerFactory.algorithm"));
+
+ /** Default value for the keystore location path. */
+ public static final String DEFAULT_KEYSTORE_PATH =
+ System.getProperty("user.home") + File.separator + ".keystore";
+
+ /** String name of key password property. */
+ public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword";
+
+ /** String name of keystore password property. */
+ public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+
+ /** Excluded protocols. */
+ private final Set<String> _excludeProtocols = new LinkedHashSet<String>();
+ /** Included protocols. */
+ private Set<String> _includeProtocols = new LinkedHashSet<String>();
+
+ /** Excluded cipher suites. */
+ private final Set<String> _excludeCipherSuites = new LinkedHashSet<String>();
+ /** Included cipher suites. */
+ private Set<String> _includeCipherSuites = new LinkedHashSet<String>();
+
+ /** Keystore path. */
+ private String _keyStorePath;
+ /** Keystore provider name */
+ private String _keyStoreProvider;
+ /** Keystore type */
+ private String _keyStoreType = "JKS";
+ /** Keystore input stream */
+ private InputStream _keyStoreInputStream;
+
+ /** SSL certificate alias */
+ private String _certAlias;
+
+ /** Truststore path */
+ private String _trustStorePath;
+ /** Truststore provider name */
+ private String _trustStoreProvider;
+ /** Truststore type */
+ private String _trustStoreType = "JKS";
+ /** Truststore input stream */
+ private InputStream _trustStoreInputStream;
+
+ /** Set to true if client certificate authentication is required */
+ private boolean _needClientAuth = false;
+ /** Set to true if client certificate authentication is desired */
+ private boolean _wantClientAuth = false;
+
+ /** Keystore password */
+ private transient Password _keyStorePassword;
+ /** Key manager password */
+ private transient Password _keyManagerPassword;
+ /** Truststore password */
+ private transient Password _trustStorePassword;
+
+ /** SSL provider name */
+ private String _sslProvider;
+ /** SSL protocol name */
+ private String _sslProtocol = "TLS";
+
+ /** SecureRandom algorithm */
+ private String _secureRandomAlgorithm;
+ /** KeyManager factory algorithm */
+ private String _keyManagerFactoryAlgorithm = DEFAULT_KEYMANAGERFACTORY_ALGORITHM;
+ /** TrustManager factory algorithm */
+ private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM;
+
+ /** Set to true if SSL certificate validation is required */
+ private boolean _validateCerts;
+ /** Set to true if SSL certificate of the peer validation is required */
+ private boolean _validatePeerCerts;
+ /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+ private int _maxCertPathLength = -1;
+ /** Path to file that contains Certificate Revocation List */
+ private String _crlPath;
+ /** Set to true to enable CRL Distribution Points (CRLDP) support */
+ private boolean _enableCRLDP = false;
+ /** Set to true to enable On-Line Certificate Status Protocol (OCSP) support */
+ private boolean _enableOCSP = false;
+ /** Location of OCSP Responder */
+ private String _ocspResponderURL;
+
+ /** SSL keystore */
+ private KeyStore _keyStore;
+ /** SSL truststore */
+ private KeyStore _trustStore;
+ /** Set to true to enable SSL Session caching */
+ private boolean _sessionCachingEnabled = true;
+
+ /** SSL context */
+ private SSLContext _context;
+
+ private boolean _trustAll;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Construct an instance of SslContextFactory
+ * Default constructor for use in XmlConfiguration files
+ */
+ public SslContextFactory()
+ {
+ _trustAll=true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create the SSLContext object and start the lifecycle
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @SuppressLint("TrulyRandom")
+ @Override
+ protected void doStart() throws Exception
+ {
+ if (_context == null)
+ {
+ if (_keyStore==null && _keyStoreInputStream == null && _keyStorePath == null &&
+ _trustStore==null && _trustStoreInputStream == null && _trustStorePath == null )
+ {
+ TrustManager[] trust_managers=null;
+
+ if (_trustAll)
+ {
+ LOG.debug("No keystore or trust store configured. ACCEPTING UNTRUSTED CERTIFICATES!!!!!");
+ // Create a trust manager that does not validate certificate chains
+ trust_managers = TRUST_ALL_CERTS;
+ }
+
+ SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+ _context = (_sslProvider == null)?SSLContext.getInstance(_sslProtocol):SSLContext.getInstance(_sslProtocol,_sslProvider);
+ _context.init(null, trust_managers, secureRandom);
+ }
+ else
+ {
+ // verify that keystore and truststore
+ // parameters are set up correctly
+ checkKeyStore();
+
+ KeyStore keyStore = loadKeyStore();
+ KeyStore trustStore = loadTrustStore();
+
+ Collection<? extends CRL> crls = loadCRL(_crlPath);
+
+ if (_validateCerts && keyStore != null)
+ {
+ if (_certAlias == null)
+ {
+ List<String> aliases = Collections.list(keyStore.aliases());
+ _certAlias = aliases.size() == 1 ? aliases.get(0) : null;
+ }
+
+ Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias);
+ if (cert == null)
+ {
+ throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias));
+ }
+
+ CertificateValidator validator = new CertificateValidator(trustStore, crls);
+ validator.setMaxCertPathLength(_maxCertPathLength);
+ validator.setEnableCRLDP(_enableCRLDP);
+ validator.setEnableOCSP(_enableOCSP);
+ validator.validate(keyStore, cert);
+ }
+
+ KeyManager[] keyManagers = getKeyManagers(keyStore);
+ TrustManager[] trustManagers = getTrustManagers(trustStore,crls);
+
+ SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+ _context = (_sslProvider == null)?SSLContext.getInstance(_sslProtocol):SSLContext.getInstance(_sslProtocol,_sslProvider);
+ _context.init(keyManagers,trustManagers,secureRandom);
+
+ SSLEngine engine=newSslEngine();
+
+ LOG.info("Enabled Protocols {} of {}",Arrays.asList(engine.getEnabledProtocols()),Arrays.asList(engine.getSupportedProtocols()));
+ if (LOG.isDebugEnabled())
+ LOG.debug("Enabled Ciphers {} of {}",Arrays.asList(engine.getEnabledCipherSuites()),Arrays.asList(engine.getSupportedCipherSuites()));
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if SSL needs client authentication.
+ * @see SSLEngine#getNeedClientAuth()
+ */
+ public boolean getNeedClientAuth()
+ {
+ return _needClientAuth;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if SSL wants client authentication.
+ * @see SSLEngine#getWantClientAuth()
+ */
+ public boolean getWantClientAuth()
+ {
+ return _wantClientAuth;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Override this method to provide alternate way to load a keystore.
+ *
+ * @return the key store instance
+ * @throws Exception if the keystore cannot be loaded
+ */
+ protected KeyStore loadKeyStore() throws Exception
+ {
+ return _keyStore != null ? _keyStore : getKeyStore(_keyStoreInputStream,
+ _keyStorePath, _keyStoreType, _keyStoreProvider,
+ _keyStorePassword==null? null: _keyStorePassword.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Override this method to provide alternate way to load a truststore.
+ *
+ * @return the key store instance
+ * @throws Exception if the truststore cannot be loaded
+ */
+ protected KeyStore loadTrustStore() throws Exception
+ {
+ return _trustStore != null ? _trustStore : getKeyStore(_trustStoreInputStream,
+ _trustStorePath, _trustStoreType, _trustStoreProvider,
+ _trustStorePassword==null? null: _trustStorePassword.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Loads keystore using an input stream or a file path in the same
+ * order of precedence.
+ *
+ * Required for integrations to be able to override the mechanism
+ * used to load a keystore in order to provide their own implementation.
+ *
+ * @param storeStream keystore input stream
+ * @param storePath path of keystore file
+ * @param storeType keystore type
+ * @param storeProvider keystore provider
+ * @param storePassword keystore password
+ * @return created keystore
+ * @throws Exception if the keystore cannot be obtained
+ */
+ protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+ {
+ return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Loads certificate revocation list (CRL) from a file.
+ *
+ * Required for integrations to be able to override the mechanism used to
+ * load CRL in order to provide their own implementation.
+ *
+ * @param crlPath path of certificate revocation list file
+ * @return Collection of CRL's
+ * @throws Exception if the certificate revocation list cannot be loaded
+ */
+ protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+ {
+ return CertificateUtils.loadCRL(crlPath);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception
+ {
+ KeyManager[] managers = null;
+
+ if (keyStore != null)
+ {
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerFactoryAlgorithm);
+ keyManagerFactory.init(keyStore,_keyManagerPassword == null?(_keyStorePassword == null?null:_keyStorePassword.toString().toCharArray()):_keyManagerPassword.toString().toCharArray());
+ managers = keyManagerFactory.getKeyManagers();
+
+ if (_certAlias != null)
+ {
+ for (int idx = 0; idx < managers.length; idx++)
+ {
+ if (managers[idx] instanceof X509KeyManager)
+ {
+ managers[idx] = new AliasedX509ExtendedKeyManager(_certAlias,(X509KeyManager)managers[idx]);
+ }
+ }
+ }
+ }
+
+ return managers;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception
+ {
+ TrustManager[] managers = null;
+ if (trustStore != null)
+ {
+ // Revocation checking is only supported for PKIX algorithm
+ if (_validatePeerCerts && _trustManagerFactoryAlgorithm.equalsIgnoreCase("PKIX"))
+ {
+ PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore,new X509CertSelector());
+
+ // Set maximum certification path length
+ pbParams.setMaxPathLength(_maxCertPathLength);
+
+ // Make sure revocation checking is enabled
+ pbParams.setRevocationEnabled(true);
+
+ if (crls != null && !crls.isEmpty())
+ {
+ pbParams.addCertStore(CertStore.getInstance("Collection",new CollectionCertStoreParameters(crls)));
+ }
+
+ if (_enableCRLDP)
+ {
+ // Enable Certificate Revocation List Distribution Points (CRLDP) support
+ System.setProperty("com.sun.security.enableCRLDP","true");
+ }
+
+ if (_enableOCSP)
+ {
+ // Enable On-Line Certificate Status Protocol (OCSP) support
+ Security.setProperty("ocsp.enable","true");
+
+ if (_ocspResponderURL != null)
+ {
+ // Override location of OCSP Responder
+ Security.setProperty("ocsp.responderURL", _ocspResponderURL);
+ }
+ }
+
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
+ trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams));
+
+ managers = trustManagerFactory.getTrustManagers();
+ }
+ else
+ {
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
+ trustManagerFactory.init(trustStore);
+
+ managers = trustManagerFactory.getTrustManagers();
+ }
+ }
+
+ return managers;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Check KeyStore Configuration. Ensures that if keystore has been
+ * configured but there's no truststore, that keystore is
+ * used as truststore.
+ * @throws IllegalStateException if SslContextFactory configuration can't be used.
+ */
+ public void checkKeyStore()
+ {
+ if (_context != null)
+ return; //nothing to check if using preconfigured context
+
+
+ if (_keyStore == null && _keyStoreInputStream == null && _keyStorePath == null)
+ throw new IllegalStateException("SSL doesn't have a valid keystore");
+
+ // if the keystore has been configured but there is no
+ // truststore configured, use the keystore as the truststore
+ if (_trustStore == null && _trustStoreInputStream == null && _trustStorePath == null)
+ {
+ _trustStore = _keyStore;
+ _trustStorePath = _keyStorePath;
+ _trustStoreInputStream = _keyStoreInputStream;
+ _trustStoreType = _keyStoreType;
+ _trustStoreProvider = _keyStoreProvider;
+ _trustStorePassword = _keyStorePassword;
+ _trustManagerFactoryAlgorithm = _keyManagerFactoryAlgorithm;
+ }
+
+ // It's the same stream we cannot read it twice, so read it once in memory
+ if (_keyStoreInputStream != null && _keyStoreInputStream == _trustStoreInputStream)
+ {
+ try
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ IO.copy(_keyStoreInputStream, baos);
+ _keyStoreInputStream.close();
+
+ _keyStoreInputStream = new ByteArrayInputStream(baos.toByteArray());
+ _trustStoreInputStream = new ByteArrayInputStream(baos.toByteArray());
+ }
+ catch (Exception ex)
+ {
+ throw new IllegalStateException(ex);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Select protocols to be used by the connector
+ * based on configured inclusion and exclusion lists
+ * as well as enabled and supported protocols.
+ * @param enabledProtocols Array of enabled protocols
+ * @param supportedProtocols Array of supported protocols
+ * @return Array of protocols to enable
+ */
+ public String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols)
+ {
+ Set<String> selected_protocols = new LinkedHashSet<String>();
+
+ // Set the starting protocols - either from the included or enabled list
+ if (!_includeProtocols.isEmpty())
+ {
+ // Use only the supported included protocols
+ for (String protocol : _includeProtocols)
+ if(Arrays.asList(supportedProtocols).contains(protocol))
+ selected_protocols.add(protocol);
+ }
+ else
+ selected_protocols.addAll(Arrays.asList(enabledProtocols));
+
+
+ // Remove any excluded protocols
+ if (_excludeProtocols != null)
+ selected_protocols.removeAll(_excludeProtocols);
+
+ return selected_protocols.toArray(new String[selected_protocols.size()]);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Select cipher suites to be used by the connector
+ * based on configured inclusion and exclusion lists
+ * as well as enabled and supported cipher suite lists.
+ * @param enabledCipherSuites Array of enabled cipher suites
+ * @param supportedCipherSuites Array of supported cipher suites
+ * @return Array of cipher suites to enable
+ */
+ public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites)
+ {
+ Set<String> selected_ciphers = new LinkedHashSet<String>();
+
+ // Set the starting ciphers - either from the included or enabled list
+ if (!_includeCipherSuites.isEmpty())
+ {
+ // Use only the supported included ciphers
+ for (String cipherSuite : _includeCipherSuites)
+ if(Arrays.asList(supportedCipherSuites).contains(cipherSuite))
+ selected_ciphers.add(cipherSuite);
+ }
+ else
+ selected_ciphers.addAll(Arrays.asList(enabledCipherSuites));
+
+
+ // Remove any excluded ciphers
+ if (_excludeCipherSuites != null)
+ selected_ciphers.removeAll(_excludeCipherSuites);
+ return selected_ciphers.toArray(new String[selected_ciphers.size()]);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if SSL Session caching is enabled
+ */
+ public boolean isSessionCachingEnabled()
+ {
+ return _sessionCachingEnabled;
+ }
+
+ /* ------------------------------------------------------------ */
+ public SSLEngine newSslEngine(String host,int port)
+ {
+ SSLEngine sslEngine=isSessionCachingEnabled()
+ ?_context.createSSLEngine(host, port)
+ :_context.createSSLEngine();
+
+ customize(sslEngine);
+ return sslEngine;
+ }
+
+ /* ------------------------------------------------------------ */
+ public SSLEngine newSslEngine()
+ {
+ SSLEngine sslEngine=_context.createSSLEngine();
+ customize(sslEngine);
+ return sslEngine;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void customize(SSLEngine sslEngine)
+ {
+ if (getWantClientAuth())
+ sslEngine.setWantClientAuth(getWantClientAuth());
+ if (getNeedClientAuth())
+ sslEngine.setNeedClientAuth(getNeedClientAuth());
+
+ sslEngine.setEnabledCipherSuites(selectCipherSuites(
+ sslEngine.getEnabledCipherSuites(),
+ sslEngine.getSupportedCipherSuites()));
+
+ sslEngine.setEnabledProtocols(selectProtocols(sslEngine.getEnabledProtocols(),sslEngine.getSupportedProtocols()));
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return String.format("%s@%x(%s,%s)",
+ getClass().getSimpleName(),
+ hashCode(),
+ _keyStorePath,
+ _trustStorePath);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/QueuedThreadPool.java
new file mode 100644
index 00000000..28cf3bd4
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -0,0 +1,385 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.util.thread;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.ConcurrentHashSet;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class QueuedThreadPool extends AbstractLifeCycle implements ThreadPool, Executor, Dumpable
+{
+ private static final Logger LOG = Log.getLogger(QueuedThreadPool.class);
+
+ private final AtomicInteger _threadsStarted = new AtomicInteger();
+ private final AtomicInteger _threadsIdle = new AtomicInteger();
+ private final AtomicLong _lastShrink = new AtomicLong();
+ private final ConcurrentHashSet<Thread> _threads=new ConcurrentHashSet<Thread>();
+ private final Object _joinLock = new Object();
+ private BlockingQueue<Runnable> _jobs;
+ private String _name;
+ private int _maxIdleTimeMs=60000;
+ private int _maxThreads=254;
+ private int _minThreads=8;
+ private int _maxQueued=-1;
+ private int _priority=Thread.NORM_PRIORITY;
+ private boolean _daemon=false;
+ private int _maxStopTime=100;
+ private boolean _detailedDump=false;
+
+ /* ------------------------------------------------------------------- */
+ /** Construct
+ */
+ public QueuedThreadPool()
+ {
+ _name="qtp"+super.hashCode();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ _threadsStarted.set(0);
+
+ if (_jobs==null)
+ {
+ _jobs=_maxQueued>0 ?new ArrayBlockingQueue<Runnable>(_maxQueued)
+ :new BlockingArrayQueue<Runnable>(_minThreads,_minThreads);
+ }
+
+ int threads=_threadsStarted.get();
+ while (isRunning() && threads<_minThreads)
+ {
+ startThread(threads);
+ threads=_threadsStarted.get();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ long start=System.currentTimeMillis();
+
+ // let jobs complete naturally for a while
+ while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < (_maxStopTime/2))
+ Thread.sleep(1);
+
+ // kill queued jobs and flush out idle jobs
+ _jobs.clear();
+ Runnable noop = new Runnable(){public void run(){}};
+ for (int i=_threadsIdle.get();i-->0;)
+ _jobs.offer(noop);
+ Thread.yield();
+
+ // interrupt remaining threads
+ if (_threadsStarted.get()>0)
+ for (Thread thread : _threads)
+ thread.interrupt();
+
+ // wait for remaining threads to die
+ while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < _maxStopTime)
+ {
+ Thread.sleep(1);
+ }
+ Thread.yield();
+ int size=_threads.size();
+ if (size>0)
+ {
+ LOG.warn(size+" threads could not be stopped");
+
+ if (size==1 || LOG.isDebugEnabled())
+ {
+ for (Thread unstopped : _threads)
+ {
+ LOG.info("Couldn't stop "+unstopped);
+ for (StackTraceElement element : unstopped.getStackTrace())
+ {
+ LOG.info(" at "+element);
+ }
+ }
+ }
+ }
+
+ synchronized (_joinLock)
+ {
+ _joinLock.notifyAll();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the maximum number of threads.
+ * Delegated to the named or anonymous Pool.
+ * @see #setMaxThreads
+ * @return maximum number of threads.
+ */
+ public int getMaxThreads()
+ {
+ return _maxThreads;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the minimum number of threads.
+ * Delegated to the named or anonymous Pool.
+ * @see #setMinThreads
+ * @return minimum number of threads.
+ */
+ public int getMinThreads()
+ {
+ return _minThreads;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean dispatch(Runnable job)
+ {
+ if (isRunning())
+ {
+ final int jobQ = _jobs.size();
+ final int idle = getIdleThreads();
+ if(_jobs.offer(job))
+ {
+ // If we had no idle threads or the jobQ is greater than the idle threads
+ if (idle==0 || jobQ>idle)
+ {
+ int threads=_threadsStarted.get();
+ if (threads<_maxThreads)
+ startThread(threads);
+ }
+ return true;
+ }
+ }
+ LOG.debug("Dispatched {} to stopped {}",job,this);
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void execute(Runnable job)
+ {
+ if (!dispatch(job))
+ throw new RejectedExecutionException();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The total number of threads currently in the pool
+ */
+ public int getThreads()
+ {
+ return _threadsStarted.get();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The number of idle threads in the pool
+ */
+ public int getIdleThreads()
+ {
+ return _threadsIdle.get();
+ }
+
+ /* ------------------------------------------------------------ */
+ private boolean startThread(int threads)
+ {
+ final int next=threads+1;
+ if (!_threadsStarted.compareAndSet(threads,next))
+ return false;
+
+ boolean started=false;
+ try
+ {
+ Thread thread=newThread(_runnable);
+ thread.setDaemon(_daemon);
+ thread.setPriority(_priority);
+ thread.setName(_name+"-"+thread.getId());
+ _threads.add(thread);
+
+ thread.start();
+ started=true;
+ }
+ finally
+ {
+ if (!started)
+ _threadsStarted.decrementAndGet();
+ }
+ return started;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Thread newThread(Runnable runnable)
+ {
+ return new Thread(runnable);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ List<Object> dump = new ArrayList<Object>(getMaxThreads());
+ for (final Thread thread: _threads)
+ {
+ final StackTraceElement[] trace=thread.getStackTrace();
+ boolean inIdleJobPoll=false;
+ // trace can be null on early java 6 jvms
+ if (trace != null)
+ {
+ for (StackTraceElement t : trace)
+ {
+ if ("idleJobPoll".equals(t.getMethodName()))
+ {
+ inIdleJobPoll = true;
+ break;
+ }
+ }
+ }
+ final boolean idle=inIdleJobPoll;
+
+ if (_detailedDump)
+ {
+ dump.add(new Dumpable()
+ {
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ out.append(String.valueOf(thread.getId())).append(' ').append(thread.getName()).append(' ').append(thread.getState().toString()).append(idle?" IDLE":"").append('\n');
+ if (!idle)
+ AggregateLifeCycle.dump(out,indent,Arrays.asList(trace));
+ }
+ });
+ }
+ else
+ {
+ dump.add(thread.getId()+" "+thread.getName()+" "+thread.getState()+" @ "+(trace.length>0?trace[0]:"???")+(idle?" IDLE":""));
+ }
+ }
+
+ AggregateLifeCycle.dumpObject(out,this);
+ AggregateLifeCycle.dump(out,indent,dump);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _name+"{"+getMinThreads()+"<="+getIdleThreads()+"<="+getThreads()+"/"+getMaxThreads()+","+(_jobs==null?-1:_jobs.size())+"}";
+ }
+
+ /* ------------------------------------------------------------ */
+ private Runnable idleJobPoll() throws InterruptedException
+ {
+ return _jobs.poll(_maxIdleTimeMs,TimeUnit.MILLISECONDS);
+ }
+
+ /* ------------------------------------------------------------ */
+ private Runnable _runnable = new Runnable()
+ {
+ public void run()
+ {
+ boolean shrink=false;
+ try
+ {
+ Runnable job=_jobs.poll();
+ while (isRunning())
+ {
+ // Job loop
+ while (job!=null && isRunning())
+ {
+ runJob(job);
+ job=_jobs.poll();
+ }
+
+ // Idle loop
+ try
+ {
+ _threadsIdle.incrementAndGet();
+
+ while (isRunning() && job==null)
+ {
+ if (_maxIdleTimeMs<=0)
+ job=_jobs.take();
+ else
+ {
+ // maybe we should shrink?
+ final int size=_threadsStarted.get();
+ if (size>_minThreads)
+ {
+ long last=_lastShrink.get();
+ long now=System.currentTimeMillis();
+ if (last==0 || (now-last)>_maxIdleTimeMs)
+ {
+ shrink=_lastShrink.compareAndSet(last,now) &&
+ _threadsStarted.compareAndSet(size,size-1);
+ if (shrink)
+ return;
+ }
+ }
+ job=idleJobPoll();
+ }
+ }
+ }
+ finally
+ {
+ _threadsIdle.decrementAndGet();
+ }
+ }
+ }
+ catch(InterruptedException e)
+ {
+ LOG.ignore(e);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ finally
+ {
+ if (!shrink)
+ _threadsStarted.decrementAndGet();
+ _threads.remove(Thread.currentThread());
+ }
+ }
+ };
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Runs the given job in the {@link Thread#currentThread() current thread}.</p>
+ * <p>Subclasses may override to perform pre/post actions before/after the job is run.</p>
+ *
+ * @param job the job to run
+ */
+ protected void runJob(Runnable job)
+ {
+ job.run();
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/ThreadPool.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/ThreadPool.java
new file mode 100644
index 00000000..4bff2682
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/ThreadPool.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+/* ------------------------------------------------------------ */
+/** ThreadPool.
+ *
+ *
+ */
+public interface ThreadPool
+{
+ /* ------------------------------------------------------------ */
+ public abstract boolean dispatch(Runnable job);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The total number of threads currently in the pool
+ */
+ public int getThreads();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The number of idle threads in the pool
+ */
+ public int getIdleThreads();
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/Timeout.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/Timeout.java
new file mode 100644
index 00000000..d8086531
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/util/thread/Timeout.java
@@ -0,0 +1,282 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Timeout queue.
+ * This class implements a timeout queue for timers that are at least as likely to be cancelled as they are to expire.
+ * Unlike the util timeout class, the duration of the timeouts is shared by all scheduled tasks and if the duration
+ * is changed, this affects all scheduled tasks.
+ * <p>
+ * The nested class Task should be extended by users of this class to obtain call back notification of
+ * expires.
+ */
+public class Timeout
+{
+ private static final Logger LOG = Log.getLogger(Timeout.class);
+ private Object _lock;
+ private long _duration;
+ private volatile long _now=System.currentTimeMillis();
+ private Task _head=new Task();
+
+ /* ------------------------------------------------------------ */
+ public Timeout(Object lock)
+ {
+ _lock=lock;
+ _head._timeout=this;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param duration The duration to set.
+ */
+ public void setDuration(long duration)
+ {
+ _duration = duration;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getNow()
+ {
+ return _now;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setNow(long now)
+ {
+ _now=now;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get an expired tasks.
+ * This is called instead of {@link #tick()} to obtain the next
+ * expired Task, but without calling it's {@link Task#expire()} or
+ * {@link Task#expired()} methods.
+ *
+ * @return the next expired task or null.
+ */
+ public Task expired()
+ {
+ synchronized (_lock)
+ {
+ long _expiry = _now-_duration;
+
+ if (_head._next!=_head)
+ {
+ Task task = _head._next;
+ if (task._timestamp>_expiry)
+ return null;
+
+ task.unlink();
+ task._expired=true;
+ return task;
+ }
+ return null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void tick()
+ {
+ final long expiry = _now-_duration;
+
+ Task task=null;
+ while (true)
+ {
+ try
+ {
+ synchronized (_lock)
+ {
+ task= _head._next;
+ if (task==_head || task._timestamp>expiry)
+ break;
+ task.unlink();
+ task._expired=true;
+ task.expire();
+ }
+
+ task.expired();
+ }
+ catch(Throwable th)
+ {
+ LOG.warn(Log.EXCEPTION,th);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void schedule(Task task)
+ {
+ schedule(task,0L);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param task
+ * @param delay A delay in addition to the default duration of the timeout
+ */
+ public void schedule(Task task,long delay)
+ {
+ synchronized (_lock)
+ {
+ if (task._timestamp!=0)
+ {
+ task.unlink();
+ task._timestamp=0;
+ }
+ task._timeout=this;
+ task._expired=false;
+ task._delay=delay;
+ task._timestamp = _now+delay;
+
+ Task last=_head._prev;
+ while (last!=_head)
+ {
+ if (last._timestamp <= task._timestamp)
+ break;
+ last=last._prev;
+ }
+ last.link(task);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void cancelAll()
+ {
+ synchronized (_lock)
+ {
+ _head._next=_head._prev=_head;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getTimeToNext()
+ {
+ synchronized (_lock)
+ {
+ if (_head._next==_head)
+ return -1;
+ long to_next = _duration+_head._next._timestamp-_now;
+ return to_next<0?0:to_next;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append(super.toString());
+
+ Task task = _head._next;
+ while (task!=_head)
+ {
+ buf.append("-->");
+ buf.append(task);
+ task=task._next;
+ }
+
+ return buf.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /** Task.
+ * The base class for scheduled timeouts. This class should be
+ * extended to implement the expire() method, which is called if the
+ * timeout expires.
+ *
+ *
+ *
+ */
+ public static class Task
+ {
+ Task _next;
+ Task _prev;
+ Timeout _timeout;
+ long _delay;
+ long _timestamp=0;
+ boolean _expired=false;
+
+ /* ------------------------------------------------------------ */
+ protected Task()
+ {
+ _next=_prev=this;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void unlink()
+ {
+ _next._prev=_prev;
+ _prev._next=_next;
+ _next=_prev=this;
+ _expired=false;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void link(Task task)
+ {
+ Task next_next = _next;
+ _next._prev=task;
+ _next=task;
+ _next._next=next_next;
+ _next._prev=this;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Cancel the task.
+ * Remove the task from the timeout.
+ */
+ public void cancel()
+ {
+ Timeout timeout = _timeout;
+ if (timeout!=null)
+ {
+ synchronized (timeout._lock)
+ {
+ unlink();
+ _timestamp=0;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Expire task.
+ * This method is called when the timeout expires. It is called
+ * in the scope of the synchronize block (on this) that sets
+ * the {@link #isExpired()} state to true.
+ * @see #expired() For an unsynchronized callback.
+ */
+ protected void expire(){}
+
+ /* ------------------------------------------------------------ */
+ /** Expire task.
+ * This method is called when the timeout expires. It is called
+ * outside of any synchronization scope and may be delayed.
+ *
+ */
+ public void expired(){}
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/Extension.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/Extension.java
new file mode 100644
index 00000000..936aa5fb
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/Extension.java
@@ -0,0 +1,31 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.util.Map;
+
+public interface Extension extends WebSocketParser.FrameHandler, WebSocketGenerator
+{
+ public String getName();
+ public String getParameterizedName();
+
+ public boolean init(Map<String,String> parameters);
+ public void bind(WebSocket.FrameConnection connection, WebSocketParser.FrameHandler inbound, WebSocketGenerator outbound);
+
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/MaskGen.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/MaskGen.java
new file mode 100644
index 00000000..9178d906
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/MaskGen.java
@@ -0,0 +1,24 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+public interface MaskGen
+{
+ void genMask(byte[] mask);
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/PerMessageDeflateExtension.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/PerMessageDeflateExtension.java
new file mode 100644
index 00000000..fdd11d93
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/PerMessageDeflateExtension.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.WebSocket.FrameConnection;
+import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.text.TextUtils;
+
+/**
+ * PerMessageDeflateExtension
+ *
+ * Copyright (c) 2015 KNOWLEDGECODE
+ */
+public class PerMessageDeflateExtension implements Extension {
+
+ private static final Logger __log = Log.getLogger(PerMessageDeflateExtension.class.getName());
+ private static final byte[] TAIL_BYTES = new byte[] { 0x00, 0x00, (byte) 0xff, (byte) 0xff };
+ private static final int INITIAL_CAPACITY = 8192;
+ private static final String EXTENSION = "permessage-deflate";
+ private static final String CLIENT_NO_CONTEXT_TAKEOVER = "client_no_context_takeover";
+ private static final String SERVER_NO_CONTEXT_TAKEOVER = "server_no_context_takeover";
+ private static final String SERVER_MAX_WINDOW_BITS = "server_max_window_bits";
+
+ private static class Zlib {
+ protected Deflater _deflater;
+ protected Inflater _inflater;
+ protected WebSocketBuffer _buffer;
+ protected byte[] _buf;
+ protected int _capcacity;
+ protected boolean _deflaterReset;
+ protected boolean _inflaterReset;
+
+ public Zlib(boolean deflaterReset, boolean inflaterReset) {
+ _deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
+ _inflater = new Inflater(true);
+ _buffer = new WebSocketBuffer(INITIAL_CAPACITY);
+ _buf = new byte[INITIAL_CAPACITY];
+ _capcacity = INITIAL_CAPACITY;
+ _deflaterReset = deflaterReset;
+ _inflaterReset = inflaterReset;
+ }
+
+ public byte[] compress(byte[] b, int offset, int length) {
+ int len;
+ int index = 0;
+
+ _deflater.reset();
+ _deflater.setInput(b, offset, length);
+ _deflater.finish();
+ while ((len = _deflater.deflate(_buf, index, _capcacity - index)) > 0) {
+ if ((index += len) == _capcacity) {
+ _buf = Arrays.copyOf(_buf, _capcacity <<= 1);
+ }
+ }
+ return Arrays.copyOf(_buf, index);
+ }
+
+ public Buffer decompress(byte[] b, int offset, int length) throws DataFormatException {
+ int len;
+ int index = 0;
+
+ if (_inflaterReset) {
+ _inflater.reset();
+ }
+ _inflater.setInput(b, offset, length);
+ while ((len = _inflater.inflate(_buf, index, _capcacity - index)) > 0) {
+ if ((index += len) == _capcacity) {
+ _buf = Arrays.copyOf(_buf, _capcacity <<= 1);
+ }
+ }
+ _buffer.clear();
+ return _buffer.append(_buf, 0, index);
+ }
+
+ public boolean isCompressible() {
+ return _deflaterReset;
+ }
+
+ public void end() {
+ _deflater.end();
+ _inflater.end();
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private static class NewZlib extends Zlib {
+ public NewZlib(boolean deflaterReset, boolean inflaterReset) {
+ super(deflaterReset, inflaterReset);
+ }
+
+ @Override
+ public byte[] compress(byte[] b, int offset, int length) {
+ int len;
+ int index = 0;
+
+ if (_deflaterReset) {
+ _deflater.reset();
+ }
+ _deflater.setInput(b, offset, length);
+ while ((len = _deflater.deflate(_buf, index, _capcacity - index, Deflater.SYNC_FLUSH)) > 0) {
+ if ((index += len) == _capcacity) {
+ _buf = Arrays.copyOf(_buf, _capcacity <<= 1);
+ }
+ }
+ return Arrays.copyOf(_buf, index - TAIL_BYTES.length);
+ }
+
+ @Override
+ public boolean isCompressible() {
+ return true;
+ }
+ }
+
+ private FrameConnection _connection;
+ private FrameHandler _inbound;
+ private WebSocketGenerator _outbound;
+ private Zlib _zlib;
+ private String _parameters;
+ private boolean _compressed;
+
+ public PerMessageDeflateExtension() {
+ if (Build.VERSION.SDK_INT < 19) {
+ _parameters = TextUtils.join("; ", new String[]{ EXTENSION, CLIENT_NO_CONTEXT_TAKEOVER });
+ } else {
+ _parameters = EXTENSION;
+ }
+ _compressed = false;
+ }
+
+ @Override
+ public void onFrame(byte flags, byte opcode, Buffer buffer) {
+ switch (opcode) {
+ case WebSocketConnectionRFC6455.OP_TEXT:
+ case WebSocketConnectionRFC6455.OP_BINARY:
+ _compressed = ((flags & 0x07) == 0x04);
+ case WebSocketConnectionRFC6455.OP_CONTINUATION:
+ if (_compressed) {
+ try {
+ if ((flags &= 0x08) > 0) {
+ buffer.put(TAIL_BYTES);
+ }
+ _inbound.onFrame(flags, opcode, _zlib.decompress(buffer.array(), buffer.getIndex(), buffer.length()));
+ } catch (DataFormatException e) {
+ __log.warn(e);
+ _connection.close(WebSocketConnectionRFC6455.CLOSE_BAD_DATA, "Bad data");
+ }
+ return;
+ }
+ }
+ _inbound.onFrame(flags, opcode, buffer);
+ }
+
+ @Override
+ public void close(int code, String message) {
+ _zlib.end();
+ }
+
+ @Override
+ public int flush() throws IOException {
+ return 0;
+ }
+
+ @Override
+ public boolean isBufferEmpty() {
+ return _outbound.isBufferEmpty();
+ }
+
+ @Override
+ public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException {
+ if (opcode == WebSocketConnectionRFC6455.OP_TEXT || opcode == WebSocketConnectionRFC6455.OP_BINARY) {
+ if (_zlib.isCompressible()) {
+ byte[] compressed = _zlib.compress(content, offset, length);
+ _outbound.addFrame((byte) (flags | 0x04), opcode, compressed, 0, compressed.length);
+ return;
+ }
+ }
+ _outbound.addFrame(flags, opcode, content, offset, length);
+ }
+
+ @Override
+ public String getName() {
+ return EXTENSION;
+ }
+
+ @Override
+ public String getParameterizedName() {
+ return _parameters;
+ }
+
+ @Override
+ public boolean init(Map<String, String> parameters) {
+ boolean extension = false;
+ boolean client_no_context_takeover = false;
+ boolean server_no_context_takeover = false;
+ int server_max_window_bits = 0;
+
+ _parameters = "";
+ for (String key : parameters.keySet()) {
+ String value = parameters.get(key);
+
+ if (EXTENSION.equals(key) && TextUtils.isEmpty(value) && !extension) {
+ extension = true;
+ } else if (CLIENT_NO_CONTEXT_TAKEOVER.equals(key) && TextUtils.isEmpty(value) && !client_no_context_takeover) {
+ client_no_context_takeover = true;
+ } else if (SERVER_NO_CONTEXT_TAKEOVER.equals(key) && TextUtils.isEmpty(value) && !server_no_context_takeover) {
+ server_no_context_takeover = true;
+ } else if (SERVER_MAX_WINDOW_BITS.equals(key) && server_max_window_bits == 0) {
+ if (TextUtils.isEmpty(value)) {
+ __log.warn("Unexpected parameter: {}", key);
+ return false;
+ }
+ if (!value.matches("[1-9]\\d?")) {
+ __log.warn("Unexpected parameter: {}={}", key, value);
+ return false;
+ }
+ int v = Integer.parseInt(value);
+ if (v < 8 || v > 15) {
+ __log.warn("Unexpected parameter: {}={}", key, value);
+ return false;
+ }
+ server_max_window_bits = v;
+ } else if (!TextUtils.isEmpty(value)) {
+ __log.warn("Unexpected parameter: {}={}", key, value);
+ return false;
+ } else {
+ __log.warn("Unexpected parameter: {}", key);
+ return false;
+ }
+ }
+ if (extension) {
+ List<String> p = new ArrayList<String>();
+
+ p.add(EXTENSION);
+ if (client_no_context_takeover) {
+ p.add(CLIENT_NO_CONTEXT_TAKEOVER);
+ }
+ if (server_no_context_takeover) {
+ p.add(SERVER_NO_CONTEXT_TAKEOVER);
+ }
+ if (server_max_window_bits > 0) {
+ p.add(String.format("%s=%d", SERVER_MAX_WINDOW_BITS, server_max_window_bits));
+ }
+ _parameters = TextUtils.join("; ", p);
+
+ if (Build.VERSION.SDK_INT < 19) {
+ _zlib = new Zlib(client_no_context_takeover, server_no_context_takeover);
+ } else {
+ _zlib = new NewZlib(client_no_context_takeover, server_no_context_takeover);
+ }
+ }
+ return extension;
+ }
+
+ @Override
+ public void bind(FrameConnection connection, FrameHandler inbound, WebSocketGenerator outbound) {
+ _connection = connection;
+ _inbound = inbound;
+ _outbound = outbound;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/RandomMaskGen.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/RandomMaskGen.java
new file mode 100644
index 00000000..a180c60f
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/RandomMaskGen.java
@@ -0,0 +1,45 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.util.Random;
+
+
+public class RandomMaskGen implements MaskGen
+{
+ private final Random _random;
+
+ public RandomMaskGen()
+ {
+ this(new Random());
+ }
+
+ public RandomMaskGen(Random random)
+ {
+ _random=random;
+ }
+
+ public void genMask(byte[] mask)
+ {
+ // The assumption is that this code is always called
+ // with an external lock held to prevent concurrent access
+ // Otherwise we need to synchronize on the _random.
+ _random.nextBytes(mask);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocket.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocket.java
new file mode 100644
index 00000000..4cf39df7
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocket.java
@@ -0,0 +1,273 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+/**
+ * WebSocket Interface.
+ * <p>
+ * This interface provides the signature for a server-side end point of a websocket connection.
+ * The Interface has several nested interfaces, for each type of message that may be received.
+ */
+public interface WebSocket
+{
+ /**
+ * Called when a new websocket connection is accepted.
+ * @param connection The Connection object to use to send messages.
+ */
+ void onOpen(Connection connection);
+
+ /**
+ * Called when an established websocket connection closes
+ * @param closeCode
+ * @param message
+ */
+ void onClose(int closeCode, String message);
+
+ /**
+ * A nested WebSocket interface for receiving text messages
+ */
+ interface OnTextMessage extends WebSocket
+ {
+ /**
+ * Called with a complete text message when all fragments have been received.
+ * The maximum size of text message that may be aggregated from multiple frames is set with {@link Connection#setMaxTextMessageSize(int)}.
+ * @param data The message
+ */
+ void onMessage(String data);
+ }
+
+ /**
+ * A nested WebSocket interface for receiving binary messages
+ */
+ interface OnBinaryMessage extends WebSocket
+ {
+ /**
+ * Called with a complete binary message when all fragments have been received.
+ * The maximum size of binary message that may be aggregated from multiple frames is set with {@link Connection#setMaxBinaryMessageSize(int)}.
+ * @param data
+ * @param offset
+ * @param length
+ */
+ void onMessage(byte[] data, int offset, int length);
+ }
+
+ /**
+ * A nested WebSocket interface for receiving control messages
+ */
+ interface OnControl extends WebSocket
+ {
+ /**
+ * Called when a control message has been received.
+ * @param controlCode
+ * @param data
+ * @param offset
+ * @param length
+ * @return true if this call has completely handled the control message and no further processing is needed.
+ */
+ boolean onControl(byte controlCode,byte[] data, int offset, int length);
+ }
+
+ /**
+ * A nested WebSocket interface for receiving any websocket frame
+ */
+ interface OnFrame extends WebSocket
+ {
+ /**
+ * Called when any websocket frame is received.
+ * @param flags
+ * @param opcode
+ * @param data
+ * @param offset
+ * @param length
+ * @return true if this call has completely handled the frame and no further processing is needed (including aggregation and/or message delivery)
+ */
+ boolean onFrame(byte flags,byte opcode,byte[] data, int offset, int length);
+
+ void onHandshake(FrameConnection connection);
+ }
+
+ /**
+ * A Connection interface is passed to a WebSocket instance via the {@link WebSocket#onOpen(Connection)} to
+ * give the application access to the specifics of the current connection. This includes methods
+ * for sending frames and messages as well as methods for interpreting the flags and opcodes of the connection.
+ *
+ * modified by KNOWLEDGECODE
+ */
+ public interface Connection
+ {
+ String getProtocol();
+ String getExtensions();
+ void sendMessage(String data) throws IOException;
+ void sendMessage(byte[] data, int offset, int length) throws IOException;
+
+ /**
+ * Close the connection with normal close code.
+ */
+ void close();
+
+ /** Close the connection with specific closeCode and message.
+ * @param closeCode The close code to send, or -1 for no close code
+ * @param message The message to send or null for no message
+ */
+ void close(int closeCode,String message);
+
+ boolean isOpen();
+
+ /**
+ * @param ms The time in ms that the connection can be idle before closing
+ */
+ void setMaxIdleTime(int ms);
+
+ /**
+ * @param size size<0 No aggregation of frames to messages, >=0 max size of text frame aggregation buffer in characters
+ */
+ void setMaxTextMessageSize(int size);
+
+ /**
+ * @param size size<0 no aggregation of binary frames, >=0 size of binary frame aggregation buffer
+ */
+ void setMaxBinaryMessageSize(int size);
+
+ /**
+ * @return The time in ms that the connection can be idle before closing
+ */
+ int getMaxIdleTime();
+
+ /**
+ * Size in characters of the maximum text message to be received
+ * @return size <0 No aggregation of frames to messages, >=0 max size of text frame aggregation buffer in characters
+ */
+ int getMaxTextMessageSize();
+
+ /**
+ * Size in bytes of the maximum binary message to be received
+ * @return size <0 no aggregation of binary frames, >=0 size of binary frame aggregation buffer
+ */
+ int getMaxBinaryMessageSize();
+ }
+
+ /**
+ * Frame Level Connection
+ * <p>The Connection interface at the level of sending/receiving frames rather than messages.
+ * Also contains methods to decode/generate flags and opcodes without using constants, so that
+ * code can be written to work with multiple drafts of the protocol.
+ *
+ */
+ public interface FrameConnection extends Connection
+ {
+ /**
+ * @return The opcode of a binary message
+ */
+ byte binaryOpcode();
+
+ /**
+ * @return The opcode of a text message
+ */
+ byte textOpcode();
+
+ /**
+ * @return The opcode of a continuation frame
+ */
+ byte continuationOpcode();
+
+ /**
+ * @return Mask for the FIN bit.
+ */
+ byte finMask();
+
+ /** Set if frames larger than the frame buffer are handled with local fragmentations
+ * @param allowFragmentation
+ */
+ void setAllowFrameFragmentation(boolean allowFragmentation);
+
+ /**
+ * @param flags The flags bytes of a frame
+ * @return True of the flags indicate a final frame.
+ */
+ boolean isMessageComplete(byte flags);
+
+ /**
+ * @param opcode
+ * @return True if the opcode is for a control frame
+ */
+ boolean isControl(byte opcode);
+
+ /**
+ * @param opcode
+ * @return True if the opcode is for a text frame
+ */
+ boolean isText(byte opcode);
+
+ /**
+ * @param opcode
+ * @return True if the opcode is for a binary frame
+ */
+ boolean isBinary(byte opcode);
+
+ /**
+ * @param opcode
+ * @return True if the opcode is for a continuation frame
+ */
+ boolean isContinuation(byte opcode);
+
+ /**
+ * @param opcode
+ * @return True if the opcode is a close control
+ */
+ boolean isClose(byte opcode);
+
+ /**
+ * @param opcode
+ * @return True if the opcode is a ping control
+ */
+ boolean isPing(byte opcode);
+
+ /**
+ * @param opcode
+ * @return True if the opcode is a pong control
+ */
+ boolean isPong(byte opcode);
+
+ /**
+ * @return True if frames larger than the frame buffer are fragmented.
+ */
+ boolean isAllowFrameFragmentation();
+
+ /** Send a control frame
+ * @param control
+ * @param data
+ * @param offset
+ * @param length
+ * @throws IOException
+ */
+ void sendControl(byte control,byte[] data, int offset, int length) throws IOException;
+
+ /** Send an arbitrary frame
+ * @param flags
+ * @param opcode
+ * @param data
+ * @param offset
+ * @param length
+ * @throws IOException
+ */
+ void sendFrame(byte flags,byte opcode,byte[] data, int offset, int length) throws IOException;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketBuffer.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketBuffer.java
new file mode 100644
index 00000000..ad89312b
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketBuffer.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.jetty.websocket;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import org.eclipse.jetty.io.Buffer;
+
+/**
+ * WebSocketBuffer
+ *
+ * Copyright (c) 2015 KNOWLEDGECODE
+ */
+class WebSocketBuffer implements Buffer {
+
+ private byte[] _buffer;
+ private int _index;
+ private int _capacity;
+
+ public WebSocketBuffer(final int capacity) {
+ _buffer = new byte[capacity];
+ _index = 0;
+ _capacity = capacity;
+ }
+
+ public WebSocketBuffer(final byte[] buffer, final int offset, final int length) {
+ _buffer = Arrays.copyOfRange(buffer, offset, length);
+ _index = length;
+ _capacity = length;
+ }
+
+ public WebSocketBuffer append(final byte[] array, final int offset, final int length) {
+ if ((_index += length) > _capacity) {
+ _buffer = Arrays.copyOf(_buffer, (_capacity = Math.max(_capacity << 1, _index)));
+ }
+ System.arraycopy(array, offset, _buffer, _index - length, length);
+ return this;
+ }
+
+ @Override
+ public byte[] array() {
+ return _buffer;
+ }
+
+ @Override
+ public byte[] asArray() {
+ return Arrays.copyOf(_buffer, _index);
+ }
+
+ @Override
+ public Buffer buffer() {
+ return null;
+ }
+
+ @Override
+ public Buffer asMutableBuffer() {
+ return null;
+ }
+
+ @Override
+ public int capacity() {
+ return _capacity;
+ }
+
+ @Override
+ public int space() {
+ return _capacity - _index;
+ }
+
+ @Override
+ public void clear() {
+ _index = 0;
+ }
+
+ @Override
+ public void compact() {
+ }
+
+ @Override
+ public byte get() {
+ return 0;
+ }
+
+ @Override
+ public int get(byte[] b, int offset, int length) {
+ return 0;
+ }
+
+ @Override
+ public Buffer get(int length) {
+ return null;
+ }
+
+ @Override
+ public int getIndex() {
+ return 0;
+ }
+
+ @Override
+ public boolean hasContent() {
+ return false;
+ }
+
+ @Override
+ public boolean equalsIgnoreCase(Buffer buffer) {
+ return false;
+ }
+
+ @Override
+ public boolean isImmutable() {
+ return false;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public boolean isVolatile() {
+ return false;
+ }
+
+ @Override
+ public int length() {
+ return _index;
+ }
+
+ @Override
+ public void mark() {
+ }
+
+ @Override
+ public int markIndex() {
+ return 0;
+ }
+
+ @Override
+ public byte peek() {
+ return 0;
+ }
+
+ @Override
+ public byte peek(int index) {
+ return 0;
+ }
+
+ @Override
+ public int peek(int index, byte[] b, int offset, int length) {
+ return 0;
+ }
+
+ @Override
+ public int poke(int index, Buffer src) {
+ return 0;
+ }
+
+ @Override
+ public void poke(int index, byte b) {
+ }
+
+ @Override
+ public int poke(int index, byte[] b, int offset, int length) {
+ return 0;
+ }
+
+ @Override
+ public int put(Buffer src) {
+ return 0;
+ }
+
+ @Override
+ public void put(byte b) {
+ }
+
+ @Override
+ public int put(byte[] b, int offset, int length) {
+ return 0;
+ }
+
+ @Override
+ public int put(byte[] b) {
+ return 0;
+ }
+
+ @Override
+ public int putIndex() {
+ return 0;
+ }
+
+ @Override
+ public void setGetIndex(int newStart) {
+ }
+
+ @Override
+ public void setMarkIndex(int newMark) {
+ }
+
+ @Override
+ public void setPutIndex(int newLimit) {
+ }
+
+ @Override
+ public int skip(int n) {
+ return n;
+ }
+
+ @Override
+ public Buffer sliceFromMark() {
+ return null;
+ }
+
+ @Override
+ public Buffer sliceFromMark(int length) {
+ return null;
+ }
+
+ @Override
+ public String toDetailString() {
+ return null;
+ }
+
+ @Override
+ public String toString(Charset charset) {
+ return new String(_buffer, 0, _index, charset);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketBuffers.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketBuffers.java
new file mode 100644
index 00000000..97dcb135
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketBuffers.java
@@ -0,0 +1,57 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.Buffers.Type;
+import org.eclipse.jetty.io.ThreadLocalBuffers;
+
+/* ------------------------------------------------------------ */
+/** The WebSocket Buffer Pool.
+ *
+ * The normal buffers are byte array buffers so that user processes
+ * can access directly. However the generator uses direct buffers
+ * for the final output stage as they are filled in bulk and are more
+ * efficient to flush.
+ */
+public class WebSocketBuffers
+{
+ final private Buffers _buffers;
+
+ public WebSocketBuffers(final int bufferSize)
+ {
+ _buffers = new ThreadLocalBuffers(Type.DIRECT, bufferSize, Type.INDIRECT, bufferSize, Type.INDIRECT);
+ }
+
+ public Buffer getBuffer()
+ {
+ return _buffers.getBuffer();
+ }
+
+ public Buffer getDirectBuffer()
+ {
+ return _buffers.getHeader();
+ }
+
+ public void returnBuffer(Buffer buffer)
+ {
+ _buffers.returnBuffer(buffer);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketClient.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketClient.java
new file mode 100644
index 00000000..dc6fcf21
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketClient.java
@@ -0,0 +1,611 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ProtocolException;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * <p>{@link WebSocketClient} allows to create multiple connections to multiple destinations
+ * that can speak the websocket protocol.</p>
+ * <p>When creating websocket connections, {@link WebSocketClient} accepts a {@link WebSocket}
+ * object (to receive events from the server), and returns a {@link WebSocket.Connection} to
+ * send data to the server.</p>
+ * <p>Example usage is as follows:</p>
+ * <pre>
+ * WebSocketClientFactory factory = new WebSocketClientFactory();
+ * factory.start();
+ *
+ * WebSocketClient client = factory.newWebSocketClient();
+ * // Configure the client
+ *
+ * WebSocket.Connection connection = client.open(new URI("ws://127.0.0.1:8080/"), new WebSocket.OnTextMessage()
+ * {
+ * public void onOpen(Connection connection)
+ * {
+ * // open notification
+ * }
+ *
+ * public void onClose(int closeCode, String message)
+ * {
+ * // close notification
+ * }
+ *
+ * public void onMessage(String data)
+ * {
+ * // handle incoming message
+ * }
+ * }).get(5, TimeUnit.SECONDS);
+ *
+ * connection.sendMessage("Hello World");
+ * </pre>
+ *
+ * modified by KNOWLEDGECODE
+ */
+public class WebSocketClient
+{
+ private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClient.class.getName());
+
+ private final WebSocketClientFactory _factory;
+ private final Map<String,String> _cookies=new ConcurrentHashMap<String, String>();
+ private final List<Extension> _extensions=new CopyOnWriteArrayList<Extension>();
+ private String _origin;
+ private String _protocol;
+ private String _agent;
+ private int _maxIdleTime=-1;
+ private int _maxTextMessageSize=-1;
+ private int _maxBinaryMessageSize=-1;
+ private MaskGen _maskGen;
+ private SocketAddress _bindAddress;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Creates a WebSocketClient with shared WebSocketClientFactory.</p>
+ *
+ * @param factory the shared {@link WebSocketClientFactory}
+ */
+ public WebSocketClient(WebSocketClientFactory factory)
+ {
+ _factory=factory;
+ _maskGen=_factory.getMaskGen();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The WebSocketClientFactory this client was created with.
+ */
+ public WebSocketClientFactory getFactory()
+ {
+ return _factory;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The maxIdleTime in ms for connections opened by this client,
+ * or -1 if the default from {@link WebSocketClientFactory#getSelectorManager()} is used.
+ * @see #setMaxIdleTime(int)
+ */
+ public int getMaxIdleTime()
+ {
+ return _maxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param maxIdleTime The max idle time in ms for connections opened by this client
+ * @see #getMaxIdleTime()
+ */
+ public void setMaxIdleTime(int maxIdleTime)
+ {
+ _maxIdleTime=maxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The subprotocol string for connections opened by this client.
+ * @see #setProtocol(String)
+ */
+ public String getProtocol()
+ {
+ return _protocol;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param protocol The subprotocol string for connections opened by this client.
+ * @see #getProtocol()
+ */
+ public void setProtocol(String protocol)
+ {
+ _protocol = protocol;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The origin URI of the client
+ * @see #setOrigin(String)
+ */
+ public String getOrigin()
+ {
+ return _origin;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param origin The origin URI of the client (eg "http://example.com")
+ * @see #getOrigin()
+ */
+ public void setOrigin(String origin)
+ {
+ _origin = origin;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Returns the map of the cookies that are sent during the initial HTTP handshake
+ * that upgrades to the websocket protocol.</p>
+ * @return The read-write cookie map
+ */
+ public Map<String,String> getCookies()
+ {
+ return _cookies;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The list of websocket protocol extensions
+ */
+ public List<Extension> getExtensions()
+ {
+ return _extensions;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the mask generator to use, or null if not mask generator should be used
+ * @see #setMaskGen(MaskGen)
+ */
+ public MaskGen getMaskGen()
+ {
+ return _maskGen;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param maskGen the mask generator to use, or null if not mask generator should be used
+ * @see #getMaskGen()
+ */
+ public void setMaskGen(MaskGen maskGen)
+ {
+ _maskGen = maskGen;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The initial maximum text message size (in characters) for a connection
+ */
+ public int getMaxTextMessageSize()
+ {
+ return _maxTextMessageSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the initial maximum text message size for a connection. This can be changed by
+ * the application calling {@link WebSocket.Connection#setMaxTextMessageSize(int)}.
+ * @param maxTextMessageSize The default maximum text message size (in characters) for a connection
+ */
+ public void setMaxTextMessageSize(int maxTextMessageSize)
+ {
+ _maxTextMessageSize = maxTextMessageSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The initial maximum binary message size (in bytes) for a connection
+ */
+ public int getMaxBinaryMessageSize()
+ {
+ return _maxBinaryMessageSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the initial maximum binary message size for a connection. This can be changed by
+ * the application calling {@link WebSocket.Connection#setMaxBinaryMessageSize(int)}.
+ * @param maxBinaryMessageSize The default maximum binary message size (in bytes) for a connection
+ */
+ public void setMaxBinaryMessageSize(int maxBinaryMessageSize)
+ {
+ _maxBinaryMessageSize = maxBinaryMessageSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return user-agent
+ */
+ public String getAgent()
+ {
+ return _agent;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param user-agent
+ */
+ public void setAgent(String _agent)
+ {
+ this._agent = _agent;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Opens a websocket connection to the URI and blocks until the connection is accepted or there is an error.</p>
+ *
+ * @param uri The URI to connect to.
+ * @param websocket The {@link WebSocket} instance to handle incoming events.
+ * @param maxConnectTime The interval to wait for a successful connection
+ * @param units the units of the maxConnectTime
+ * @return A {@link WebSocket.Connection}
+ * @throws IOException if the connection fails
+ * @throws InterruptedException if the thread is interrupted
+ * @throws TimeoutException if the timeout elapses before the connection is completed
+ * @see #open(URI, WebSocket)
+ */
+ public WebSocket.Connection open(URI uri, WebSocket websocket,long maxConnectTime,TimeUnit units) throws IOException, InterruptedException, TimeoutException
+ {
+ try
+ {
+ return open(uri,websocket).get(maxConnectTime,units);
+ }
+ catch (ExecutionException e)
+ {
+ Throwable cause = e.getCause();
+ if (cause instanceof IOException)
+ throw (IOException)cause;
+ if (cause instanceof Error)
+ throw (Error)cause;
+ if (cause instanceof RuntimeException)
+ throw (RuntimeException)cause;
+ throw new RuntimeException(cause);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Asynchronously opens a websocket connection and returns a {@link Future} to obtain the connection.</p>
+ * <p>The caller must call {@link Future#get(long, TimeUnit)} if they wish to impose a connect timeout on the open.</p>
+ *
+ * @param uri The URI to connect to.
+ * @param websocket The {@link WebSocket} instance to handle incoming events.
+ * @return A {@link Future} to the {@link WebSocket.Connection}
+ * @throws IOException if the connection fails
+ * @see #open(URI, WebSocket, long, TimeUnit)
+ */
+ public Future<WebSocket.Connection> open(URI uri, WebSocket websocket) throws IOException
+ {
+ if (!_factory.isStarted())
+ throw new IllegalStateException("Factory !started");
+
+ InetSocketAddress address = toSocketAddress(uri);
+
+ SocketChannel channel = null;
+ try
+ {
+ channel = SocketChannel.open();
+ if (_bindAddress != null)
+ channel.socket().bind(_bindAddress);
+ channel.socket().setTcpNoDelay(true);
+
+ WebSocketFuture holder = new WebSocketFuture(websocket,uri,this,channel);
+
+ channel.configureBlocking(false);
+ channel.connect(address);
+ _factory.getSelectorManager().register(channel,holder);
+
+ return holder;
+ }
+ catch (RuntimeException e)
+ {
+ // close the channel (prevent connection leak)
+ IO.close(channel);
+
+ // rethrow
+ throw e;
+ }
+ catch(IOException e)
+ {
+ // close the channel (prevent connection leak)
+ IO.close(channel);
+
+ // rethrow
+ throw e;
+ }
+ }
+
+ public static InetSocketAddress toSocketAddress(URI uri)
+ {
+ String scheme = uri.getScheme();
+ if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme)))
+ throw new IllegalArgumentException("Bad WebSocket scheme: " + scheme);
+ int port = uri.getPort();
+ if (port == 0)
+ throw new IllegalArgumentException("Bad WebSocket port: " + port);
+ if (port < 0)
+ port = "ws".equals(scheme) ? 80 : 443;
+
+ return new InetSocketAddress(uri.getHost(), port);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** The Future Websocket Connection.
+ */
+ static class WebSocketFuture implements Future<WebSocket.Connection>
+ {
+ final WebSocket _websocket;
+ final URI _uri;
+ final WebSocketClient _client;
+ final CountDownLatch _done = new CountDownLatch(1);
+ ByteChannel _channel;
+ WebSocketConnection _connection;
+ Throwable _exception;
+
+ private WebSocketFuture(WebSocket websocket, URI uri, WebSocketClient client, ByteChannel channel)
+ {
+ _websocket=websocket;
+ _uri=uri;
+ _client=client;
+ _channel=channel;
+ }
+
+ public void onConnection(WebSocketConnection connection)
+ {
+ try
+ {
+ _client.getFactory().addConnection(connection);
+
+ connection.getConnection().setMaxTextMessageSize(_client.getMaxTextMessageSize());
+ connection.getConnection().setMaxBinaryMessageSize(_client.getMaxBinaryMessageSize());
+
+ WebSocketConnection con;
+ synchronized (this)
+ {
+ if (_channel!=null)
+ _connection=connection;
+ con=_connection;
+ }
+
+ if (con!=null)
+ {
+ if (_websocket instanceof WebSocket.OnFrame)
+ ((WebSocket.OnFrame)_websocket).onHandshake((WebSocket.FrameConnection)con.getConnection());
+
+ _websocket.onOpen(con.getConnection());
+ }
+ }
+ finally
+ {
+ _done.countDown();
+ }
+ }
+
+ public void handshakeFailed(Throwable ex)
+ {
+ try
+ {
+ ByteChannel channel=null;
+ synchronized (this)
+ {
+ if (_channel!=null)
+ {
+ channel=_channel;
+ _channel=null;
+ _exception=ex;
+ }
+ }
+
+ if (channel!=null)
+ {
+ if (ex instanceof ProtocolException)
+ closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_PROTOCOL,ex.getMessage());
+ else
+ closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,ex.getMessage());
+ }
+ }
+ finally
+ {
+ _done.countDown();
+ }
+ }
+
+ public Map<String,String> getCookies()
+ {
+ return _client.getCookies();
+ }
+
+ public String getProtocol()
+ {
+ return _client.getProtocol();
+ }
+
+ public WebSocket getWebSocket()
+ {
+ return _websocket;
+ }
+
+ public URI getURI()
+ {
+ return _uri;
+ }
+
+ public int getMaxIdleTime()
+ {
+ return _client.getMaxIdleTime();
+ }
+
+ public String getOrigin()
+ {
+ return _client.getOrigin();
+ }
+
+ public MaskGen getMaskGen()
+ {
+ return _client.getMaskGen();
+ }
+
+ public String getAgent()
+ {
+ return _client.getAgent();
+ }
+
+ public List<Extension> getExtensions() {
+ return _client.getExtensions();
+ }
+
+ @Override
+ public String toString()
+ {
+ return "[" + _uri + ","+_websocket+"]@"+hashCode();
+ }
+
+ public boolean cancel(boolean mayInterruptIfRunning)
+ {
+ try
+ {
+ ByteChannel channel=null;
+ synchronized (this)
+ {
+ if (_connection==null && _exception==null && _channel!=null)
+ {
+ channel=_channel;
+ _channel=null;
+ }
+ }
+
+ if (channel!=null)
+ {
+ closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"cancelled");
+ return true;
+ }
+ return false;
+ }
+ finally
+ {
+ _done.countDown();
+ }
+ }
+
+ public boolean isCancelled()
+ {
+ synchronized (this)
+ {
+ return _channel==null && _connection==null;
+ }
+ }
+
+ public boolean isDone()
+ {
+ synchronized (this)
+ {
+ return _connection!=null && _exception==null;
+ }
+ }
+
+ public org.eclipse.jetty.websocket.WebSocket.Connection get() throws InterruptedException, ExecutionException
+ {
+ try
+ {
+ return get(Long.MAX_VALUE,TimeUnit.SECONDS);
+ }
+ catch(TimeoutException e)
+ {
+ throw new IllegalStateException("The universe has ended",e);
+ }
+ }
+
+ public org.eclipse.jetty.websocket.WebSocket.Connection get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
+ TimeoutException
+ {
+ _done.await(timeout,unit);
+ ByteChannel channel=null;
+ org.eclipse.jetty.websocket.WebSocket.Connection connection=null;
+ Throwable exception;
+ synchronized (this)
+ {
+ exception=_exception;
+ if (_connection==null)
+ {
+ exception=_exception;
+ channel=_channel;
+ _channel=null;
+ }
+ else
+ connection=_connection.getConnection();
+ }
+
+ if (channel!=null)
+ closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"timeout");
+ if (exception!=null)
+ throw new ExecutionException(exception);
+ if (connection!=null)
+ return connection;
+ throw new TimeoutException();
+ }
+
+ private void closeChannel(ByteChannel channel,int code, String message)
+ {
+ try
+ {
+ _websocket.onClose(code,message);
+ }
+ catch(Exception e)
+ {
+ __log.warn(e);
+ }
+
+ try
+ {
+ channel.close();
+ }
+ catch(IOException e)
+ {
+ __log.debug(e);
+ }
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketClientFactory.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketClientFactory.java
new file mode 100644
index 00000000..f4d4549d
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketClientFactory.java
@@ -0,0 +1,634 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SimpleBuffers;
+import org.eclipse.jetty.io.nio.AsyncConnection;
+import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
+import org.eclipse.jetty.io.nio.SelectorManager;
+import org.eclipse.jetty.io.nio.SslConnection;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+import android.text.TextUtils;
+import android.util.Base64;
+
+/* ------------------------------------------------------------ */
+/**
+ * <p>WebSocketClientFactory contains the common components needed by multiple {@link WebSocketClient} instances
+ * (for example, a {@link ThreadPool}, a {@link SelectorManager NIO selector}, etc).</p>
+ * <p>WebSocketClients with different configurations should share the same factory to avoid to waste resources.</p>
+ * <p>If a ThreadPool or MaskGen is passed in the constructor, then it is not added with {@link AggregateLifeCycle#addBean(Object)},
+ * so it's lifecycle must be controlled externally.
+ *
+ * @see WebSocketClient
+ *
+ * modified by KNOWLEDGECODE
+ */
+public class WebSocketClientFactory extends AggregateLifeCycle
+{
+ private static final Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClientFactory.class.getName());
+ private static final ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept");
+ private static final ByteArrayBuffer __PROTOCOL = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Protocol");
+ private static final ByteArrayBuffer __EXTENSIONS = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Extensions");
+ private final Queue<WebSocketConnection> connections = new ConcurrentLinkedQueue<WebSocketConnection>();
+ private final SslContextFactory _sslContextFactory = new SslContextFactory();
+ private final ThreadPool _threadPool;
+ private final WebSocketClientSelector _selector;
+ private MaskGen _maskGen;
+ private WebSocketBuffers _buffers;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Creates a WebSocketClientFactory with the default configuration.</p>
+ */
+ public WebSocketClientFactory()
+ {
+ this(null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Creates a WebSocketClientFactory with the given ThreadPool and the default configuration.</p>
+ *
+ * @param threadPool the ThreadPool instance to use
+ */
+ public WebSocketClientFactory(ThreadPool threadPool)
+ {
+ this(threadPool, new RandomMaskGen());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Creates a WebSocketClientFactory with the given ThreadPool and the given MaskGen.</p>
+ *
+ * @param threadPool the ThreadPool instance to use
+ * @param maskGen the MaskGen instance to use
+ */
+ public WebSocketClientFactory(ThreadPool threadPool, MaskGen maskGen)
+ {
+ this(threadPool, maskGen, 8192);
+ }
+
+ /* ------------------------------------------------------------ */
+
+ /**
+ * <p>Creates a WebSocketClientFactory with the specified configuration.</p>
+ *
+ * @param threadPool the ThreadPool instance to use
+ * @param maskGen the mask generator to use
+ * @param bufferSize the read buffer size
+ */
+ public WebSocketClientFactory(ThreadPool threadPool, MaskGen maskGen, int bufferSize)
+ {
+ if (threadPool == null)
+ threadPool = new QueuedThreadPool();
+ _threadPool = threadPool;
+ addBean(_threadPool);
+
+ _buffers = new WebSocketBuffers(bufferSize);
+ addBean(_buffers);
+
+ _maskGen = maskGen;
+ addBean(_maskGen);
+
+ _selector = new WebSocketClientSelector();
+ addBean(_selector);
+
+ addBean(_sslContextFactory);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the SslContextFactory used to configure SSL parameters
+ */
+ public SslContextFactory getSslContextFactory()
+ {
+ return _sslContextFactory;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the selectorManager. Used to configure the manager.
+ *
+ * @return The {@link SelectorManager} instance.
+ */
+ public SelectorManager getSelectorManager()
+ {
+ return _selector;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the ThreadPool.
+ * Used to set/query the thread pool configuration.
+ *
+ * @return The {@link ThreadPool}
+ */
+ public ThreadPool getThreadPool()
+ {
+ return _threadPool;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the shared mask generator, or null if no shared mask generator is used
+ * @see WebSocketClient#getMaskGen()
+ */
+ public MaskGen getMaskGen()
+ {
+ return _maskGen;
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ closeConnections();
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Creates and returns a new instance of a {@link WebSocketClient}, configured with this
+ * WebSocketClientFactory instance.</p>
+ *
+ * @return a new {@link WebSocketClient} instance
+ */
+ public WebSocketClient newWebSocketClient()
+ {
+ return new WebSocketClient(this);
+ }
+
+ protected SSLEngine newSslEngine(SocketChannel channel) throws IOException
+ {
+ SSLEngine sslEngine;
+ if (channel != null)
+ {
+ String peerHost = channel.socket().getInetAddress().getHostAddress();
+ int peerPort = channel.socket().getPort();
+ sslEngine = _sslContextFactory.newSslEngine(peerHost, peerPort);
+ }
+ else
+ {
+ sslEngine = _sslContextFactory.newSslEngine();
+ }
+ sslEngine.setUseClientMode(true);
+ sslEngine.beginHandshake();
+
+ return sslEngine;
+ }
+
+ protected boolean addConnection(WebSocketConnection connection)
+ {
+ return isRunning() && connections.add(connection);
+ }
+
+ protected boolean removeConnection(WebSocketConnection connection)
+ {
+ return connections.remove(connection);
+ }
+
+ protected void closeConnections()
+ {
+ for (WebSocketConnection connection : connections)
+ connection.shutdown();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * WebSocket Client Selector Manager
+ */
+ class WebSocketClientSelector extends SelectorManager
+ {
+ @Override
+ public boolean dispatch(Runnable task)
+ {
+ return _threadPool.dispatch(task);
+ }
+
+ @Override
+ protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, final SelectionKey key) throws IOException
+ {
+ WebSocketClient.WebSocketFuture holder = (WebSocketClient.WebSocketFuture)key.attachment();
+ int maxIdleTime = holder.getMaxIdleTime();
+ if (maxIdleTime < 0)
+ maxIdleTime = (int)getMaxIdleTime();
+ SelectChannelEndPoint result = new SelectChannelEndPoint(channel, selectSet, key, maxIdleTime);
+ AsyncEndPoint endPoint = result;
+
+ // Detect if it is SSL, and wrap the connection if so
+ if ("wss".equals(holder.getURI().getScheme()))
+ {
+ SSLEngine sslEngine = newSslEngine(channel);
+ SslConnection sslConnection = new SslConnection(sslEngine, endPoint);
+ endPoint.setConnection(sslConnection);
+ endPoint = sslConnection.getSslEndPoint();
+ }
+
+ AsyncConnection connection = selectSet.getManager().newConnection(channel, endPoint, holder);
+ endPoint.setConnection(connection);
+
+ return result;
+ }
+
+ @Override
+ public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment)
+ {
+ WebSocketClient.WebSocketFuture holder = (WebSocketClient.WebSocketFuture)attachment;
+ return new HandshakeConnection(endpoint, holder);
+ }
+
+ @Override
+ protected void endPointOpened(SelectChannelEndPoint endpoint)
+ {
+ // TODO expose on outer class ??
+ }
+
+ @Override
+ protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
+ {
+ LOG.debug("upgrade {} -> {}", oldConnection, endpoint.getConnection());
+ }
+
+ @Override
+ protected void endPointClosed(SelectChannelEndPoint endpoint)
+ {
+ endpoint.getConnection().onClose();
+ }
+
+ @Override
+ protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
+ {
+ if (!(attachment instanceof WebSocketClient.WebSocketFuture))
+ super.connectionFailed(channel, ex, attachment);
+ else
+ {
+ __log.debug(ex);
+ WebSocketClient.WebSocketFuture future = (WebSocketClient.WebSocketFuture)attachment;
+
+ future.handshakeFailed(ex);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Handshake Connection.
+ * Handles the connection until the handshake succeeds or fails.
+ */
+ class HandshakeConnection extends AbstractConnection implements AsyncConnection
+ {
+ private static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;=";
+
+ private final AsyncEndPoint _endp;
+ private final WebSocketClient.WebSocketFuture _future;
+ private final String _key;
+ private final HttpParser _parser;
+ private String _accept;
+ private String _protocol;
+ private List<String> _extensions;
+ private String _error;
+ private ByteArrayBuffer _handshake;
+
+ public HandshakeConnection(AsyncEndPoint endpoint, WebSocketClient.WebSocketFuture future)
+ {
+ super(endpoint, System.currentTimeMillis());
+ _endp = endpoint;
+ _future = future;
+
+ byte[] bytes = new byte[16];
+ new Random().nextBytes(bytes);
+ _key = new String(Base64.encodeToString(bytes, Base64.NO_WRAP));
+ _extensions = new ArrayList<String>();
+
+ Buffers buffers = new SimpleBuffers(_buffers.getBuffer(), null);
+ _parser = new HttpParser(buffers, _endp, new HttpParser.EventHandler()
+ {
+ @Override
+ public void startResponse(Buffer version, int status, Buffer reason) throws IOException
+ {
+ if (status != 101)
+ {
+ _error = "Bad response status " + status + " " + reason;
+ _endp.close();
+ }
+ }
+
+ @Override
+ public void parsedHeader(Buffer name, Buffer value) throws IOException
+ {
+ if (__ACCEPT.equals(name))
+ _accept = value.toString();
+ else if (__PROTOCOL.equals(name))
+ _protocol = value.toString();
+ else if (__EXTENSIONS.equals(name))
+ for (String s : TextUtils.split(value.toString(), ","))
+ _extensions.add(s.trim());
+ }
+
+ @Override // TODO simone says shouldn't be needed
+ public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
+ {
+ if (_error == null)
+ _error = "Bad response: " + method + " " + url + " " + version;
+ _endp.close();
+ }
+
+ @Override // TODO simone says shouldn't be needed
+ public void content(Buffer ref) throws IOException
+ {
+ if (_error == null)
+ _error = "Bad response. " + ref.length() + "B of content?";
+ _endp.close();
+ }
+ });
+ }
+
+ private boolean initExtensions(List<Extension> extensions)
+ {
+ Map<String, Map<String, String>> map = new HashMap<String, Map<String, String>>();
+ Set<String> s = new HashSet<String>();
+
+ for (String ext : (String[]) _extensions.toArray(new String[]{}))
+ {
+ Map<String, String> m = new HashMap<String, String>();
+ String key = null;
+
+ for (String p : TextUtils.split(ext, ";"))
+ {
+ String[] pp = TextUtils.split(p.trim(), "=");
+ m.put(pp[0].trim(), pp.length > 1 ? pp[1].trim() : "");
+ if (key == null)
+ key = pp[0].trim();
+ if (!s.add(pp[0].trim()))
+ return false;
+ }
+ map.put(key, m);
+ }
+ for (Extension extension : extensions)
+ {
+ Map<String, String> m = map.remove(extension.getName());
+ if (m != null)
+ {
+ if (!extension.init(m))
+ return false;
+ }
+ else
+ {
+ extensions.remove(extension);
+ }
+ }
+ if (map.size() > 0)
+ return false;
+
+ return true;
+ }
+
+ private boolean handshake()
+ {
+ if (_handshake==null)
+ {
+ String path = _future.getURI().getPath();
+ if (path == null || path.length() == 0)
+ path = "/";
+
+ if (_future.getURI().getRawQuery() != null)
+ path += "?" + _future.getURI().getRawQuery();
+
+ String origin = _future.getOrigin();
+
+ StringBuilder request = new StringBuilder(512);
+ request.append("GET ").append(path).append(" HTTP/1.1\r\n")
+ .append("Host: ").append(_future.getURI().getHost()).append(":");
+
+ int port = _future.getURI().getPort();
+ if (port <= 0)
+ {
+ // fix it
+ String scheme = _future.getURI().getScheme();
+
+ if ("ws".equalsIgnoreCase(scheme))
+ {
+ port = 80;
+ }
+ else if ("wss".equalsIgnoreCase(scheme))
+ {
+ port = 443;
+ }
+ else
+ {
+ throw new RuntimeException("No valid port provided for scheme [" + scheme + "]");
+ }
+ }
+
+ request.append(port).append("\r\n");
+
+ request.append("Upgrade: websocket\r\n")
+ .append("Connection: Upgrade\r\n");
+
+ if (origin != null)
+ request.append("Origin: ").append(origin).append("\r\n");
+
+ if (_future.getProtocol() != null)
+ request.append("Sec-WebSocket-Protocol: ").append(_future.getProtocol()).append("\r\n");
+
+ request.append("pragma: no-cache\r\n");
+
+ request.append("cache-control: no-cache\r\n");
+
+ request.append("Sec-WebSocket-Key: ").append(_key).append("\r\n");
+
+ request.append("Sec-WebSocket-Version: ").append(WebSocketConnectionRFC6455.VERSION).append("\r\n");
+
+ if (_future.getAgent() != null)
+ request.append("user-agent: ").append(_future.getAgent()).append("\r\n");
+
+ if (_future.getExtensions().size() > 0)
+ {
+ List<String> extensions = new ArrayList<String>();
+ for (Extension ext :_future.getExtensions())
+ extensions.add(ext.getParameterizedName());
+ request.append("Sec-WebSocket-Extensions: ").append(TextUtils.join(", ", extensions)).append("\r\n");
+ }
+
+ Map<String, String> cookies = _future.getCookies();
+ if (cookies != null && cookies.size() > 0)
+ {
+ for (String cookie : cookies.keySet())
+ request.append("Cookie: ")
+ .append(QuotedStringTokenizer.quoteIfNeeded(cookie, __COOKIE_DELIM))
+ .append("=")
+ .append(QuotedStringTokenizer.quoteIfNeeded(cookies.get(cookie), __COOKIE_DELIM))
+ .append("\r\n");
+ }
+
+ request.append("\r\n");
+
+ _handshake=new ByteArrayBuffer(request.toString(), false);
+ }
+
+ try
+ {
+ int flushed = _endp.flush(_handshake);
+ if (flushed<0)
+ throw new IOException("incomplete handshake");
+ }
+ catch (IOException e)
+ {
+ _future.handshakeFailed(e);
+ }
+ return _handshake.length()==0;
+ }
+
+ public Connection handle() throws IOException
+ {
+ while (_endp.isOpen() && !_parser.isComplete())
+ {
+ if (_handshake==null || _handshake.length()>0)
+ if (!handshake())
+ return this;
+
+ if (!_parser.parseAvailable())
+ {
+ if (_endp.isInputShutdown())
+ _future.handshakeFailed(new IOException("Incomplete handshake response"));
+ return this;
+ }
+ }
+ if (_error == null)
+ {
+ if (_accept == null)
+ {
+ _error = "No Sec-WebSocket-Accept";
+ }
+ else if (!WebSocketConnectionRFC6455.hashKey(_key).equals(_accept))
+ {
+ _error = "Bad Sec-WebSocket-Accept";
+ }
+ else if (!initExtensions(_future.getExtensions()))
+ {
+ _error = "Bad Sec-WebSocket-Extension";
+ }
+ else
+ {
+ WebSocketConnection connection = newWebSocketConnection();
+
+ Buffer header = _parser.getHeaderBuffer();
+ if (header.hasContent())
+ connection.fillBuffersFrom(header);
+ _buffers.returnBuffer(header);
+
+ _future.onConnection(connection);
+
+ return connection;
+ }
+ }
+
+ _endp.close();
+ return this;
+ }
+
+ private WebSocketConnection newWebSocketConnection() throws IOException
+ {
+ __log.debug("newWebSocketConnection()");
+ return new WebSocketClientConnection(
+ _future._client.getFactory(),
+ _future.getWebSocket(),
+ _endp,
+ _buffers,
+ System.currentTimeMillis(),
+ _future.getMaxIdleTime(),
+ _protocol,
+ _future.getExtensions(),
+ WebSocketConnectionRFC6455.VERSION,
+ _future.getMaskGen());
+ }
+
+ public void onInputShutdown() throws IOException
+ {
+ _endp.close();
+ }
+
+ public boolean isIdle()
+ {
+ return false;
+ }
+
+ public boolean isSuspended()
+ {
+ return false;
+ }
+
+ public void onClose()
+ {
+ if (_error != null)
+ _future.handshakeFailed(new ProtocolException(_error));
+ else
+ _future.handshakeFailed(new EOFException());
+ }
+ }
+
+ private static class WebSocketClientConnection extends WebSocketConnectionRFC6455
+ {
+ private final WebSocketClientFactory factory;
+
+ public WebSocketClientConnection(WebSocketClientFactory factory, WebSocket webSocket, EndPoint endPoint, WebSocketBuffers buffers, long timeStamp, int maxIdleTime, String protocol, List<Extension> extensions, int draftVersion, MaskGen maskGen) throws IOException
+ {
+ super(webSocket, endPoint, buffers, timeStamp, maxIdleTime, protocol, extensions, draftVersion, maskGen);
+ this.factory = factory;
+ }
+
+ @Override
+ public void onClose()
+ {
+ super.onClose();
+ factory.removeConnection(this);
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketConnection.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketConnection.java
new file mode 100644
index 00000000..76385f7d
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketConnection.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+
+import java.util.List;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.nio.AsyncConnection;
+
+public interface WebSocketConnection extends AsyncConnection
+{
+ void fillBuffersFrom(Buffer buffer);
+
+ List<Extension> getExtensions();
+
+ WebSocket.Connection getConnection();
+
+ void shutdown();
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java
new file mode 100644
index 00000000..c9f2759d
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java
@@ -0,0 +1,887 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may select to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
+import org.eclipse.jetty.websocket.WebSocket.OnControl;
+import org.eclipse.jetty.websocket.WebSocket.OnFrame;
+import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
+
+import android.text.TextUtils;
+import android.util.Base64;
+
+/* ------------------------------------------------------------ */
+/**
+ * <pre>
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-------+-+-------------+-------------------------------+
+ * |F|R|R|R| opcode|M| Payload len | Extended payload length |
+ * |I|S|S|S| (4) |A| (7) | (16/64) |
+ * |N|V|V|V| |S| | (if payload len==126/127) |
+ * | |1|2|3| |K| | |
+ * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ * | Extended payload length continued, if payload len == 127 |
+ * + - - - - - - - - - - - - - - - +-------------------------------+
+ * | |Masking-key, if MASK set to 1 |
+ * +-------------------------------+-------------------------------+
+ * | Masking-key (continued) | Payload Data |
+ * +-------------------------------- - - - - - - - - - - - - - - - +
+ * : Payload Data continued ... :
+ * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ * | Payload Data continued ... |
+ * +---------------------------------------------------------------+
+ * </pre>
+ *
+ * modified by KNOWLEDGECODE
+ */
+public class WebSocketConnectionRFC6455 extends AbstractConnection implements WebSocketConnection
+{
+ private static final Logger LOG = Log.getLogger(WebSocketConnectionRFC6455.class);
+
+ final static byte OP_CONTINUATION = 0x00;
+ final static byte OP_TEXT = 0x01;
+ final static byte OP_BINARY = 0x02;
+ final static byte OP_EXT_DATA = 0x03;
+
+ final static byte OP_CONTROL = 0x08;
+ final static byte OP_CLOSE = 0x08;
+ final static byte OP_PING = 0x09;
+ final static byte OP_PONG = 0x0A;
+ final static byte OP_EXT_CTRL = 0x0B;
+
+ final static int CLOSE_NORMAL=1000;
+ final static int CLOSE_SHUTDOWN=1001;
+ final static int CLOSE_PROTOCOL=1002;
+ final static int CLOSE_BAD_DATA=1003;
+ final static int CLOSE_UNDEFINED=1004;
+ final static int CLOSE_NO_CODE=1005;
+ final static int CLOSE_NO_CLOSE=1006;
+ final static int CLOSE_BAD_PAYLOAD=1007;
+ final static int CLOSE_POLICY_VIOLATION=1008;
+ final static int CLOSE_MESSAGE_TOO_LARGE=1009;
+ final static int CLOSE_REQUIRED_EXTENSION=1010;
+ final static int CLOSE_SERVER_ERROR=1011;
+ final static int CLOSE_FAILED_TLS_HANDSHAKE=1015;
+
+ final static int FLAG_FIN=0x8;
+
+ // Per RFC 6455, section 1.3 - Opening Handshake - this version is "13"
+ final static int VERSION=13;
+
+ static boolean isLastFrame(byte flags)
+ {
+ return (flags&FLAG_FIN)!=0;
+ }
+
+ static boolean isControlFrame(byte opcode)
+ {
+ return (opcode&OP_CONTROL)!=0;
+ }
+
+ private final static byte[] MAGIC;
+ private final List<Extension> _extensions;
+ private final WebSocketParserRFC6455 _parser;
+ private final WebSocketGeneratorRFC6455 _generator;
+ private final WebSocketGenerator _outbound;
+ private final WebSocket _webSocket;
+ private final OnFrame _onFrame;
+ private final OnBinaryMessage _onBinaryMessage;
+ private final OnTextMessage _onTextMessage;
+ private final OnControl _onControl;
+ private final String _protocol;
+ private final ClassLoader _context;
+ private volatile int _closeCode;
+ private volatile String _closeMessage;
+ private volatile boolean _closedIn;
+ private volatile boolean _closedOut;
+ private int _maxTextMessageSize=-1;
+ private int _maxBinaryMessageSize=-1;
+
+ private static final Charset _iso88591 = Charset.forName("ISO-8859-1");
+ private static final Charset _utf8 = Charset.forName("UTF-8");
+
+ static
+ {
+ MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(_iso88591);
+ }
+
+ private final WebSocket.FrameConnection _connection = new WSFrameConnection();
+
+ /* ------------------------------------------------------------ */
+ public WebSocketConnectionRFC6455(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft, MaskGen maskgen)
+ throws IOException
+ {
+ super(endpoint,timestamp);
+
+ _context=Thread.currentThread().getContextClassLoader();
+
+ _endp.setMaxIdleTime(maxIdleTime);
+
+ _webSocket = websocket;
+ _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null;
+ _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null;
+ _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null;
+ _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
+ _generator = new WebSocketGeneratorRFC6455(buffers, _endp,maskgen);
+
+ _extensions=extensions;
+ WebSocketParser.FrameHandler frameHandler = new WSFrameHandler();
+ if (_extensions!=null)
+ {
+ int e=0;
+ for (Extension extension : _extensions)
+ {
+ extension.bind(
+ _connection,
+ e==extensions.size()-1? frameHandler :extensions.get(e+1),
+ e==0?_generator:extensions.get(e-1));
+ e++;
+ }
+ }
+
+ _outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1);
+ WebSocketParser.FrameHandler inbound = (_extensions == null || _extensions.size() == 0) ? frameHandler : extensions.get(0);
+
+ _parser = new WebSocketParserRFC6455(buffers, endpoint, inbound,maskgen==null);
+
+ _protocol=protocol;
+
+ }
+
+ /* ------------------------------------------------------------ */
+ public WebSocket.Connection getConnection()
+ {
+ return _connection;
+ }
+
+ /* ------------------------------------------------------------ */
+ public List<Extension> getExtensions()
+ {
+ if (_extensions==null)
+ return Collections.emptyList();
+
+ return _extensions;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Connection handle() throws IOException
+ {
+ Thread current = Thread.currentThread();
+ ClassLoader oldcontext = current.getContextClassLoader();
+ current.setContextClassLoader(_context);
+ try
+ {
+ // handle the framing protocol
+ boolean progress=true;
+
+ while (progress)
+ {
+ int flushed=_generator.flushBuffer();
+ int filled=_parser.parseNext();
+
+ progress = flushed>0 || filled>0;
+ _endp.flush();
+
+ if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
+ progress=true;
+ }
+ }
+ catch(IOException e)
+ {
+ try
+ {
+ if (_endp.isOpen())
+ _endp.close();
+ }
+ catch(IOException e2)
+ {
+ LOG.ignore(e2);
+ }
+ throw e;
+ }
+ finally
+ {
+ current.setContextClassLoader(oldcontext);
+ _parser.returnBuffer();
+ _generator.returnBuffer();
+ if (_endp.isOpen())
+ {
+ if (_closedIn && _closedOut && _outbound.isBufferEmpty())
+ _endp.close();
+ else if (_endp.isInputShutdown() && !_closedIn)
+ closeIn(CLOSE_NO_CLOSE,null);
+ else
+ checkWriteable();
+ }
+ }
+ return this;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void onInputShutdown() throws IOException
+ {
+ if (!_closedIn)
+ _endp.close();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isIdle()
+ {
+ return _parser.isBufferEmpty() && _outbound.isBufferEmpty();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void onIdleExpired(long idleForMs)
+ {
+ closeOut(WebSocketConnectionRFC6455.CLOSE_NORMAL,"Idle for "+idleForMs+"ms > "+_endp.getMaxIdleTime()+"ms");
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isSuspended()
+ {
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void onClose()
+ {
+ final boolean closed;
+ synchronized (this)
+ {
+ closed=_closeCode==0;
+ if (closed)
+ _closeCode=WebSocketConnectionRFC6455.CLOSE_NO_CLOSE;
+ }
+ if (closed)
+ _webSocket.onClose(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"closed");
+ }
+
+ /* ------------------------------------------------------------ */
+ public void closeIn(int code,String message)
+ {
+ LOG.debug("ClosedIn {} {} {}",this,code,message);
+
+ final boolean closed_out;
+ final boolean tell_app;
+ synchronized (this)
+ {
+ closed_out=_closedOut;
+ _closedIn=true;
+ tell_app=_closeCode==0;
+ if (tell_app)
+ {
+ _closeCode=code;
+ _closeMessage=message;
+ }
+ }
+
+ try
+ {
+ if (!closed_out)
+ closeOut(code,message);
+ }
+ finally
+ {
+ if (tell_app)
+ _webSocket.onClose(code,message);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void closeOut(int code,String message)
+ {
+ LOG.debug("ClosedOut {} {} {}",this,code,message);
+
+ final boolean closed_out;
+ final boolean tell_app;
+ synchronized (this)
+ {
+ closed_out=_closedOut;
+ _closedOut=true;
+ tell_app=_closeCode==0;
+ if (tell_app)
+ {
+ _closeCode=code;
+ _closeMessage=message;
+ }
+ }
+
+ try
+ {
+ if (tell_app)
+ _webSocket.onClose(code,message);
+ }
+ finally
+ {
+ try
+ {
+ if (!closed_out)
+ {
+ // Close code 1005/1006/1015 are never to be sent as a status over
+ // a Close control frame. Code<-1 also means no node.
+
+ if (code < 0 || (code == WebSocketConnectionRFC6455.CLOSE_NO_CODE) || (code == WebSocketConnectionRFC6455.CLOSE_NO_CLOSE)
+ || (code == WebSocketConnectionRFC6455.CLOSE_FAILED_TLS_HANDSHAKE))
+ {
+ code = -1;
+ }
+ else if (code == 0)
+ {
+ code = WebSocketConnectionRFC6455.CLOSE_NORMAL;
+ }
+
+ byte[] bytes = ("xx"+(message==null?"":message)).getBytes(_iso88591);
+ bytes[0]=(byte)(code/0x100);
+ bytes[1]=(byte)(code%0x100);
+ _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionRFC6455.OP_CLOSE,bytes,0,code>0?bytes.length:0);
+ _outbound.flush();
+ }
+ }
+ catch(IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ }
+
+ public void shutdown()
+ {
+ final WebSocket.Connection connection = _connection;
+ if (connection != null)
+ connection.close(CLOSE_SHUTDOWN, null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void fillBuffersFrom(Buffer buffer)
+ {
+ _parser.fill(buffer);
+ }
+
+ /* ------------------------------------------------------------ */
+ private void checkWriteable()
+ {
+ if (!_outbound.isBufferEmpty() && _endp instanceof AsyncEndPoint)
+ {
+ ((AsyncEndPoint)_endp).scheduleWrite();
+ }
+ }
+
+ protected void onFrameHandshake()
+ {
+ if (_onFrame != null)
+ {
+ _onFrame.onHandshake(_connection);
+ }
+ }
+
+ protected void onWebSocketOpen()
+ {
+ _webSocket.onOpen(_connection);
+ }
+
+ /* ------------------------------------------------------------ */
+ private class WSFrameConnection implements WebSocket.FrameConnection
+ {
+ private volatile boolean _disconnecting;
+
+ /* ------------------------------------------------------------ */
+ public void sendMessage(String content) throws IOException
+ {
+ if (_closedOut)
+ throw new IOException("closedOut " + _closeCode + ":" + _closeMessage);
+ byte[] data = content.getBytes(_utf8);
+ _outbound.addFrame((byte)FLAG_FIN, WebSocketConnectionRFC6455.OP_TEXT, data, 0, data.length);
+ checkWriteable();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void sendMessage(byte[] content, int offset, int length) throws IOException
+ {
+ if (_closedOut)
+ throw new IOException("closedOut " + _closeCode + ":" + _closeMessage);
+ _outbound.addFrame((byte)FLAG_FIN, WebSocketConnectionRFC6455.OP_BINARY, content, offset, length);
+ checkWriteable();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
+ {
+ if (_closedOut)
+ throw new IOException("closedOut " + _closeCode + ":" + _closeMessage);
+ _outbound.addFrame(flags, opcode, content, offset, length);
+ checkWriteable();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException
+ {
+ // TODO: section 5.5 states that control frames MUST never be length > 125 bytes and MUST NOT be fragmented
+ if (_closedOut)
+ throw new IOException("closedOut " + _closeCode + ":" + _closeMessage);
+ _outbound.addFrame((byte)FLAG_FIN, ctrl, data, offset, length);
+ checkWriteable();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isMessageComplete(byte flags)
+ {
+ return isLastFrame(flags);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isOpen()
+ {
+ return _endp!=null&&_endp.isOpen();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void close(int code, String message)
+ {
+ if (_disconnecting)
+ return;
+ _disconnecting=true;
+ WebSocketConnectionRFC6455.this.closeOut(code,message);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxIdleTime(int ms)
+ {
+ try
+ {
+ _endp.setMaxIdleTime(ms);
+ }
+ catch(IOException e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxTextMessageSize(int size)
+ {
+ _maxTextMessageSize=size;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxBinaryMessageSize(int size)
+ {
+ _maxBinaryMessageSize=size;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxIdleTime()
+ {
+ return _endp.getMaxIdleTime();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxTextMessageSize()
+ {
+ return _maxTextMessageSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxBinaryMessageSize()
+ {
+ return _maxBinaryMessageSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getProtocol()
+ {
+ return _protocol;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getExtensions()
+ {
+ List<String> extensions = new ArrayList<String>();
+
+ for (Extension extension : _extensions)
+ {
+ extensions.add(extension.getParameterizedName());
+ }
+ return TextUtils.join(", ", extensions);
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte binaryOpcode()
+ {
+ return OP_BINARY;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte textOpcode()
+ {
+ return OP_TEXT;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte continuationOpcode()
+ {
+ return OP_CONTINUATION;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte finMask()
+ {
+ return FLAG_FIN;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isControl(byte opcode)
+ {
+ return isControlFrame(opcode);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isText(byte opcode)
+ {
+ return opcode==OP_TEXT;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isBinary(byte opcode)
+ {
+ return opcode==OP_BINARY;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isContinuation(byte opcode)
+ {
+ return opcode==OP_CONTINUATION;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isClose(byte opcode)
+ {
+ return opcode==OP_CLOSE;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isPing(byte opcode)
+ {
+ return opcode==OP_PING;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isPong(byte opcode)
+ {
+ return opcode==OP_PONG;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void close()
+ {
+ close(CLOSE_NORMAL,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAllowFrameFragmentation(boolean allowFragmentation)
+ {
+ _parser.setFakeFragments(allowFragmentation);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isAllowFrameFragmentation()
+ {
+ return _parser.isFakeFragments();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format(Locale.getDefault(), "%s@%x l(%s:%d)<->r(%s:%d)",
+ getClass().getSimpleName(),
+ hashCode(),
+ _endp.getLocalAddr(),
+ _endp.getLocalPort(),
+ _endp.getRemoteAddr(),
+ _endp.getRemotePort());
+ }
+ }
+
+ private class WSFrameHandler implements WebSocketParser.FrameHandler
+ {
+ private static final int MAX_CONTROL_FRAME_PAYLOAD = 125;
+ private static final int INITIAL_CAPACITY = 8192;
+ private WebSocketBuffer _buffer = new WebSocketBuffer(INITIAL_CAPACITY);
+ private byte _opcode = -1;
+
+ private boolean excess(int opcode, int length)
+ {
+ switch (opcode)
+ {
+ case WebSocketConnectionRFC6455.OP_TEXT:
+ return _maxTextMessageSize > 0 && _maxTextMessageSize < length;
+ case WebSocketConnectionRFC6455.OP_BINARY:
+ return _maxBinaryMessageSize > 0 && _maxBinaryMessageSize < length;
+ default:
+ return false;
+ }
+ }
+
+ public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
+ {
+ synchronized (WebSocketConnectionRFC6455.this)
+ {
+ // Ignore incoming after a close
+ if (_closedIn)
+ {
+ return;
+ }
+ }
+
+ byte[] array = buffer.array();
+ int offset = buffer.getIndex();
+ int length = buffer.length();
+
+ if (isControlFrame(opcode) && length > MAX_CONTROL_FRAME_PAYLOAD)
+ {
+ errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL, "Control frame too large: " + length + " > " + MAX_CONTROL_FRAME_PAYLOAD);
+ return;
+ }
+
+ if ((flags & 0x7) != 0)
+ {
+ errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL, "RSV bits set 0x" + Integer.toHexString(flags));
+ return;
+ }
+
+ // Ignore all frames after error close
+ if (_closeCode != 0 && _closeCode != CLOSE_NORMAL && opcode != OP_CLOSE)
+ {
+ return;
+ }
+
+ // Deliver frame if websocket is a FrameWebSocket
+ if (_onFrame != null)
+ {
+ if (_onFrame.onFrame(flags, opcode, array, offset, length))
+ {
+ return;
+ }
+ }
+
+ if (_onControl != null && isControlFrame(opcode))
+ {
+ if (_onControl.onControl(opcode, array, offset, length))
+ {
+ return;
+ }
+ }
+
+ switch (opcode)
+ {
+ case WebSocketConnectionRFC6455.OP_TEXT:
+ case WebSocketConnectionRFC6455.OP_BINARY:
+ {
+ if (_opcode != -1)
+ {
+ _buffer.clear();
+ errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL, "Expected Continuation" + Integer.toHexString(opcode));
+ return;
+ }
+ _opcode = opcode;
+ }
+ case WebSocketConnectionRFC6455.OP_CONTINUATION:
+ {
+ if (_opcode == -1)
+ {
+ _buffer.clear();
+ errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL, "Bad Continuation");
+ return;
+ }
+ if (excess(_opcode, _buffer.length() + length))
+ {
+ switch (_opcode)
+ {
+ case WebSocketConnectionRFC6455.OP_TEXT:
+ _connection.close(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE, "Text message size > " + _maxTextMessageSize + " chars");
+ return;
+ case WebSocketConnectionRFC6455.OP_BINARY:
+ _connection.close(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE, "Message size > " + _maxBinaryMessageSize);
+ return;
+ }
+ }
+ if (isLastFrame(flags))
+ {
+ switch (_opcode)
+ {
+ case WebSocketConnectionRFC6455.OP_TEXT:
+ if (_buffer.length() == 0)
+ {
+ _onTextMessage.onMessage(new String(array, offset, length, _utf8));
+ }
+ else
+ {
+ _onTextMessage.onMessage(_buffer.append(array, offset, length).toString(_utf8));
+ }
+ break;
+ case WebSocketConnectionRFC6455.OP_BINARY:
+ if (_buffer.length() == 0)
+ {
+ _onBinaryMessage.onMessage(array, offset, length);
+ }
+ else
+ {
+ _onBinaryMessage.onMessage(_buffer.append(array, offset, length).array(), 0, _buffer.length());
+ }
+ break;
+ }
+ _opcode = -1;
+ _buffer.clear();
+ }
+ else
+ {
+ _buffer.append(array, offset, length);
+ }
+ break;
+ }
+
+ case WebSocketConnectionRFC6455.OP_PING:
+ {
+ LOG.debug("PING {}",this);
+ if (!_closedOut)
+ {
+ try
+ {
+ _connection.sendControl(WebSocketConnectionRFC6455.OP_PONG, array, offset, length);
+ }
+ catch (Throwable e)
+ {
+ errorClose(WebSocketConnectionRFC6455.CLOSE_SERVER_ERROR, "Internal Server Error: " + e);
+ }
+ }
+ break;
+ }
+
+ case WebSocketConnectionRFC6455.OP_PONG:
+ {
+ LOG.debug("PONG {}",this);
+ break;
+ }
+
+ case WebSocketConnectionRFC6455.OP_CLOSE:
+ {
+ int code = WebSocketConnectionRFC6455.CLOSE_NO_CODE;
+ String message = null;
+ if (length >= 2)
+ {
+ code=(0xff & array[offset]) * 0x100 + (0xff & array[offset + 1]);
+
+ // Validate close status codes.
+ if (code < WebSocketConnectionRFC6455.CLOSE_NORMAL ||
+ code == WebSocketConnectionRFC6455.CLOSE_UNDEFINED ||
+ code == WebSocketConnectionRFC6455.CLOSE_NO_CLOSE ||
+ code == WebSocketConnectionRFC6455.CLOSE_NO_CODE ||
+ ( code > 1011 && code <= 2999 ) ||
+ code >= 5000 )
+ {
+ errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Invalid close code " + code);
+ return;
+ }
+ }
+ else if(buffer.length() == 1)
+ {
+ // Invalid length. use status code 1002 (Protocol error)
+ errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Invalid payload length of 1");
+ return;
+ }
+ closeIn(code, message);
+ break;
+ }
+
+ default:
+ errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Bad opcode 0x"+Integer.toHexString(opcode));
+ break;
+ }
+ }
+
+ private void errorClose(int code, String message)
+ {
+ _connection.close(code, message);
+
+ // Brutally drop the connection
+ try
+ {
+ _endp.close();
+ }
+ catch (IOException e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+ }
+
+ public void close(int code,String message)
+ {
+ if (code != CLOSE_NORMAL)
+ LOG.warn("Close: " + code + " " + message);
+ _connection.close(code, message);
+ }
+
+ @Override
+ public String toString()
+ {
+ return WebSocketConnectionRFC6455.this.toString() + "FH";
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String hashKey(String key)
+ {
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance("SHA1");
+ md.update(key.getBytes(_utf8));
+ md.update(MAGIC);
+ return Base64.encodeToString(md.digest(), Base64.NO_WRAP);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s p=%s g=%s", getClass().getSimpleName(), _parser, _generator);
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketGenerator.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketGenerator.java
new file mode 100644
index 00000000..10046ac2
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketGenerator.java
@@ -0,0 +1,33 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+
+
+/* ------------------------------------------------------------ */
+/** WebSocketGenerator.
+ */
+public interface WebSocketGenerator
+{
+ int flush() throws IOException;
+ boolean isBufferEmpty();
+ void addFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException;
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java
new file mode 100644
index 00000000..7fedfb3f
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java
@@ -0,0 +1,319 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+
+
+/**
+ * WebSocketGenerator.
+ * This class generates websocket packets.
+ * It is fully synchronized because it is likely that async
+ * threads will call the addMessage methods while other
+ * threads are flushing the generator.
+ *
+ * modified by KNOWLEDGECODE
+ */
+public class WebSocketGeneratorRFC6455 implements WebSocketGenerator
+{
+ private final Lock _lock = new ReentrantLock();
+ private final WebSocketBuffers _buffers;
+ private final EndPoint _endp;
+ private final byte[] _mask = new byte[4];
+ private final MaskGen _maskGen;
+ private Buffer _buffer;
+ private int _m;
+ private boolean _opsent;
+ private boolean _closed;
+
+ public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp)
+ {
+ this(buffers, endp, null);
+ }
+
+ public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen)
+ {
+ _buffers = buffers;
+ _endp = endp;
+ _maskGen = maskGen;
+ }
+
+ public Buffer getBuffer()
+ {
+ _lock.lock();
+ try
+ {
+ return _buffer;
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
+ {
+ _lock.lock();
+ try
+ {
+ if (_closed)
+ throw new EofException("Closed");
+ if (opcode == WebSocketConnectionRFC6455.OP_CLOSE)
+ _closed = true;
+
+ boolean mask = _maskGen != null;
+
+ if (_buffer == null)
+ _buffer = mask ? _buffers.getBuffer() : _buffers.getDirectBuffer();
+
+ boolean last = WebSocketConnectionRFC6455.isLastFrame(flags);
+
+ int space = mask ? 14 : 10;
+
+ do
+ {
+ if (_opsent)
+ {
+ flags &= 0x0b;
+ opcode = WebSocketConnectionRFC6455.OP_CONTINUATION;
+ }
+ opcode = (byte)(((0xf & flags) << 4) + (0xf & opcode));
+ _opsent = true;
+
+ int payload = length;
+ if (payload + space > _buffer.capacity())
+ {
+ // We must fragement, so clear FIN bit
+ opcode = (byte)(opcode & 0x7F); // Clear the FIN bit
+ payload = _buffer.capacity() - space;
+ }
+ else if (last)
+ opcode = (byte)(opcode | 0x80); // Set the FIN bit
+
+ // ensure there is space for header
+ if (_buffer.space() <= space)
+ {
+ flushBuffer();
+ if (_buffer.space() <= space)
+ flush();
+ }
+
+ // write the opcode and length
+ if (payload > 0xffff)
+ {
+ _buffer.put(new byte[]{
+ opcode,
+ mask ? (byte)0xff : (byte)0x7f,
+ (byte)0,
+ (byte)0,
+ (byte)0,
+ (byte)0,
+ (byte)((payload >> 24) & 0xff),
+ (byte)((payload >> 16) & 0xff),
+ (byte)((payload >> 8) & 0xff),
+ (byte)(payload & 0xff)});
+ }
+ else if (payload >= 0x7e)
+ {
+ _buffer.put(new byte[]{
+ opcode,
+ mask ? (byte)0xfe : (byte)0x7e,
+ (byte)(payload >> 8),
+ (byte)(payload & 0xff)});
+ }
+ else
+ {
+ _buffer.put(new byte[]{
+ opcode,
+ (byte)(mask ? (0x80 | payload) : payload)});
+ }
+
+ // write mask
+ if (mask)
+ {
+ _maskGen.genMask(_mask);
+ _m = 0;
+ _buffer.put(_mask);
+ }
+
+ // write payload
+ int remaining = payload;
+ while (remaining > 0)
+ {
+ _buffer.compact();
+ int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
+
+ if (mask)
+ {
+ for (int i = 0; i < chunk; i++)
+ _buffer.put((byte)(content[offset + (payload - remaining) + i] ^ _mask[+_m++ % 4]));
+ }
+ else
+ _buffer.put(content, offset + (payload - remaining), chunk);
+
+ remaining -= chunk;
+ if (_buffer.space() > 0)
+ {
+ // Gently flush the data, issuing a non-blocking write
+ flushBuffer();
+ }
+ else
+ {
+ // Forcibly flush the data, issuing a blocking write
+ flush();
+ if (remaining == 0)
+ {
+ // Gently flush the data, issuing a non-blocking write
+ flushBuffer();
+ }
+ }
+ }
+ offset += payload;
+ length -= payload;
+ }
+ while (length > 0);
+ _opsent = !last;
+
+ if (_buffer != null && _buffer.length() == 0)
+ {
+ _buffers.returnBuffer(_buffer);
+ _buffer = null;
+ }
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ public int flushBuffer() throws IOException
+ {
+ if (!_lock.tryLock())
+ return 0;
+
+ try
+ {
+ if (!_endp.isOpen())
+ throw new EofException();
+
+ if (_buffer != null)
+ {
+ int flushed = _buffer.hasContent() ? _endp.flush(_buffer) : 0;
+ if (_closed && _buffer.length() == 0)
+ _endp.shutdownOutput();
+ return flushed;
+ }
+
+ return 0;
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ public int flush() throws IOException
+ {
+ if (!_lock.tryLock())
+ return 0;
+
+ try
+ {
+ if (_buffer == null)
+ return 0;
+
+ int result = flushBuffer();
+ if (!_endp.isBlocking())
+ {
+ long now = System.currentTimeMillis();
+ long end = now + _endp.getMaxIdleTime();
+ while (_buffer.length() > 0)
+ {
+ boolean ready = _endp.blockWritable(end - now);
+ if (!ready)
+ {
+ now = System.currentTimeMillis();
+ if (now < end)
+ continue;
+ throw new IOException("Write timeout");
+ }
+
+ result += flushBuffer();
+ }
+ }
+ _buffer.compact();
+ return result;
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ public boolean isBufferEmpty()
+ {
+ _lock.lock();
+ try
+ {
+ return _buffer == null || _buffer.length() == 0;
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ public void returnBuffer()
+ {
+ _lock.lock();
+ try
+ {
+ if (_buffer != null && _buffer.length() == 0)
+ {
+ _buffers.returnBuffer(_buffer);
+ _buffer = null;
+ }
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ // Do NOT use synchronized (this)
+ // because it's very easy to deadlock when debugging is enabled.
+ // We do a best effort to print the right toString() and that's it.
+ Buffer buffer = _buffer;
+ return String.format(Locale.getDefault(), "%s@%x closed=%b buffer=%d",
+ getClass().getSimpleName(),
+ hashCode(),
+ _closed,
+ buffer == null ? -1 : buffer.length());
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketParser.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketParser.java
new file mode 100644
index 00000000..b53250b5
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketParser.java
@@ -0,0 +1,49 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import org.eclipse.jetty.io.Buffer;
+
+/* ------------------------------------------------------------ */
+/**
+ * Parser the WebSocket protocol.
+ *
+ */
+public interface WebSocketParser
+{
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public interface FrameHandler
+ {
+ void onFrame(byte flags, byte opcode, Buffer buffer);
+ void close(int code,String message);
+ }
+
+ Buffer getBuffer();
+
+ /**
+ * @return an indication of progress, normally bytes filled plus events parsed, or -1 for EOF
+ */
+ int parseNext();
+
+ boolean isBufferEmpty();
+
+ void fill(Buffer buffer);
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketParserRFC6455.java b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketParserRFC6455.java
new file mode 100644
index 00000000..5b516e19
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/org/eclipse/jetty/websocket/WebSocketParserRFC6455.java
@@ -0,0 +1,392 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Parser the WebSocket protocol.
+ *
+ */
+public class WebSocketParserRFC6455 implements WebSocketParser
+{
+ private static final Logger LOG = Log.getLogger(WebSocketParserRFC6455.class);
+
+ public enum State {
+
+ START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1), SEEK_EOF(1);
+
+ int _needs;
+
+ State(int needs)
+ {
+ _needs=needs;
+ }
+
+ int getNeeds()
+ {
+ return _needs;
+ }
+ }
+
+ private final WebSocketBuffers _buffers;
+ private final EndPoint _endp;
+ private final FrameHandler _handler;
+ private final boolean _shouldBeMasked;
+ private State _state;
+ private Buffer _buffer;
+ private byte _flags;
+ private byte _opcode;
+ private int _bytesNeeded;
+ private long _length;
+ private boolean _masked;
+ private final byte[] _mask = new byte[4];
+ private int _m;
+ private boolean _skip;
+ private boolean _fragmentFrames=true;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param buffers The buffers to use for parsing. Only the {@link Buffers#getBuffer()} is used.
+ * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
+ * is mostly used.
+ * @param endp the endpoint
+ * @param handler the handler to notify when a parse event occurs
+ * @param shouldBeMasked whether masking should be handled
+ */
+ public WebSocketParserRFC6455(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked)
+ {
+ _buffers=buffers;
+ _endp=endp;
+ _handler=handler;
+ _shouldBeMasked=shouldBeMasked;
+ _state=State.START;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if fake fragments should be created for frames larger than the buffer.
+ */
+ public boolean isFakeFragments()
+ {
+ return _fragmentFrames;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
+ */
+ public void setFakeFragments(boolean fakeFragments)
+ {
+ _fragmentFrames = fakeFragments;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isBufferEmpty()
+ {
+ return _buffer==null || _buffer.length()==0;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Buffer getBuffer()
+ {
+ return _buffer;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Parse to next event.
+ * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
+ * available. Fill data from the {@link EndPoint} only as necessary.
+ * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
+ * that no bytes were read and no messages parsed. A positive number indicates either
+ * the bytes filled or the messages parsed.
+ */
+ public int parseNext()
+ {
+ if (_buffer==null)
+ _buffer=_buffers.getBuffer();
+
+ boolean progress=false;
+ int filled=-1;
+
+ // Loop until a datagram call back or can't fill anymore
+ while(!progress && (!_endp.isInputShutdown()||_buffer.length()>0))
+ {
+ int available=_buffer.length();
+
+ // Fill buffer if we need a byte or need length
+ while (available<(_state==State.SKIP?1:_bytesNeeded))
+ {
+ // compact to mark (set at start of data)
+ _buffer.compact();
+
+ // if no space, then the data is too big for buffer
+ if (_buffer.space() == 0)
+ {
+ // Can we send a fake frame?
+ if (_fragmentFrames && _state==State.DATA)
+ {
+ Buffer data =_buffer.get(4*(available/4));
+ _buffer.compact();
+ if (_masked)
+ {
+ if (data.array()==null)
+ data=_buffer.asMutableBuffer();
+ byte[] array = data.array();
+ final int end=data.putIndex();
+ for (int i=data.getIndex();i<end;i++)
+ array[i]^=_mask[_m++%4];
+ }
+
+ // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
+ _bytesNeeded-=data.length();
+ progress=true;
+ _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionRFC6455.FLAG_FIN)), _opcode, data);
+
+ _opcode=WebSocketConnectionRFC6455.OP_CONTINUATION;
+ }
+
+ if (_buffer.space() == 0)
+ throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
+ }
+
+ // catch IOExceptions (probably EOF) and try to parse what we have
+ try
+ {
+ filled=_endp.isInputShutdown()?-1:_endp.fill(_buffer);
+ available=_buffer.length();
+ // System.err.printf(">> filled %d/%d%n",filled,available);
+ if (filled<=0)
+ break;
+ }
+ catch(IOException e)
+ {
+ LOG.debug(e);
+ filled=-1;
+ break;
+ }
+ }
+ // Did we get enough?
+ if (available<(_state==State.SKIP?1:_bytesNeeded))
+ break;
+
+ // if we are here, then we have sufficient bytes to process the current state.
+ // Parse the buffer byte by byte (unless it is STATE_DATA)
+ byte b;
+ while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
+ {
+ switch (_state)
+ {
+ case START:
+ _skip=false;
+ _state=_opcode==WebSocketConnectionRFC6455.OP_CLOSE?State.SEEK_EOF:State.OPCODE;
+ _bytesNeeded=_state.getNeeds();
+ continue;
+
+ case OPCODE:
+ b=_buffer.get();
+ available--;
+ _opcode=(byte)(b&0xf);
+ _flags=(byte)(0xf&(b>>4));
+
+ if (WebSocketConnectionRFC6455.isControlFrame(_opcode)&&!WebSocketConnectionRFC6455.isLastFrame(_flags))
+ {
+ LOG.warn("Fragmented Control from "+_endp);
+ _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Fragmented control");
+ progress=true;
+ _skip=true;
+ }
+
+ _state=State.LENGTH_7;
+ _bytesNeeded=_state.getNeeds();
+
+ continue;
+
+ case LENGTH_7:
+ b=_buffer.get();
+ available--;
+ _masked=(b&0x80)!=0;
+ b=(byte)(0x7f&b);
+
+ switch(b)
+ {
+ case 0x7f:
+ _length=0;
+ _state=State.LENGTH_63;
+ break;
+ case 0x7e:
+ _length=0;
+ _state=State.LENGTH_16;
+ break;
+ default:
+ _length=(0x7f&b);
+ _state=_masked?State.MASK:State.PAYLOAD;
+ }
+ _bytesNeeded=_state.getNeeds();
+ continue;
+
+ case LENGTH_16:
+ b=_buffer.get();
+ available--;
+ _length = _length*0x100 + (0xff&b);
+ if (--_bytesNeeded==0)
+ {
+ if (_length>_buffer.capacity() && !_fragmentFrames)
+ {
+ progress=true;
+ _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
+ _skip=true;
+ }
+
+ _state=_masked?State.MASK:State.PAYLOAD;
+ _bytesNeeded=_state.getNeeds();
+ }
+ continue;
+
+ case LENGTH_63:
+ b=_buffer.get();
+ available--;
+ _length = _length*0x100 + (0xff&b);
+ if (--_bytesNeeded==0)
+ {
+ _bytesNeeded=(int)_length;
+ if (_length>=_buffer.capacity() && !_fragmentFrames)
+ {
+ progress=true;
+ _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
+ _skip=true;
+ }
+
+ _state=_masked?State.MASK:State.PAYLOAD;
+ _bytesNeeded=_state.getNeeds();
+ }
+ continue;
+
+ case MASK:
+ _buffer.get(_mask,0,4);
+ _m=0;
+ available-=4;
+ _state=State.PAYLOAD;
+ _bytesNeeded=_state.getNeeds();
+ break;
+
+ case PAYLOAD:
+ _bytesNeeded=(int)_length;
+ _state=_skip?State.SKIP:State.DATA;
+ break;
+
+ case DATA:
+ break;
+
+ case SKIP:
+ int skip=Math.min(available,_bytesNeeded);
+ progress=true;
+ _buffer.skip(skip);
+ available-=skip;
+ _bytesNeeded-=skip;
+ if (_bytesNeeded==0)
+ _state=State.START;
+ break;
+
+ case SEEK_EOF:
+ progress=true;
+ _buffer.skip(available);
+ available=0;
+ break;
+ }
+ }
+
+ if (_state==State.DATA && available>=_bytesNeeded)
+ {
+ if ( _masked!=_shouldBeMasked)
+ {
+ _buffer.skip(_bytesNeeded);
+ _state=State.START;
+ progress=true;
+ _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Not masked");
+ }
+ else
+ {
+ Buffer data =_buffer.get(_bytesNeeded);
+ if (_masked)
+ {
+ if (data.array()==null)
+ data=_buffer.asMutableBuffer();
+ byte[] array = data.array();
+ final int end=data.putIndex();
+ for (int i=data.getIndex();i<end;i++)
+ array[i]^=_mask[_m++%4];
+ }
+
+ // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
+
+ progress=true;
+ _handler.onFrame(_flags, _opcode, data);
+ _bytesNeeded=0;
+ _state=State.START;
+ }
+
+ break;
+ }
+ }
+
+ return progress?1:filled;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void fill(Buffer buffer)
+ {
+ if (buffer!=null && buffer.length()>0)
+ {
+ if (_buffer==null)
+ _buffer=_buffers.getBuffer();
+
+ _buffer.put(buffer);
+ buffer.clear();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void returnBuffer()
+ {
+ if (_buffer!=null && _buffer.length()==0)
+ {
+ _buffers.returnBuffer(_buffer);
+ _buffer=null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x state=%s buffer=%s",
+ getClass().getSimpleName(),
+ hashCode(),
+ _state,
+ _buffer);
+ }
+}