summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/IceInternal/Buffer.java28
-rw-r--r--java/src/IceInternal/IPEndpointI.java6
-rw-r--r--java/src/IceInternal/TcpEndpointI.java6
-rw-r--r--java/src/IceInternal/UdpEndpointI.java6
-rw-r--r--java/src/IceSSL/EndpointI.java6
-rw-r--r--java/src/IceWS/AcceptorI.java56
-rw-r--r--java/src/IceWS/ConnectorI.java74
-rw-r--r--java/src/IceWS/EndpointFactoryI.java54
-rw-r--r--java/src/IceWS/EndpointI.java302
-rw-r--r--java/src/IceWS/HttpParser.java735
-rw-r--r--java/src/IceWS/Instance.java18
-rw-r--r--java/src/IceWS/PluginFactory.java32
-rw-r--r--java/src/IceWS/PluginI.java38
-rw-r--r--java/src/IceWS/TransceiverI.java1502
-rw-r--r--java/src/IceWS/Util.java14
-rw-r--r--java/src/IceWS/WebSocketException.java28
16 files changed, 2890 insertions, 15 deletions
diff --git a/java/src/IceInternal/Buffer.java b/java/src/IceInternal/Buffer.java
index b8c902e7334..b14d732c643 100644
--- a/java/src/IceInternal/Buffer.java
+++ b/java/src/IceInternal/Buffer.java
@@ -18,33 +18,54 @@ public class Buffer
public
Buffer(int maxCapacity, boolean direct)
{
+ this(maxCapacity, direct, java.nio.ByteOrder.LITTLE_ENDIAN);
+ }
+
+ public
+ Buffer(int maxCapacity, boolean direct, java.nio.ByteOrder order)
+ {
b = _emptyBuffer;
_size = 0;
_capacity = 0;
_maxCapacity = maxCapacity;
_direct = direct;
+ _order = order;
}
public
Buffer(byte[] data)
{
+ this(data, java.nio.ByteOrder.LITTLE_ENDIAN);
+ }
+
+ public
+ Buffer(byte[] data, java.nio.ByteOrder order)
+ {
b = java.nio.ByteBuffer.wrap(data);
- b.order(java.nio.ByteOrder.LITTLE_ENDIAN);
+ b.order(order);
_size = data.length;
_capacity = 0;
_maxCapacity = 0;
_direct = false;
+ _order = order;
}
public
Buffer(java.nio.ByteBuffer data)
{
+ this(data, java.nio.ByteOrder.LITTLE_ENDIAN);
+ }
+
+ public
+ Buffer(java.nio.ByteBuffer data, java.nio.ByteOrder order)
+ {
b = data;
- b.order(java.nio.ByteOrder.LITTLE_ENDIAN);
+ b.order(order);
_size = data.remaining();
_capacity = 0;
_maxCapacity = 0;
_direct = false;
+ _order = order;
}
public int
@@ -181,7 +202,7 @@ public class Buffer
b.position(pos);
}
- b.order(java.nio.ByteOrder.LITTLE_ENDIAN);
+ b.order(_order); // Preserve the original order.
}
catch(OutOfMemoryError ex)
{
@@ -199,4 +220,5 @@ public class Buffer
private int _maxCapacity;
private boolean _direct; // Use direct buffers?
private int _shrinkCounter;
+ private java.nio.ByteOrder _order;
}
diff --git a/java/src/IceInternal/IPEndpointI.java b/java/src/IceInternal/IPEndpointI.java
index 3375b51d36c..ebf0da9bdbd 100644
--- a/java/src/IceInternal/IPEndpointI.java
+++ b/java/src/IceInternal/IPEndpointI.java
@@ -228,13 +228,13 @@ public abstract class IPEndpointI extends EndpointI
return _port;
}
- protected void streamWriteImpl(BasicStream s)
+ public void streamWriteImpl(BasicStream s)
{
s.writeString(_host);
s.writeInt(_port);
}
- protected int hashInit(int h)
+ public int hashInit(int h)
{
h = HashUtil.hashAdd(h, _host);
h = HashUtil.hashAdd(h, _port);
@@ -242,7 +242,7 @@ public abstract class IPEndpointI extends EndpointI
return h;
}
- protected void fillEndpointInfo(Ice.IPEndpointInfo info)
+ public void fillEndpointInfo(Ice.IPEndpointInfo info)
{
info.host = _host;
info.port = _port;
diff --git a/java/src/IceInternal/TcpEndpointI.java b/java/src/IceInternal/TcpEndpointI.java
index 6fb7a7e9ce1..28ab17d227c 100644
--- a/java/src/IceInternal/TcpEndpointI.java
+++ b/java/src/IceInternal/TcpEndpointI.java
@@ -215,14 +215,14 @@ final class TcpEndpointI extends IPEndpointI
return super.compareTo(obj);
}
- protected void streamWriteImpl(BasicStream s)
+ public void streamWriteImpl(BasicStream s)
{
super.streamWriteImpl(s);
s.writeInt(_timeout);
s.writeBool(_compress);
}
- protected int hashInit(int h)
+ public int hashInit(int h)
{
h = super.hashInit(h);
h = IceInternal.HashUtil.hashAdd(h, _timeout);
@@ -230,7 +230,7 @@ final class TcpEndpointI extends IPEndpointI
return h;
}
- protected void fillEndpointInfo(Ice.IPEndpointInfo info)
+ public void fillEndpointInfo(Ice.IPEndpointInfo info)
{
super.fillEndpointInfo(info);
if(info instanceof Ice.TCPEndpointInfo)
diff --git a/java/src/IceInternal/UdpEndpointI.java b/java/src/IceInternal/UdpEndpointI.java
index 43b5ba48d50..9bda0e86d4b 100644
--- a/java/src/IceInternal/UdpEndpointI.java
+++ b/java/src/IceInternal/UdpEndpointI.java
@@ -249,7 +249,7 @@ final class UdpEndpointI extends IPEndpointI
//
// Marshal the endpoint
//
- protected void streamWriteImpl(BasicStream s)
+ public void streamWriteImpl(BasicStream s)
{
super.streamWriteImpl(s);
if(s.getWriteEncoding().equals(Ice.Util.Encoding_1_0))
@@ -262,7 +262,7 @@ final class UdpEndpointI extends IPEndpointI
s.writeBool(_compress);
}
- protected int hashInit(int h)
+ public int hashInit(int h)
{
h = super.hashInit(h);
h = IceInternal.HashUtil.hashAdd(h, _mcastInterface);
@@ -272,7 +272,7 @@ final class UdpEndpointI extends IPEndpointI
return h;
}
- protected void fillEndpointInfo(Ice.IPEndpointInfo info)
+ public void fillEndpointInfo(Ice.IPEndpointInfo info)
{
super.fillEndpointInfo(info);
if(info instanceof Ice.UDPEndpointInfo)
diff --git a/java/src/IceSSL/EndpointI.java b/java/src/IceSSL/EndpointI.java
index c2d297a9e85..dfac583deee 100644
--- a/java/src/IceSSL/EndpointI.java
+++ b/java/src/IceSSL/EndpointI.java
@@ -218,14 +218,14 @@ final class EndpointI extends IceInternal.IPEndpointI
return super.compareTo(obj);
}
- protected void streamWriteImpl(IceInternal.BasicStream s)
+ public void streamWriteImpl(IceInternal.BasicStream s)
{
super.streamWriteImpl(s);
s.writeInt(_timeout);
s.writeBool(_compress);
}
- protected int hashInit(int h)
+ public int hashInit(int h)
{
h = super.hashInit(h);
h = IceInternal.HashUtil.hashAdd(h, _timeout);
@@ -233,7 +233,7 @@ final class EndpointI extends IceInternal.IPEndpointI
return h;
}
- protected void fillEndpointInfo(Ice.IPEndpointInfo info)
+ public void fillEndpointInfo(Ice.IPEndpointInfo info)
{
super.fillEndpointInfo(info);
if(info instanceof IceSSL.EndpointInfo)
diff --git a/java/src/IceWS/AcceptorI.java b/java/src/IceWS/AcceptorI.java
new file mode 100644
index 00000000000..171f0a2b8bb
--- /dev/null
+++ b/java/src/IceWS/AcceptorI.java
@@ -0,0 +1,56 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+final class AcceptorI implements IceInternal.Acceptor
+{
+ public java.nio.channels.ServerSocketChannel fd()
+ {
+ return _delegate.fd();
+ }
+
+ public void close()
+ {
+ _delegate.close();
+ }
+
+ public void listen()
+ {
+ _delegate.listen();
+ }
+
+ public IceInternal.Transceiver accept()
+ {
+ //
+ // WebSocket handshaking is performed in TransceiverI::initialize, since
+ // accept must not block.
+ //
+ return new TransceiverI(_instance, _delegate.accept());
+ }
+
+ public String protocol()
+ {
+ return _delegate.protocol();
+ }
+
+ public String toString()
+ {
+ return _delegate.toString();
+ }
+
+ AcceptorI(Instance instance, IceInternal.Acceptor del)
+ {
+ _instance = instance;
+ _delegate = del;
+ }
+
+ private Instance _instance;
+ private IceInternal.Acceptor _delegate;
+}
diff --git a/java/src/IceWS/ConnectorI.java b/java/src/IceWS/ConnectorI.java
new file mode 100644
index 00000000000..c558d92fac1
--- /dev/null
+++ b/java/src/IceWS/ConnectorI.java
@@ -0,0 +1,74 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+final class ConnectorI implements IceInternal.Connector
+{
+ public IceInternal.Transceiver connect()
+ {
+ return new TransceiverI(_instance, _delegate.connect(), _host, _port, _resource);
+ }
+
+ public short type()
+ {
+ return _delegate.type();
+ }
+
+ public String toString()
+ {
+ return _delegate.toString();
+ }
+
+ public int hashCode()
+ {
+ return _delegate.hashCode();
+ }
+
+ ConnectorI(Instance instance, IceInternal.Connector del, String host, int port, String resource)
+ {
+ _instance = instance;
+ _delegate = del;
+ _host = host;
+ _port = port;
+ _resource = resource;
+ }
+
+ public boolean equals(java.lang.Object obj)
+ {
+ if(!(obj instanceof ConnectorI))
+ {
+ return false;
+ }
+
+ if(this == obj)
+ {
+ return true;
+ }
+
+ ConnectorI p = (ConnectorI)obj;
+ if(!_delegate.equals(p._delegate))
+ {
+ return false;
+ }
+
+ if(!_resource.equals(p._resource))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private Instance _instance;
+ private IceInternal.Connector _delegate;
+ private String _host;
+ private int _port;
+ private String _resource;
+}
diff --git a/java/src/IceWS/EndpointFactoryI.java b/java/src/IceWS/EndpointFactoryI.java
new file mode 100644
index 00000000000..20d2395d133
--- /dev/null
+++ b/java/src/IceWS/EndpointFactoryI.java
@@ -0,0 +1,54 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+final class EndpointFactoryI implements IceInternal.EndpointFactory
+{
+ EndpointFactoryI(Instance instance, IceInternal.EndpointFactory delegate)
+ {
+ _instance = instance;
+ _delegate = delegate;
+ }
+
+ public short type()
+ {
+ return _instance.type();
+ }
+
+ public String protocol()
+ {
+ return _instance.protocol();
+ }
+
+ public IceInternal.EndpointI create(java.util.ArrayList<String> args, boolean oaEndpoint)
+ {
+ return new EndpointI(_instance, _delegate.create(args, oaEndpoint), args);
+ }
+
+ public IceInternal.EndpointI read(IceInternal.BasicStream s)
+ {
+ return new EndpointI(_instance, _delegate.read(s), s);
+ }
+
+ public void destroy()
+ {
+ _delegate.destroy();
+ _instance = null;
+ }
+
+ public IceInternal.EndpointFactory clone(IceInternal.ProtocolInstance instance)
+ {
+ assert(false); // We don't support cloning this transport.
+ return null;
+ }
+
+ private Instance _instance;
+ private IceInternal.EndpointFactory _delegate;
+}
diff --git a/java/src/IceWS/EndpointI.java b/java/src/IceWS/EndpointI.java
new file mode 100644
index 00000000000..3c9b62cee04
--- /dev/null
+++ b/java/src/IceWS/EndpointI.java
@@ -0,0 +1,302 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+final class EndpointI extends IceInternal.EndpointI
+{
+ public EndpointI(Instance instance, IceInternal.EndpointI del, String res)
+ {
+ _instance = instance;
+ _delegate = (IceInternal.IPEndpointI)del;
+ _resource = res;
+ }
+
+ public EndpointI(Instance instance, IceInternal.EndpointI del, java.util.ArrayList<String> args)
+ {
+ _instance = instance;
+ _delegate = (IceInternal.IPEndpointI)del;
+
+ initWithOptions(args);
+
+ if(_resource == null)
+ {
+ _resource = "/";
+ }
+ }
+
+ public EndpointI(Instance instance, IceInternal.EndpointI del, IceInternal.BasicStream s)
+ {
+ _instance = instance;
+ _delegate = (IceInternal.IPEndpointI)del;
+
+ _resource = s.readString();
+ }
+
+ public Ice.EndpointInfo getInfo()
+ {
+ IceWS.EndpointInfo info = new IceWS.EndpointInfo()
+ {
+ public short type()
+ {
+ return EndpointI.this.type();
+ }
+
+ public boolean datagram()
+ {
+ return EndpointI.this.datagram();
+ }
+
+ public boolean secure()
+ {
+ return EndpointI.this.secure();
+ }
+ };
+
+ info.timeout = _delegate.timeout();
+ info.compress = _delegate.compress();
+ _delegate.fillEndpointInfo(info);
+ info.resource = _resource;
+ return info;
+ }
+
+ public short type()
+ {
+ return _delegate.type();
+ }
+
+ public String protocol()
+ {
+ return _delegate.protocol();
+ }
+
+ public void streamWrite(IceInternal.BasicStream s)
+ {
+ s.startWriteEncaps();
+ _delegate.streamWriteImpl(s);
+ s.writeString(_resource);
+ s.endWriteEncaps();
+ }
+
+ public int timeout()
+ {
+ return _delegate.timeout();
+ }
+
+ public IceInternal.EndpointI timeout(int timeout)
+ {
+ if(timeout == _delegate.timeout())
+ {
+ return this;
+ }
+ else
+ {
+ return new EndpointI(_instance, _delegate.timeout(timeout), _resource);
+ }
+ }
+
+ public String connectionId()
+ {
+ return _delegate.connectionId();
+ }
+
+ public IceInternal.EndpointI connectionId(String connectionId)
+ {
+ if(connectionId.equals(_delegate.connectionId()))
+ {
+ return this;
+ }
+ else
+ {
+ return new EndpointI(_instance, _delegate.connectionId(connectionId), _resource);
+ }
+ }
+
+ public boolean compress()
+ {
+ return _delegate.compress();
+ }
+
+ public IceInternal.EndpointI compress(boolean compress)
+ {
+ if(compress == _delegate.compress())
+ {
+ return this;
+ }
+ else
+ {
+ return new EndpointI(_instance, _delegate.compress(compress), _resource);
+ }
+ }
+
+ public boolean datagram()
+ {
+ return _delegate.datagram();
+ }
+
+ public boolean secure()
+ {
+ return _delegate.secure();
+ }
+
+ public IceInternal.Transceiver transceiver(IceInternal.EndpointIHolder endpoint)
+ {
+ endpoint.value = this;
+ return null;
+ }
+
+ public java.util.List<IceInternal.Connector> connectors(Ice.EndpointSelectionType selType)
+ {
+ java.util.List<IceInternal.Connector> connectors = _delegate.connectors(selType);
+ java.util.List<IceInternal.Connector> l = new java.util.ArrayList<IceInternal.Connector>();
+ for(IceInternal.Connector c : connectors)
+ {
+ l.add(new ConnectorI(_instance, c, _delegate.host(), _delegate.port(), _resource));
+ }
+ return l;
+ }
+
+ public void connectors_async(Ice.EndpointSelectionType selType, final IceInternal.EndpointI_connectors callback)
+ {
+ IceInternal.EndpointI_connectors cb = new IceInternal.EndpointI_connectors()
+ {
+ public void connectors(java.util.List<IceInternal.Connector> connectors)
+ {
+ java.util.List<IceInternal.Connector> l = new java.util.ArrayList<IceInternal.Connector>();
+ for(IceInternal.Connector c : connectors)
+ {
+ l.add(new ConnectorI(_instance, c, _delegate.host(), _delegate.port(), _resource));
+ }
+ callback.connectors(l);
+ }
+
+ public void exception(Ice.LocalException ex)
+ {
+ callback.exception(ex);
+ }
+ };
+ _delegate.connectors_async(selType, cb);
+ }
+
+ public IceInternal.Acceptor acceptor(IceInternal.EndpointIHolder endpoint, String adapterName)
+ {
+ IceInternal.EndpointIHolder delEndp = new IceInternal.EndpointIHolder();
+ IceInternal.Acceptor delAcc = _delegate.acceptor(delEndp, adapterName);
+ if(delEndp.value != null)
+ {
+ endpoint.value = new EndpointI(_instance, delEndp.value, _resource);
+ }
+ return new AcceptorI(_instance, delAcc);
+ }
+
+ public java.util.List<IceInternal.EndpointI> expand()
+ {
+ java.util.List<IceInternal.EndpointI> endps = _delegate.expand();
+ java.util.List<IceInternal.EndpointI> l = new java.util.ArrayList<IceInternal.EndpointI>();
+ for(IceInternal.EndpointI e : endps)
+ {
+ l.add(e == _delegate ? this : new EndpointI(_instance, e, _resource));
+ }
+ return l;
+ }
+
+ public boolean equivalent(IceInternal.EndpointI endpoint)
+ {
+ if(!(endpoint instanceof EndpointI))
+ {
+ return false;
+ }
+ EndpointI wsEndpointI = (EndpointI)endpoint;
+ return _delegate.equivalent(wsEndpointI._delegate);
+ }
+
+ synchronized public int hashCode()
+ {
+ int h = _delegate.hashCode();
+ h = IceInternal.HashUtil.hashAdd(h, _resource);
+ return h;
+ }
+
+ public String options()
+ {
+ //
+ // WARNING: Certain features, such as proxy validation in Glacier2,
+ // depend on the format of proxy strings. Changes to toString() and
+ // methods called to generate parts of the reference string could break
+ // these features. Please review for all features that depend on the
+ // format of proxyToString() before changing this and related code.
+ //
+ String s = _delegate.options();
+
+ if(_resource != null && _resource.length() > 0)
+ {
+ s += " -r ";
+ boolean addQuote = _resource.indexOf(':') != -1;
+ if(addQuote)
+ {
+ s += "\"";
+ }
+ s += _resource;
+ if(addQuote)
+ {
+ s += "\"";
+ }
+ }
+
+ return s;
+ }
+
+ public int compareTo(IceInternal.EndpointI obj) // From java.lang.Comparable
+ {
+ if(!(obj instanceof EndpointI))
+ {
+ return type() < obj.type() ? -1 : 1;
+ }
+
+ EndpointI p = (EndpointI)obj;
+ if(this == p)
+ {
+ return 0;
+ }
+
+ int v = _resource.compareTo(p._resource);
+ if(v != 0)
+ {
+ return v;
+ }
+
+ return _delegate.compareTo(p._delegate);
+ }
+
+ protected boolean checkOption(String option, String argument, String endpoint)
+ {
+ switch(option.charAt(1))
+ {
+ case 'r':
+ {
+ if(argument == null)
+ {
+ throw new Ice.EndpointParseException("no argument provided for -r option in endpoint " + endpoint +
+ _delegate.options());
+ }
+ _resource = argument;
+ return true;
+ }
+
+ default:
+ {
+ return false;
+ }
+ }
+ }
+
+ private Instance _instance;
+ private IceInternal.IPEndpointI _delegate;
+ private String _resource;
+}
diff --git a/java/src/IceWS/HttpParser.java b/java/src/IceWS/HttpParser.java
new file mode 100644
index 00000000000..7e6e3981e39
--- /dev/null
+++ b/java/src/IceWS/HttpParser.java
@@ -0,0 +1,735 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+final class HttpParser
+{
+ HttpParser()
+ {
+ _type = Type.Unknown;
+ _versionMajor = 0;
+ _versionMinor = 0;
+ _status = 0;
+ _state = State.Init;
+ }
+
+ enum Type
+ {
+ Unknown,
+ Request,
+ Response
+ };
+
+ int isCompleteMessage(java.nio.ByteBuffer buf, int begin, int end)
+ {
+ int p = begin;
+
+ //
+ // Skip any leading CR-LF characters.
+ //
+ while(p < end)
+ {
+ byte ch = buf.get(p);
+ if(ch != (byte)'\r' && ch != (byte)'\n')
+ {
+ break;
+ }
+ ++p;
+ }
+
+ //
+ // Look for adjacent CR-LF/CR-LF or LF/LF.
+ //
+ boolean seenFirst = false;
+ while(p < end)
+ {
+ byte ch = buf.get(p++);
+ if(ch == (byte)'\n')
+ {
+ if(seenFirst)
+ {
+ return p;
+ }
+ else
+ {
+ seenFirst = true;
+ }
+ }
+ else if(ch != (byte)'\r')
+ {
+ seenFirst = false;
+ }
+ }
+
+ return -1;
+ }
+
+ boolean parse(java.nio.ByteBuffer buf, int begin, int end)
+ {
+ int p = begin;
+ int start = 0;
+ final char CR = '\r';
+ final char LF = '\n';
+
+ if(_state == State.Complete)
+ {
+ _state = State.Init;
+ }
+
+ while(p != end && _state != State.Complete)
+ {
+ char c = (char)buf.get(p);
+
+ switch(_state)
+ {
+ case Init:
+ {
+ _method.setLength(0);
+ _uri.setLength(0);
+ _versionMajor = -1;
+ _versionMinor = -1;
+ _status = -1;
+ _reason = "";
+ _headers.clear();
+ _state = State.Type;
+ continue;
+ }
+ case Type:
+ {
+ if(c == CR || c == LF)
+ {
+ break;
+ }
+ else if(c == 'H')
+ {
+ //
+ // Could be the start of "HTTP/1.1" or "HEAD".
+ //
+ _state = State.TypeCheck;
+ break;
+ }
+ else
+ {
+ _state = State.Request;
+ continue;
+ }
+ }
+ case TypeCheck:
+ {
+ if(c == 'T') // Continuing "H_T_TP/1.1"
+ {
+ _state = State.Response;
+ }
+ else if(c == 'E') // Expecting "HEAD"
+ {
+ _state = State.Request;
+ _method.append('H');
+ _method.append('E');
+ }
+ else
+ {
+ throw new WebSocketException("malformed request or response");
+ }
+ break;
+ }
+ case Request:
+ {
+ _type = Type.Request;
+ _state = State.RequestMethod;
+ continue;
+ }
+ case RequestMethod:
+ {
+ if(c == ' ' || c == CR || c == LF)
+ {
+ _state = State.RequestMethodSP;
+ continue;
+ }
+ _method.append(c);
+ break;
+ }
+ case RequestMethodSP:
+ {
+ if(c == ' ')
+ {
+ break;
+ }
+ else if(c == CR || c == LF)
+ {
+ throw new WebSocketException("malformed request");
+ }
+ _state = State.RequestURI;
+ continue;
+ }
+ case RequestURI:
+ {
+ if(c == ' ' || c == CR || c == LF)
+ {
+ _state = State.RequestURISP;
+ continue;
+ }
+ _uri.append(c);
+ break;
+ }
+ case RequestURISP:
+ {
+ if(c == ' ')
+ {
+ break;
+ }
+ else if(c == CR || c == LF)
+ {
+ throw new WebSocketException("malformed request");
+ }
+ _state = State.Version;
+ continue;
+ }
+ case RequestLF:
+ {
+ if(c != LF)
+ {
+ throw new WebSocketException("malformed request");
+ }
+ _state = State.HeaderFieldStart;
+ break;
+ }
+ case HeaderFieldStart:
+ {
+ //
+ // We've already seen a LF to reach this state.
+ //
+ // Another CR or LF indicates the end of the header fields.
+ //
+ if(c == CR)
+ {
+ _state = State.HeaderFieldEndLF;
+ break;
+ }
+ else if(c == LF)
+ {
+ _state = State.Complete;
+ break;
+ }
+ else if(c == ' ')
+ {
+ //
+ // Could be a continuation line.
+ //
+ _state = State.HeaderFieldContStart;
+ break;
+ }
+
+ _state = State.HeaderFieldNameStart;
+ continue;
+ }
+ case HeaderFieldContStart:
+ {
+ if(c == ' ')
+ {
+ break;
+ }
+
+ _state = State.HeaderFieldCont;
+ start = p;
+ continue;
+ }
+ case HeaderFieldCont:
+ {
+ if(c == CR || c == LF)
+ {
+ if(p > start)
+ {
+ if(_headerName.length() == 0)
+ {
+ throw new WebSocketException("malformed header");
+ }
+ String s = _headers.get(_headerName);
+ assert(s != null);
+ StringBuffer newValue = new StringBuffer(s);
+ newValue.append(' ');
+ for(int i = start; i < p; ++i)
+ {
+ newValue.append((char)buf.get(i));
+ }
+ _headers.put(_headerName, newValue.toString());
+ _state = c == CR ? State.HeaderFieldLF : State.HeaderFieldStart;
+ }
+ else
+ {
+ //
+ // Could mark the end of the header fields.
+ //
+ _state = c == CR ? State.HeaderFieldEndLF : State.Complete;
+ }
+ }
+
+ break;
+ }
+ case HeaderFieldNameStart:
+ {
+ assert(c != ' ');
+ start = p;
+ _headerName = "";
+ _state = State.HeaderFieldName;
+ continue;
+ }
+ case HeaderFieldName:
+ {
+ if(c == ' ' || c == ':')
+ {
+ _state = State.HeaderFieldNameEnd;
+ continue;
+ }
+ else if(c == CR || c == LF)
+ {
+ throw new WebSocketException("malformed header");
+ }
+ break;
+ }
+ case HeaderFieldNameEnd:
+ {
+ if(_headerName.length() == 0)
+ {
+ StringBuffer str = new StringBuffer();
+ for(int i = start; i < p; ++i)
+ {
+ str.append((char)buf.get(i));
+ }
+ _headerName = str.toString().toLowerCase();
+ //
+ // Add a placeholder entry if necessary.
+ //
+ if(!_headers.containsKey(_headerName))
+ {
+ _headers.put(_headerName, "");
+ }
+ }
+
+ if(c == ' ')
+ {
+ break;
+ }
+ else if(c != ':' || p == start)
+ {
+ throw new WebSocketException("malformed header");
+ }
+
+ _state = State.HeaderFieldValueStart;
+ break;
+ }
+ case HeaderFieldValueStart:
+ {
+ if(c == ' ')
+ {
+ break;
+ }
+
+ //
+ // Check for "Name:\r\n"
+ //
+ if(c == CR)
+ {
+ _state = State.HeaderFieldLF;
+ break;
+ }
+ else if(c == LF)
+ {
+ _state = State.HeaderFieldStart;
+ break;
+ }
+
+ start = p;
+ _state = State.HeaderFieldValue;
+ continue;
+ }
+ case HeaderFieldValue:
+ {
+ if(c == CR || c == LF)
+ {
+ _state = State.HeaderFieldValueEnd;
+ continue;
+ }
+ break;
+ }
+ case HeaderFieldValueEnd:
+ {
+ assert(c == CR || c == LF);
+ if(p > start)
+ {
+ StringBuffer str = new StringBuffer();
+ for(int i = start; i < p; ++i)
+ {
+ str.append((char)buf.get(i));
+ }
+ String s = _headers.get(_headerName);
+ if(s == null || s.length() == 0)
+ {
+ _headers.put(_headerName, str.toString());
+ }
+ else
+ {
+ _headers.put(_headerName, s + ", " + str.toString());
+ }
+ }
+
+ if(c == CR)
+ {
+ _state = State.HeaderFieldLF;
+ }
+ else
+ {
+ _state = State.HeaderFieldStart;
+ }
+ break;
+ }
+ case HeaderFieldLF:
+ {
+ if(c != LF)
+ {
+ throw new WebSocketException("malformed header");
+ }
+ _state = State.HeaderFieldStart;
+ break;
+ }
+ case HeaderFieldEndLF:
+ {
+ if(c != LF)
+ {
+ throw new WebSocketException("malformed header");
+ }
+ _state = State.Complete;
+ break;
+ }
+ case Version:
+ {
+ if(c != 'H')
+ {
+ throw new WebSocketException("malformed version");
+ }
+ _state = State.VersionH;
+ break;
+ }
+ case VersionH:
+ {
+ if(c != 'T')
+ {
+ throw new WebSocketException("malformed version");
+ }
+ _state = State.VersionHT;
+ break;
+ }
+ case VersionHT:
+ {
+ if(c != 'T')
+ {
+ throw new WebSocketException("malformed version");
+ }
+ _state = State.VersionHTT;
+ break;
+ }
+ case VersionHTT:
+ {
+ if(c != 'P')
+ {
+ throw new WebSocketException("malformed version");
+ }
+ _state = State.VersionHTTP;
+ break;
+ }
+ case VersionHTTP:
+ {
+ if(c != '/')
+ {
+ throw new WebSocketException("malformed version");
+ }
+ _state = State.VersionMajor;
+ break;
+ }
+ case VersionMajor:
+ {
+ if(c == '.')
+ {
+ if(_versionMajor == -1)
+ {
+ throw new WebSocketException("malformed version");
+ }
+ _state = State.VersionMinor;
+ break;
+ }
+ else if(c < '0' || c > '9')
+ {
+ throw new WebSocketException("malformed version");
+ }
+ if(_versionMajor == -1)
+ {
+ _versionMajor = 0;
+ }
+ _versionMajor *= 10;
+ _versionMajor += (int)(c - '0');
+ break;
+ }
+ case VersionMinor:
+ {
+ if(c == CR)
+ {
+ if(_versionMinor == -1 || _type != Type.Request)
+ {
+ throw new WebSocketException("malformed version");
+ }
+ _state = State.RequestLF;
+ break;
+ }
+ else if(c == LF)
+ {
+ if(_versionMinor == -1 || _type != Type.Request)
+ {
+ throw new WebSocketException("malformed version");
+ }
+ _state = State.HeaderFieldStart;
+ break;
+ }
+ else if(c == ' ')
+ {
+ if(_versionMinor == -1 || _type != Type.Response)
+ {
+ throw new WebSocketException("malformed version");
+ }
+ _state = State.ResponseVersionSP;
+ break;
+ }
+ else if(c < '0' || c > '9')
+ {
+ throw new WebSocketException("malformed version");
+ }
+ if(_versionMinor == -1)
+ {
+ _versionMinor = 0;
+ }
+ _versionMinor *= 10;
+ _versionMinor += (int)(c - '0');
+ break;
+ }
+ case Response:
+ {
+ _type = Type.Response;
+ _state = State.VersionHT;
+ continue;
+ }
+ case ResponseVersionSP:
+ {
+ if(c == ' ')
+ {
+ break;
+ }
+
+ _state = State.ResponseStatus;
+ continue;
+ }
+ case ResponseStatus:
+ {
+ // TODO: Is reason string optional?
+ if(c == CR)
+ {
+ if(_status == -1)
+ {
+ throw new WebSocketException("malformed response status");
+ }
+ _state = State.ResponseLF;
+ break;
+ }
+ else if(c == LF)
+ {
+ if(_status == -1)
+ {
+ throw new WebSocketException("malformed response status");
+ }
+ _state = State.HeaderFieldStart;
+ break;
+ }
+ else if(c == ' ')
+ {
+ if(_status == -1)
+ {
+ throw new WebSocketException("malformed response status");
+ }
+ _state = State.ResponseReasonStart;
+ break;
+ }
+ else if(c < '0' || c > '9')
+ {
+ throw new WebSocketException("malformed response status");
+ }
+ if(_status == -1)
+ {
+ _status = 0;
+ }
+ _status *= 10;
+ _status += (int)(c - '0');
+ break;
+ }
+ case ResponseReasonStart:
+ {
+ //
+ // Skip leading spaces.
+ //
+ if(c == ' ')
+ {
+ break;
+ }
+
+ _state = State.ResponseReason;
+ start = p;
+ continue;
+ }
+ case ResponseReason:
+ {
+ if(c == CR || c == LF)
+ {
+ if(p > start)
+ {
+ StringBuffer str = new StringBuffer();
+ for(int i = start; i < p; ++i)
+ {
+ str.append((char)buf.get(i));
+ }
+ _reason = str.toString();
+ }
+ _state = c == CR ? State.ResponseLF : State.HeaderFieldStart;
+ }
+
+ break;
+ }
+ case ResponseLF:
+ {
+ if(c != LF)
+ {
+ throw new WebSocketException("malformed status line");
+ }
+ _state = State.HeaderFieldStart;
+ break;
+ }
+ case Complete:
+ {
+ assert(false); // Shouldn't reach
+ }
+ }
+
+ ++p;
+ }
+
+ return _state == State.Complete;
+ }
+
+ Type type()
+ {
+ return _type;
+ }
+
+ String method()
+ {
+ assert(_type == Type.Request);
+ return _method.toString();
+ }
+
+ String uri()
+ {
+ assert(_type == Type.Request);
+ return _uri.toString();
+ }
+
+ int versionMajor()
+ {
+ return _versionMajor;
+ }
+
+ int versionMinor()
+ {
+ return _versionMinor;
+ }
+
+ int status()
+ {
+ return _status;
+ }
+
+ String reason()
+ {
+ return _reason;
+ }
+
+ String getHeader(String name, boolean toLower)
+ {
+ String s = _headers.get(name.toLowerCase());
+ if(s != null)
+ {
+ return toLower ? s.trim().toLowerCase() : s.trim();
+ }
+
+ return null;
+ }
+
+ java.util.Map<String, String> headers()
+ {
+ return _headers;
+ }
+
+ private Type _type;
+
+ private StringBuffer _method = new StringBuffer();
+ private StringBuffer _uri = new StringBuffer();
+
+ private java.util.Map<String, String> _headers = new java.util.HashMap<String, String>();
+ private String _headerName = "";
+
+ private int _versionMajor;
+ private int _versionMinor;
+
+ private int _status;
+ private String _reason;
+
+ enum State
+ {
+ Init,
+ Type,
+ TypeCheck,
+ Request,
+ RequestMethod,
+ RequestMethodSP,
+ RequestURI,
+ RequestURISP,
+ RequestLF,
+ HeaderFieldStart,
+ HeaderFieldContStart,
+ HeaderFieldCont,
+ HeaderFieldNameStart,
+ HeaderFieldName,
+ HeaderFieldNameEnd,
+ HeaderFieldValueStart,
+ HeaderFieldValue,
+ HeaderFieldValueEnd,
+ HeaderFieldLF,
+ HeaderFieldEndLF,
+ Version,
+ VersionH,
+ VersionHT,
+ VersionHTT,
+ VersionHTTP,
+ VersionMajor,
+ VersionMinor,
+ Response,
+ ResponseVersionSP,
+ ResponseStatus,
+ ResponseReasonStart,
+ ResponseReason,
+ ResponseLF,
+ Complete
+ };
+ private State _state;
+}
diff --git a/java/src/IceWS/Instance.java b/java/src/IceWS/Instance.java
new file mode 100644
index 00000000000..621b9d0d67e
--- /dev/null
+++ b/java/src/IceWS/Instance.java
@@ -0,0 +1,18 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+class Instance extends IceInternal.ProtocolInstance
+{
+ Instance(Ice.Communicator communicator, short type, String protocol)
+ {
+ super(communicator, type, protocol);
+ }
+}
diff --git a/java/src/IceWS/PluginFactory.java b/java/src/IceWS/PluginFactory.java
new file mode 100644
index 00000000000..919d822a346
--- /dev/null
+++ b/java/src/IceWS/PluginFactory.java
@@ -0,0 +1,32 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+/**
+ * Plug-in factories must implement this interface.
+ **/
+public class PluginFactory implements Ice.PluginFactory
+{
+ /**
+ * Returns a new plug-in.
+ *
+ * @param communicator The communicator for the plug-in.
+ * @param name The name of the plug-in.
+ * @param args The arguments that are specified in the plug-in's configuration.
+ *
+ * @return The new plug-in. <code>null</code> can be returned to indicate
+ * that a general error occurred. Alternatively, <code>create</code> can throw
+ * {@link PluginInitializationException} to provide more detailed information.
+ **/
+ public Ice.Plugin create(Ice.Communicator communicator, String name, String[] args)
+ {
+ return new PluginI(communicator);
+ }
+}
diff --git a/java/src/IceWS/PluginI.java b/java/src/IceWS/PluginI.java
new file mode 100644
index 00000000000..ee1e46141ed
--- /dev/null
+++ b/java/src/IceWS/PluginI.java
@@ -0,0 +1,38 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+class PluginI implements Ice.Plugin
+{
+ public PluginI(Ice.Communicator communicator)
+ {
+ IceInternal.ProtocolPluginFacade facade = IceInternal.Util.getProtocolPluginFacade(communicator);
+ IceInternal.EndpointFactory tcpFactory = facade.getEndpointFactory(Ice.TCPEndpointType.value);
+ if(tcpFactory != null)
+ {
+ Instance tcpInstance = new Instance(communicator, WSEndpointType.value, "ws");
+ facade.addEndpointFactory(new EndpointFactoryI(tcpInstance, tcpFactory.clone(tcpInstance)));
+ }
+ IceInternal.EndpointFactory sslFactory = facade.getEndpointFactory((short)2); // 2 = SSLEndpointType
+ if(sslFactory != null)
+ {
+ Instance sslInstance = new Instance(communicator, WSSEndpointType.value, "wss");
+ facade.addEndpointFactory(new EndpointFactoryI(sslInstance, sslFactory.clone(sslInstance)));
+ }
+ }
+
+ public void initialize()
+ {
+ }
+
+ public void destroy()
+ {
+ }
+}
diff --git a/java/src/IceWS/TransceiverI.java b/java/src/IceWS/TransceiverI.java
new file mode 100644
index 00000000000..fff5940023b
--- /dev/null
+++ b/java/src/IceWS/TransceiverI.java
@@ -0,0 +1,1502 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+import java.nio.*;
+import java.security.*;
+
+final class TransceiverI implements IceInternal.Transceiver
+{
+ public java.nio.channels.SelectableChannel fd()
+ {
+ return _delegate.fd();
+ }
+
+ public int initialize(IceInternal.Buffer readBuffer, IceInternal.Buffer writeBuffer, Ice.BooleanHolder moreData)
+ {
+ //
+ // Delegate logs exceptions that occur during initialize(), so there's no need to trap them here.
+ //
+ if(_state == StateInitializeDelegate)
+ {
+ int op = _delegate.initialize(readBuffer, writeBuffer, moreData);
+ if(op != 0)
+ {
+ return op;
+ }
+ _state = StateConnected;
+ }
+
+ try
+ {
+ if(_state == StateConnected)
+ {
+ //
+ // We don't know how much we'll need to read.
+ //
+ _readBuffer.resize(1024, true);
+ _readBuffer.b.position(0);
+ _readBufferPos = 0;
+
+ //
+ // The server waits for the client's upgrade request, the
+ // client sends the upgrade request.
+ //
+ _state = StateUpgradeRequestPending;
+ if(!_incoming)
+ {
+ //
+ // Compose the upgrade request.
+ //
+ StringBuffer out = new StringBuffer();
+ out.append("GET " + _resource + " HTTP/1.1\r\n");
+ out.append("Host: " + _host + ":");
+ out.append(_port);
+ out.append("\r\n");
+ out.append("Upgrade: websocket\r\n");
+ out.append("Connection: Upgrade\r\n");
+ out.append("Sec-WebSocket-Protocol: " + _iceProtocol + "\r\n");
+ out.append("Sec-WebSocket-Version: 13\r\n");
+ out.append("Sec-WebSocket-Key: ");
+
+ //
+ // The value for Sec-WebSocket-Key is a 16-byte random number,
+ // encoded with Base64.
+ //
+ byte[] key = new byte[16];
+ _rand.nextBytes(key);
+ _key = IceUtilInternal.Base64.encode(key);
+ out.append(_key + "\r\n\r\n"); // EOM
+
+ _writeBuffer.resize(out.length(), false);
+ _writeBuffer.b.position(0);
+ _writeBuffer.b.put(out.toString().getBytes(_ascii));
+ _writeBuffer.b.flip();
+ }
+ }
+
+ //
+ // Try to write the client's upgrade request.
+ //
+ if(_state == StateUpgradeRequestPending && !_incoming)
+ {
+ if(_writeBuffer.b.hasRemaining())
+ {
+ int s = _delegate.write(_writeBuffer);
+ if(s != 0)
+ {
+ return s;
+ }
+ }
+ assert(!_writeBuffer.b.hasRemaining());
+ _state = StateUpgradeResponsePending;
+ }
+
+ while(true)
+ {
+ if(_readBuffer.b.hasRemaining())
+ {
+ int s = _delegate.read(_readBuffer, moreData);
+ if(s == IceInternal.SocketOperation.Write || _readBuffer.b.position() == 0)
+ {
+ return s;
+ }
+ }
+
+ //
+ // Try to read the client's upgrade request or the server's response.
+ //
+ if((_state == StateUpgradeRequestPending && _incoming) ||
+ (_state == StateUpgradeResponsePending && !_incoming))
+ {
+ //
+ // Check if we have enough data for a complete message.
+ //
+ int p = _parser.isCompleteMessage(_readBuffer.b, 0, _readBuffer.b.position());
+ if(p == -1)
+ {
+ if(_readBuffer.b.hasRemaining())
+ {
+ return IceInternal.SocketOperation.Read;
+ }
+
+ //
+ // Enlarge the buffer and try to read more.
+ //
+ final int oldSize = _readBuffer.b.position();
+ if(oldSize + 1024 > _instance.messageSizeMax())
+ {
+ throw new Ice.MemoryLimitException();
+ }
+ _readBuffer.resize(oldSize + 1024, true);
+ _readBuffer.b.position(oldSize);
+ continue; // Try again to read the response/request
+ }
+
+ //
+ // Set _readBufferPos at the end of the response/request message.
+ //
+ _readBufferPos = p;
+ }
+
+ //
+ // We're done, the client's upgrade request or server's response is read.
+ //
+ break;
+ }
+
+ try
+ {
+ //
+ // Parse the client's upgrade request.
+ //
+ if(_state == StateUpgradeRequestPending && _incoming)
+ {
+ if(_parser.parse(_readBuffer.b, 0, _readBufferPos))
+ {
+ handleRequest(_writeBuffer);
+ _state = StateUpgradeResponsePending;
+ }
+ else
+ {
+ throw new Ice.ProtocolException("incomplete request message");
+ }
+ }
+
+ if(_state == StateUpgradeResponsePending)
+ {
+ if(_incoming)
+ {
+ if(_writeBuffer.b.hasRemaining())
+ {
+ int s = _delegate.write(_writeBuffer);
+ if(s != 0)
+ {
+ return s;
+ }
+ }
+ }
+ else
+ {
+ //
+ // Parse the server's response
+ //
+ if(_parser.parse(_readBuffer.b, 0, _readBufferPos))
+ {
+ handleResponse();
+ }
+ else
+ {
+ throw new Ice.ProtocolException("incomplete response message");
+ }
+ }
+ }
+ }
+ catch(WebSocketException ex)
+ {
+ throw new Ice.ProtocolException(ex.getMessage());
+ }
+
+ _state = StateOpened;
+ _nextState = StateOpened;
+
+ moreData.value = _readBufferPos < _readBuffer.b.position();
+ }
+ catch(Ice.LocalException ex)
+ {
+ if(_instance.traceLevel() >= 2)
+ {
+ _instance.logger().trace(_instance.traceCategory(),
+ protocol() + " connection HTTP upgrade request failed\n" + toString() + "\n" + ex);
+ }
+ throw ex;
+ }
+
+ if(_instance.traceLevel() >= 1)
+ {
+ if(_incoming)
+ {
+ _instance.logger().trace(_instance.traceCategory(),
+ "accepted " + protocol() + " connection HTTP upgrade request\n" + toString());
+ }
+ else
+ {
+ _instance.logger().trace(_instance.traceCategory(),
+ protocol() + " connection HTTP upgrade request accepted\n" + toString());
+ }
+ }
+
+ return IceInternal.SocketOperation.None;
+ }
+
+ public int closing(boolean initiator, Ice.LocalException reason)
+ {
+ if(_instance.traceLevel() >= 1)
+ {
+ _instance.logger().trace(_instance.traceCategory(),
+ "gracefully closing " + protocol() + " connection\n" + toString());
+ }
+
+ int s = _nextState == StateOpened ? _state : _nextState;
+
+ if(s == StateClosingRequestPending && _closingInitiator)
+ {
+ //
+ // If we initiated a close connection but also received a
+ // close connection, we assume we didn't initiated the
+ // connection and we send the close frame now. This is to
+ // ensure that if both peers close the connection at the same
+ // time we don't hang having both peer waiting for the close
+ // frame of the other.
+ //
+ assert(!initiator);
+ _closingInitiator = false;
+ return IceInternal.SocketOperation.Write;
+ }
+ else if(s >= StateClosingRequestPending)
+ {
+ return IceInternal.SocketOperation.None;
+ }
+
+ _closingInitiator = initiator;
+ if(reason instanceof Ice.CloseConnectionException)
+ {
+ _closingReason = CLOSURE_NORMAL;
+ }
+ else if(reason instanceof Ice.ObjectAdapterDeactivatedException ||
+ reason instanceof Ice.CommunicatorDestroyedException)
+ {
+ _closingReason = CLOSURE_SHUTDOWN;
+ }
+ else if(reason instanceof Ice.ProtocolException)
+ {
+ _closingReason = CLOSURE_PROTOCOL_ERROR;
+ }
+ else if(reason instanceof Ice.MemoryLimitException)
+ {
+ _closingReason = CLOSURE_TOO_BIG;
+ }
+
+ if(_state == StateOpened)
+ {
+ _state = StateClosingRequestPending;
+ return initiator ? IceInternal.SocketOperation.Read : IceInternal.SocketOperation.Write;
+ }
+ else
+ {
+ _nextState = StateClosingRequestPending;
+ return IceInternal.SocketOperation.None;
+ }
+ }
+
+ public void close()
+ {
+ _delegate.close();
+ _state = StateClosed;
+ }
+
+ public int write(IceInternal.Buffer buf)
+ {
+ if(_writePending)
+ {
+ return IceInternal.SocketOperation.Write;
+ }
+
+ if(_state < StateOpened)
+ {
+ if(_state < StateConnected)
+ {
+ return _delegate.write(buf);
+ }
+ else
+ {
+ return _delegate.write(_writeBuffer);
+ }
+ }
+
+ int s = IceInternal.SocketOperation.None;
+ do
+ {
+ if(preWrite(buf))
+ {
+ if(_writeState == WriteStateFlush)
+ {
+ //
+ // Invoke write() even though there's nothing to write.
+ //
+ assert(!buf.b.hasRemaining());
+ s = _delegate.write(buf);
+ }
+
+ if(s == IceInternal.SocketOperation.None && _writeBuffer.b.hasRemaining())
+ {
+ s = _delegate.write(_writeBuffer);
+ }
+
+ if(s == IceInternal.SocketOperation.None && _incoming && !buf.empty() &&
+ _writeState == WriteStatePayload)
+ {
+ s = _delegate.write(buf);
+ }
+ }
+ }
+ while(postWrite(buf, s));
+
+ if(s != IceInternal.SocketOperation.None)
+ {
+ return s;
+ }
+ if(_state == StateClosingResponsePending && !_closingInitiator)
+ {
+ return IceInternal.SocketOperation.Read;
+ }
+ return IceInternal.SocketOperation.None;
+ }
+
+ @SuppressWarnings("deprecation")
+ public int read(IceInternal.Buffer buf, Ice.BooleanHolder moreData)
+ {
+ if(_readPending)
+ {
+ return IceInternal.SocketOperation.Read;
+ }
+
+ if(_state < StateOpened)
+ {
+ if(_state < StateConnected)
+ {
+ return _delegate.read(buf, moreData);
+ }
+ else
+ {
+ if(_delegate.read(_readBuffer, moreData) == IceInternal.SocketOperation.Write)
+ {
+ return IceInternal.SocketOperation.Write;
+ }
+ else
+ {
+ return IceInternal.SocketOperation.None;
+ }
+ }
+ }
+
+ int s = IceInternal.SocketOperation.None;
+ do
+ {
+ if(preRead(buf))
+ {
+ if(_readState == ReadStatePayload)
+ {
+ s = _delegate.read(buf, moreData);
+ }
+ else
+ {
+ s = _delegate.read(_readBuffer, moreData);
+ }
+
+ if(s == IceInternal.SocketOperation.Write)
+ {
+ postRead(buf);
+ return s;
+ }
+ }
+ }
+ while(postRead(buf));
+
+ moreData.value = _readBufferPos < _readBuffer.b.position() || moreData.value;
+
+ s = !buf.b.hasRemaining() ? IceInternal.SocketOperation.None : IceInternal.SocketOperation.Read;
+
+ if(((_state == StateClosingRequestPending && !_closingInitiator) ||
+ (_state == StateClosingResponsePending && _closingInitiator) ||
+ _state == StatePingPending ||
+ _state == StatePongPending) &&
+ _writeState == WriteStateHeader)
+ {
+ // We have things to write, ask to be notified when writes are ready.
+ s |= IceInternal.SocketOperation.Write;
+ }
+
+ return s;
+ }
+
+ public String protocol()
+ {
+ return _instance.protocol();
+ }
+
+ public String toString()
+ {
+ return _delegate.toString();
+ }
+
+ public Ice.ConnectionInfo getInfo()
+ {
+ Ice.IPConnectionInfo di = (Ice.IPConnectionInfo)_delegate.getInfo();
+ IceWS.ConnectionInfo info = new IceWS.ConnectionInfo();
+ info.localAddress = di.localAddress;
+ info.localPort = di.localPort;
+ info.remoteAddress = di.remoteAddress;
+ info.remotePort = di.remotePort;
+ return info;
+ }
+
+ public void checkSendSize(IceInternal.Buffer buf, int messageSizeMax)
+ {
+ _delegate.checkSendSize(buf, messageSizeMax);
+ }
+
+ TransceiverI(Instance instance, IceInternal.Transceiver del, String host, int port, String resource)
+ {
+ init(instance, del);
+ _host = host;
+ _port = port;
+ _resource = resource;
+ _incoming = false;
+
+ //
+ // For client connections, the sent frame payload must be
+ // masked. So we copy and send the message buffer data in chuncks
+ // of data whose size is up to the write buffer size.
+ //
+ java.nio.channels.SocketChannel channel = (java.nio.channels.SocketChannel)del.fd();
+ _writeBufferSize = Math.max(IceInternal.Network.getSendBufferSize(channel), 1024);
+
+ //
+ // Write and read buffer size must be large enough to hold the frame header!
+ //
+ assert(_writeBufferSize > 256);
+ assert(_readBufferSize > 256);
+ }
+
+ TransceiverI(Instance instance, IceInternal.Transceiver del)
+ {
+ init(instance, del);
+ _host = "";
+ _port = -1;
+ _resource = "";
+ _incoming = true;
+
+ //
+ // Write and read buffer size must be large enough to hold the frame header!
+ //
+ assert(_writeBufferSize > 256);
+ assert(_readBufferSize > 256);
+ }
+
+ @SuppressWarnings("deprecation")
+ private void init(Instance instance, IceInternal.Transceiver del)
+ {
+ _instance = instance;
+ _delegate = del;
+ _state = StateInitializeDelegate;
+ _parser = new HttpParser();
+ _readState = ReadStateOpcode;
+ _readBuffer = new IceInternal.Buffer(0, false, java.nio.ByteOrder.BIG_ENDIAN); // Use network byte order.
+ _readBufferSize = 1024;
+ _readLastFrame = false;
+ _readOpCode = 0;
+ _readHeaderLength = 0;
+ _readPayloadLength = 0;
+ _readMask = new byte[4];
+ _writeState = WriteStateHeader;
+ _writeBuffer = new IceInternal.Buffer(0, false, java.nio.ByteOrder.BIG_ENDIAN); // Use network byte order.
+ _writeBufferSize = 1024;
+ _readPending = false;
+ _writePending = false;
+ _readMask = new byte[4];
+ _writeMask = new byte[4];
+ _key = "";
+ _pingPayload = new byte[0];
+ _rand = new java.util.Random();
+ }
+
+ private void handleRequest(IceInternal.Buffer responseBuffer)
+ {
+ //
+ // HTTP/1.1
+ //
+ if(_parser.versionMajor() != 1 || _parser.versionMinor() != 1)
+ {
+ throw new WebSocketException("unsupported HTTP version");
+ }
+
+ //
+ // "An |Upgrade| header field containing the value 'websocket',
+ // treated as an ASCII case-insensitive value."
+ //
+ String val = _parser.getHeader("Upgrade", true);
+ if(val == null)
+ {
+ throw new WebSocketException("missing value for Upgrade field");
+ }
+ else if(!val.equals("websocket"))
+ {
+ throw new WebSocketException("invalid value `" + val + "' for Upgrade field");
+ }
+
+ //
+ // "A |Connection| header field that includes the token 'Upgrade',
+ // treated as an ASCII case-insensitive value.
+ //
+ val = _parser.getHeader("Connection", true);
+ if(val == null)
+ {
+ throw new WebSocketException("missing value for Connection field");
+ }
+ else if(val.indexOf("upgrade") == -1)
+ {
+ throw new WebSocketException("invalid value `" + val + "' for Connection field");
+ }
+
+ //
+ // "A |Sec-WebSocket-Version| header field, with a value of 13."
+ //
+ val = _parser.getHeader("Sec-WebSocket-Version", false);
+ if(val == null)
+ {
+ throw new WebSocketException("missing value for WebSocket version");
+ }
+ else if(!val.equals("13"))
+ {
+ throw new WebSocketException("unsupported WebSocket version `" + val + "'");
+ }
+
+ //
+ // "Optionally, a |Sec-WebSocket-Protocol| header field, with a list
+ // of values indicating which protocols the client would like to
+ // speak, ordered by preference."
+ //
+ boolean addProtocol = false;
+ val = _parser.getHeader("Sec-WebSocket-Protocol", true);
+ if(val != null)
+ {
+ String[] protocols = IceUtilInternal.StringUtil.splitString(val, ",");
+ if(protocols == null)
+ {
+ throw new WebSocketException("invalid value `" + val + "' for WebSocket protocol");
+ }
+ for(String p : protocols)
+ {
+ if(!p.trim().equals(_iceProtocol))
+ {
+ throw new WebSocketException("unknown value `" + p + "' for WebSocket protocol");
+ }
+ addProtocol = true;
+ }
+ }
+
+ //
+ // "A |Sec-WebSocket-Key| header field with a base64-encoded
+ // value that, when decoded, is 16 bytes in length."
+ //
+ String key = _parser.getHeader("Sec-WebSocket-Key", false);
+ if(key == null)
+ {
+ throw new WebSocketException("missing value for WebSocket key");
+ }
+
+ byte[] decodedKey = IceUtilInternal.Base64.decode(key);
+ if(decodedKey.length != 16)
+ {
+ throw new WebSocketException("invalid value `" + key + "' for WebSocket key");
+ }
+
+ //
+ // Retain the target resource.
+ //
+ _resource = _parser.uri();
+
+ //
+ // Compose the response.
+ //
+ StringBuffer out = new StringBuffer();
+ out.append("HTTP/1.1 101 Switching Protocols\r\n");
+ out.append("Upgrade: websocket\r\n");
+ out.append("Connection: Upgrade\r\n");
+ if(addProtocol)
+ {
+ out.append("Sec-WebSocket-Protocol: " + _iceProtocol + "\r\n");
+ }
+
+ //
+ // The response includes:
+ //
+ // "A |Sec-WebSocket-Accept| header field. The value of this
+ // header field is constructed by concatenating /key/, defined
+ // above in step 4 in Section 4.2.2, with the string "258EAFA5-
+ // E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this
+ // concatenated value to obtain a 20-byte value and base64-
+ // encoding (see Section 4 of [RFC4648]) this 20-byte hash.
+ //
+ out.append("Sec-WebSocket-Accept: ");
+ final String input = key + _wsUUID;
+ try
+ {
+ final MessageDigest sha1 = MessageDigest.getInstance("SHA1");
+ sha1.update(input.getBytes(_ascii));
+ final byte[] hash = sha1.digest();
+ out.append(IceUtilInternal.Base64.encode(hash) + "\r\n" + "\r\n"); // EOM
+ }
+ catch(NoSuchAlgorithmException ex)
+ {
+ throw new WebSocketException(ex);
+ }
+
+ final byte[] bytes = out.toString().getBytes(_ascii);
+ assert(bytes.length == out.length());
+ responseBuffer.resize(bytes.length, false);
+ responseBuffer.b.position(0);
+ responseBuffer.b.put(bytes);
+ responseBuffer.b.flip();
+ }
+
+ private void handleResponse()
+ {
+ String val;
+
+ //
+ // HTTP/1.1
+ //
+ if(_parser.versionMajor() != 1 || _parser.versionMinor() != 1)
+ {
+ throw new WebSocketException("unsupported HTTP version");
+ }
+
+ //
+ // "If the status code received from the server is not 101, the
+ // client handles the response per HTTP [RFC2616] procedures. In
+ // particular, the client might perform authentication if it
+ // receives a 401 status code; the server might redirect the client
+ // using a 3xx status code (but clients are not required to follow
+ // them), etc."
+ //
+ if(_parser.status() != 101)
+ {
+ StringBuffer out = new StringBuffer("unexpected status value " + _parser.status());
+ if(_parser.reason().length() > 0)
+ {
+ out.append(":\n" + _parser.reason());
+ }
+ throw new WebSocketException(out.toString());
+ }
+
+ //
+ // "If the response lacks an |Upgrade| header field or the |Upgrade|
+ // header field contains a value that is not an ASCII case-
+ // insensitive match for the value "websocket", the client MUST
+ // _Fail the WebSocket Connection_."
+ //
+ val = _parser.getHeader("Upgrade", true);
+ if(val == null)
+ {
+ throw new WebSocketException("missing value for Upgrade field");
+ }
+ else if(!val.equals("websocket"))
+ {
+ throw new WebSocketException("invalid value `" + val + "' for Upgrade field");
+ }
+
+ //
+ // "If the response lacks a |Connection| header field or the
+ // |Connection| header field doesn't contain a token that is an
+ // ASCII case-insensitive match for the value "Upgrade", the client
+ // MUST _Fail the WebSocket Connection_."
+ //
+ val = _parser.getHeader("Connection", true);
+ if(val == null)
+ {
+ throw new WebSocketException("missing value for Connection field");
+ }
+ else if(val.indexOf("upgrade") == -1)
+ {
+ throw new WebSocketException("invalid value `" + val + "' for Connection field");
+ }
+
+ //
+ // "If the response includes a |Sec-WebSocket-Protocol| header field
+ // and this header field indicates the use of a subprotocol that was
+ // not present in the client's handshake (the server has indicated a
+ // subprotocol not requested by the client), the client MUST _Fail
+ // the WebSocket Connection_."
+ //
+ val = _parser.getHeader("Sec-WebSocket-Protocol", true);
+ if(val != null && !val.equals(_iceProtocol))
+ {
+ throw new WebSocketException("invalid value `" + val + "' for WebSocket protocol");
+ }
+
+ //
+ // "If the response lacks a |Sec-WebSocket-Accept| header field or
+ // the |Sec-WebSocket-Accept| contains a value other than the
+ // base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket-
+ // Key| (as a string, not base64-decoded) with the string "258EAFA5-
+ // E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
+ // trailing whitespace, the client MUST _Fail the WebSocket
+ // Connection_."
+ //
+ val = _parser.getHeader("Sec-WebSocket-Accept", false);
+ if(val == null)
+ {
+ throw new WebSocketException("missing value for Sec-WebSocket-Accept");
+ }
+
+ try
+ {
+ final String input = _key + _wsUUID;
+ final MessageDigest sha1 = MessageDigest.getInstance("SHA1");
+ sha1.update(input.getBytes(_ascii));
+ if(!val.equals(IceUtilInternal.Base64.encode(sha1.digest())))
+ {
+ throw new WebSocketException("invalid value `" + val + "' for Sec-WebSocket-Accept");
+ }
+ }
+ catch(NoSuchAlgorithmException ex)
+ {
+ throw new WebSocketException(ex);
+ }
+ }
+
+ private boolean preRead(IceInternal.Buffer buf)
+ {
+ while(true)
+ {
+ if(_readState == ReadStateOpcode)
+ {
+ //
+ // Is there enough data available to read the opcode?
+ //
+ if(!readBuffered(2))
+ {
+ return true;
+ }
+
+ //
+ // Most-significant bit indicates whether this is the
+ // last frame. Least-significant four bits hold the
+ // opcode.
+ //
+ int ch = _readBuffer.b.get(_readBufferPos++);
+ if(ch < 0)
+ {
+ ch += 256;
+ }
+ _readLastFrame = (ch & FLAG_FINAL) == FLAG_FINAL;
+ _readOpCode = ch & 0xf;
+
+ ch = _readBuffer.b.get(_readBufferPos++);
+ if(ch < 0)
+ {
+ ch += 256;
+ }
+
+ //
+ // Check the MASK bit. Messages sent by a client must be masked;
+ // messages sent by a server must not be masked.
+ //
+ final boolean masked = (ch & FLAG_MASKED) == FLAG_MASKED;
+ if(masked != _incoming)
+ {
+ throw new Ice.ProtocolException("invalid masking");
+ }
+
+ //
+ // Extract the payload length, which can have the following values:
+ //
+ // 0-125: The payload length
+ // 126: The subsequent two bytes contain the payload length
+ // 127: The subsequent eight bytes contain the payload length
+ //
+ _readPayloadLength = (ch & 0x7f);
+ if(_readPayloadLength < 126)
+ {
+ _readHeaderLength = 0;
+ }
+ else if(_readPayloadLength == 126)
+ {
+ _readHeaderLength = 2; // Need to read a 16-bit payload length.
+ }
+ else
+ {
+ _readHeaderLength = 8; // Need to read a 64-bit payload length.
+ }
+ if(masked)
+ {
+ _readHeaderLength += 4; // Need to read a 32-bit mask.
+ }
+
+ _readState = ReadStateHeader;
+ }
+
+ if(_readState == ReadStateHeader)
+ {
+ //
+ // Is there enough data available to read the header?
+ //
+ if(_readHeaderLength > 0 && !readBuffered(_readHeaderLength))
+ {
+ return true;
+ }
+
+ if(_readPayloadLength == 126)
+ {
+ _readPayloadLength = _readBuffer.b.getShort(_readBufferPos); // Uses network byte order.
+ if(_readPayloadLength < 0)
+ {
+ _readPayloadLength += 65536;
+ }
+ _readBufferPos += 2;
+ }
+ else if(_readPayloadLength == 127)
+ {
+ long l = _readBuffer.b.getLong(_readBufferPos);
+ _readBufferPos += 8;
+ if(l < 0 || l > Integer.MAX_VALUE)
+ {
+ throw new Ice.ProtocolException("invalid WebSocket payload length: " + l);
+ }
+ _readPayloadLength = (int)l;
+ }
+
+ //
+ // Read the mask if this is an incoming connection.
+ //
+ if(_incoming)
+ {
+ assert(_readBuffer.b.position() - _readBufferPos >= 4); // We must have needed to read the mask.
+ for(int i = 0; i < 4; ++i)
+ {
+ _readMask[i] = _readBuffer.b.get(_readBufferPos++); // Copy the mask.
+ }
+ }
+
+ switch(_readOpCode)
+ {
+ case OP_CONT: // Continuation frame
+ {
+ // TODO: Add support for continuation frames?
+ throw new Ice.ProtocolException("continuation frames not supported");
+ }
+ case OP_TEXT: // Text frame
+ {
+ throw new Ice.ProtocolException("text frames not supported");
+ }
+ case OP_DATA: // Data frame
+ {
+ if(!_readLastFrame)
+ {
+ throw new Ice.ProtocolException("continuation frames not supported");
+ }
+ if(_readPayloadLength <= 0)
+ {
+ throw new Ice.ProtocolException("payload length is 0");
+ }
+ _readState = ReadStatePayload;
+ break;
+ }
+ case OP_CLOSE: // Connection close
+ {
+ if(_instance.traceLevel() >= 2)
+ {
+ _instance.logger().trace(_instance.traceCategory(),
+ "received " + protocol() + " connection close frame\n" + toString());
+ }
+
+ int s = _nextState == StateOpened ? _state : _nextState;
+ if(s == StateClosingRequestPending)
+ {
+ //
+ // If we receive a close frame while we were actually
+ // waiting to send one, change the role and send a
+ // close frame response.
+ //
+ if(!_closingInitiator)
+ {
+ _closingInitiator = true;
+ }
+ if(_state == StateClosingRequestPending)
+ {
+ _state = StateClosingResponsePending;
+ }
+ else
+ {
+ _nextState = StateClosingResponsePending;
+ }
+ return false; // No longer interested in reading
+ }
+ else
+ {
+ throw new Ice.ConnectionLostException();
+ }
+ }
+ case OP_PING:
+ {
+ if(_instance.traceLevel() >= 2)
+ {
+ _instance.logger().trace(_instance.traceCategory(),
+ "received " + protocol() + " connection ping frame\n" + toString());
+ }
+ _readState = ReadStateControlFrame;
+ break;
+ }
+ case OP_PONG: // Pong
+ {
+ if(_instance.traceLevel() >= 2)
+ {
+ _instance.logger().trace(_instance.traceCategory(),
+ "received " + protocol() + " connection pong frame\n" + toString());
+ }
+ _readState = ReadStateControlFrame;
+ break;
+ }
+ default:
+ {
+ throw new Ice.ProtocolException("unsupported opcode: " + _readOpCode);
+ }
+ }
+ }
+
+ if(_readState == ReadStateControlFrame)
+ {
+ if(_readPayloadLength > 0 && !readBuffered(_readPayloadLength))
+ {
+ return true;
+ }
+
+ if(_readPayloadLength > 0 && _readOpCode == OP_PING)
+ {
+ _pingPayload = new byte[_readPayloadLength];
+ if(_readBuffer.b.hasArray())
+ {
+ System.arraycopy(_readBuffer.b.array(), _readBuffer.b.arrayOffset() + _readBufferPos,
+ _pingPayload, 0, _readPayloadLength);
+ }
+ else
+ {
+ for(int i = 0; i < _readPayloadLength; ++i)
+ {
+ _pingPayload[i] = _readBuffer.b.get(_readBufferPos + i);
+ }
+ }
+ }
+
+ _readBufferPos += _readPayloadLength;
+ _readPayloadLength = 0;
+
+ if(_readOpCode == OP_PING)
+ {
+ if(_state == StateOpened)
+ {
+ _state = StatePongPending; // Send pong frame now
+ }
+ else if(_nextState < StatePongPending)
+ {
+ _nextState = StatePongPending; // Send pong frame next
+ }
+ }
+
+ //
+ // We've read the payload of the PING/PONG frame, we're ready
+ // to read a new frame.
+ //
+ _readState = ReadStateOpcode;
+ }
+
+ if(_readState == ReadStatePayload)
+ {
+ //
+ // This must be assigned before the check for the buffer. If the buffer is empty
+ // or already read, postRead will return false.
+ //
+ _readStart = buf.b.position();
+
+ if(buf.empty() || !buf.b.hasRemaining())
+ {
+ return false;
+ }
+
+ if(_readBufferPos < _readBuffer.b.position())
+ {
+ final int n = Math.min(_readBuffer.b.position() - _readBufferPos, buf.b.remaining());
+ if(buf.b.hasArray() && _readBuffer.b.hasArray())
+ {
+ System.arraycopy(_readBuffer.b.array(), _readBuffer.b.arrayOffset() + _readBufferPos,
+ buf.b.array(), buf.b.arrayOffset() + buf.b.position(), n);
+ buf.b.position(buf.b.position() + n);
+ }
+ else
+ {
+ for(int i = 0; i < n; ++i)
+ {
+ buf.b.put(_readBuffer.b.get(_readBufferPos + i));
+ }
+ }
+ _readBufferPos += n;
+ }
+
+ //
+ // Continue reading if we didn't read the full message, otherwise give back
+ // the control to the connection
+ //
+ return buf.b.hasRemaining();
+ }
+ }
+ }
+
+ private boolean postRead(IceInternal.Buffer buf)
+ {
+ if(_readState != ReadStatePayload)
+ {
+ return _readStart < _readBuffer.b.position(); // Returns true if data was read.
+ }
+
+ if(_readStart == buf.b.position())
+ {
+ return false; // Nothing was read or nothing to read.
+ }
+ assert(_readStart < buf.b.position());
+
+ if(_incoming)
+ {
+ //
+ // Unmask the data we just read.
+ //
+ int p = _readStart;
+ for(int n = _readStart; p < buf.b.position(); ++p, ++n)
+ {
+ final byte b = (byte)(buf.b.get(n) ^ _readMask[n % 4]);
+ buf.b.put(n, b);
+ }
+ }
+
+ _readPayloadLength -= buf.b.position() - _readStart;
+ _readStart = buf.b.position();
+ if(_readPayloadLength == 0)
+ {
+ //
+ // We've read the complete payload, we're ready to read a new frame.
+ //
+ _readState = ReadStateOpcode;
+ }
+ return buf.b.hasRemaining();
+ }
+
+ private boolean preWrite(IceInternal.Buffer buf)
+ {
+ if(_writeState == WriteStateHeader)
+ {
+ if(_state == StateOpened)
+ {
+ if(buf.empty() || !buf.b.hasRemaining())
+ {
+ return false;
+ }
+
+ assert(buf.b.position() == 0);
+ prepareWriteHeader((byte)OP_DATA, buf.size());
+
+ //
+ // For server connections, we use the _writeBuffer only to
+ // write the header, the message is sent directly from the
+ // message buffer. For client connections, we use the write
+ // buffer for both the header and message buffer since we need
+ // to mask the message data.
+ //
+ if(_incoming)
+ {
+ _writeBuffer.b.flip();
+ }
+ _writeState = WriteStatePayload;
+ }
+ else if(_state == StatePingPending)
+ {
+ prepareWriteHeader((byte)OP_PING, 0); // Don't send any payload
+
+ _writeState = WriteStateControlFrame;
+ _writeBuffer.b.flip();
+ }
+ else if(_state == StatePongPending)
+ {
+ prepareWriteHeader((byte)OP_PONG, _pingPayload.length);
+ if(_pingPayload.length > _writeBuffer.b.remaining())
+ {
+ final int pos = _writeBuffer.b.position();
+ _writeBuffer.resize(pos + _pingPayload.length, false);
+ _writeBuffer.b.position(pos);
+ }
+ _writeBuffer.b.put(_pingPayload);
+ _pingPayload = new byte[0];
+
+ _writeState = WriteStateControlFrame;
+ _writeBuffer.b.flip();
+ }
+ else if((_state == StateClosingRequestPending && !_closingInitiator) ||
+ (_state == StateClosingResponsePending && _closingInitiator))
+ {
+ prepareWriteHeader((byte)OP_CLOSE, 2);
+
+ // Write closing reason
+ _writeBuffer.b.putShort((short)_closingReason);
+
+ if(!_incoming)
+ {
+ byte b;
+ int pos = _writeBuffer.b.position() - 2;
+ b = (byte)(_writeBuffer.b.get(pos) ^ _writeMask[0]);
+ _writeBuffer.b.put(pos, b);
+ pos++;
+ b = (byte)(_writeBuffer.b.get(pos) ^ _writeMask[1]);
+ _writeBuffer.b.put(pos, b);
+ }
+
+ _writeState = WriteStateControlFrame;
+ _writeBuffer.b.flip();
+ }
+ else
+ {
+ assert(_state != StateClosed);
+ return false; // Nothing to write in this state
+ }
+
+ _writePayloadLength = 0;
+ }
+
+ if(_writeState == WriteStatePayload)
+ {
+ //
+ // For an outgoing connection, each message must be masked with a random
+ // 32-bit value, so we copy the entire message into the internal buffer
+ // for writing.
+ //
+ // For an incoming connection, we use the internal buffer to hold the
+ // frame header, and then write the caller's buffer to avoid copying.
+ //
+ if(!_incoming)
+ {
+ if(_writePayloadLength == 0 || !_writeBuffer.b.hasRemaining())
+ {
+ if(!_writeBuffer.b.hasRemaining())
+ {
+ _writeBuffer.b.position(0);
+ }
+
+ int n = buf.b.position();
+ final int sz = buf.size();
+ if(buf.b.hasArray() && _writeBuffer.b.hasArray())
+ {
+ int pos = _writeBuffer.b.position();
+ final int count = Math.min(sz - n, _writeBuffer.b.remaining());
+ final byte[] src = buf.b.array();
+ final int srcOff = buf.b.arrayOffset();
+ final byte[] dest = _writeBuffer.b.array();
+ final int destOff = _writeBuffer.b.arrayOffset();
+ for(int i = 0; i < count; ++i, ++n, ++pos)
+ {
+ dest[destOff + pos] = (byte)(src[srcOff + n] ^ _writeMask[n % 4]);
+ }
+ _writeBuffer.b.position(pos);
+ }
+ else
+ {
+ for(; n < sz && _writeBuffer.b.hasRemaining(); ++n)
+ {
+ final byte b = (byte)(buf.b.get(n) ^ _writeMask[n % 4]);
+ _writeBuffer.b.put(b);
+ }
+ }
+ _writePayloadLength = n;
+
+ _writeBuffer.b.flip();
+ }
+ }
+ return true;
+ }
+ else if(_writeState == WriteStateControlFrame)
+ {
+ return _writeBuffer.b.hasRemaining();
+ }
+ else
+ {
+ assert(_writeState == WriteStateFlush);
+ return true;
+ }
+ }
+
+ private boolean postWrite(IceInternal.Buffer buf, int status)
+ {
+ if(_state > StateOpened && _writeState == WriteStateControlFrame)
+ {
+ if(!_writeBuffer.b.hasRemaining())
+ {
+ if(_state == StatePingPending)
+ {
+ if(_instance.traceLevel() >= 2)
+ {
+ _instance.logger().trace(_instance.traceCategory(),
+ "sent " + protocol() + " connection ping frame\n" + toString());
+ }
+ }
+ else if(_state == StatePongPending)
+ {
+ if(_instance.traceLevel() >= 2)
+ {
+ _instance.logger().trace(_instance.traceCategory(),
+ "sent " + protocol() + " connection pong frame\n" + toString());
+ }
+ }
+ else if((_state == StateClosingRequestPending && !_closingInitiator) ||
+ (_state == StateClosingResponsePending && _closingInitiator))
+ {
+ if(_instance.traceLevel() >= 2)
+ {
+ _instance.logger().trace(_instance.traceCategory(),
+ "sent " + protocol() + " connection close frame\n" + toString());
+ }
+
+ if(_state == StateClosingRequestPending && !_closingInitiator)
+ {
+ _writeState = WriteStateHeader;
+ _state = StateClosingResponsePending;
+ return false;
+ }
+ else
+ {
+ throw new Ice.ConnectionLostException();
+ }
+ }
+ else if(_state == StateClosed)
+ {
+ return false;
+ }
+
+ _state = _nextState;
+ _nextState = StateOpened;
+ _writeState = WriteStateHeader;
+ }
+ else
+ {
+ return status == IceInternal.SocketOperation.None;
+ }
+ }
+
+ if(!_incoming && _writePayloadLength > 0)
+ {
+ if(!_writeBuffer.b.hasRemaining())
+ {
+ buf.b.position(_writePayloadLength);
+ }
+ }
+
+ if(status == IceInternal.SocketOperation.Write && !buf.b.hasRemaining() && !_writeBuffer.b.hasRemaining())
+ {
+ //
+ // Our buffers are empty but the delegate needs another call to write().
+ //
+ _writeState = WriteStateFlush;
+ return false;
+ }
+ else if(!buf.b.hasRemaining())
+ {
+ _writeState = WriteStateHeader;
+ if(_state == StatePingPending ||
+ _state == StatePongPending ||
+ (_state == StateClosingRequestPending && !_closingInitiator) ||
+ (_state == StateClosingResponsePending && _closingInitiator))
+ {
+ return true;
+ }
+ }
+ else if(_state == StateOpened)
+ {
+ return status == IceInternal.SocketOperation.None;
+ }
+
+ return false;
+ }
+
+ private boolean readBuffered(int sz)
+ {
+ if(_readBufferPos == _readBuffer.b.position())
+ {
+ _readBuffer.resize(_readBufferSize, true);
+ _readBufferPos = 0;
+ _readBuffer.b.position(0);
+ }
+ else
+ {
+ final int available = _readBuffer.b.position() - _readBufferPos;
+ if(available < sz)
+ {
+ if(_readBufferPos > 0)
+ {
+ _readBuffer.b.limit(_readBuffer.b.position());
+ _readBuffer.b.position(_readBufferPos);
+ _readBuffer.b.compact();
+ assert(_readBuffer.b.position() == available);
+ }
+ _readBuffer.resize(Math.max(_readBufferSize, sz), true);
+ _readBufferPos = 0;
+ _readBuffer.b.position(available);
+ }
+ }
+
+ _readStart = _readBuffer.b.position();
+ if(_readBufferPos + sz > _readBuffer.b.position())
+ {
+ return false; // Not enough read.
+ }
+ assert(_readBuffer.b.position() > _readBufferPos);
+ return true;
+ }
+
+ private void prepareWriteHeader(byte opCode, int payloadLength)
+ {
+ //
+ // We need to prepare the frame header.
+ //
+ _writeBuffer.resize(_writeBufferSize, false);
+ _writeBuffer.b.limit(_writeBufferSize);
+ _writeBuffer.b.position(0);
+
+ //
+ // Set the opcode - this is the one and only data frame.
+ //
+ _writeBuffer.b.put((byte)(opCode | FLAG_FINAL));
+
+ //
+ // Set the payload length.
+ //
+ if(payloadLength <= 125)
+ {
+ _writeBuffer.b.put((byte)payloadLength);
+ }
+ else if(payloadLength > 125 && payloadLength <= 65535)
+ {
+ //
+ // Use an extra 16 bits to encode the payload length.
+ //
+ _writeBuffer.b.put((byte)126);
+ _writeBuffer.b.putShort((short)payloadLength);
+ }
+ else if(payloadLength > 65535)
+ {
+ //
+ // Use an extra 64 bits to encode the payload length.
+ //
+ _writeBuffer.b.put((byte)127);
+ _writeBuffer.b.putLong(payloadLength);
+ }
+
+ if(!_incoming)
+ {
+ //
+ // Add a random 32-bit mask to every outgoing frame, copy the payload data,
+ // and apply the mask.
+ //
+ _writeBuffer.b.put(1, (byte)(_writeBuffer.b.get(1) | FLAG_MASKED));
+ _rand.nextBytes(_writeMask);
+ _writeBuffer.b.put(_writeMask);
+ }
+ }
+
+ private Instance _instance;
+ private IceInternal.Transceiver _delegate;
+ private String _host;
+ private int _port;
+ private String _resource;
+ private boolean _incoming;
+
+ private static final int StateInitializeDelegate = 0;
+ private static final int StateConnected = 1;
+ private static final int StateUpgradeRequestPending = 2;
+ private static final int StateUpgradeResponsePending = 3;
+ private static final int StateOpened = 4;
+ private static final int StatePingPending = 5;
+ private static final int StatePongPending = 6;
+ private static final int StateClosingRequestPending = 7;
+ private static final int StateClosingResponsePending = 8;
+ private static final int StateClosed = 9;
+
+ int _state;
+ int _nextState;
+
+ private HttpParser _parser;
+ private String _key;
+
+ private static final int ReadStateOpcode = 0;
+ private static final int ReadStateHeader = 1;
+ private static final int ReadStateControlFrame = 2;
+ private static final int ReadStatePayload = 3;
+
+ private int _readState;
+ private IceInternal.Buffer _readBuffer;
+ private int _readBufferPos;
+ private int _readBufferSize;
+
+ private boolean _readLastFrame;
+ private int _readOpCode;
+ private int _readHeaderLength;
+ private int _readPayloadLength;
+ private int _readStart;
+ private byte[] _readMask;
+
+ private static final int WriteStateHeader = 0;
+ private static final int WriteStatePayload = 1;
+ private static final int WriteStateControlFrame = 2;
+ private static final int WriteStateFlush = 3;
+
+ private int _writeState;
+ private IceInternal.Buffer _writeBuffer;
+ private int _writeBufferSize;
+ private byte[] _writeMask;
+ private int _writePayloadLength;
+
+ private boolean _closingInitiator;
+ private int _closingReason;
+
+ private boolean _readPending;
+ private boolean _writePending;
+
+ private byte[] _pingPayload;
+
+ private java.util.Random _rand;
+
+ //
+ // WebSocket opcodes
+ //
+ final static private int OP_CONT = 0x0; // Continuation frame
+ final static private int OP_TEXT = 0x1; // Text frame
+ final static private int OP_DATA = 0x2; // Data frame
+ final static private int OP_RES_0x3 = 0x3; // Reserved
+ final static private int OP_RES_0x4 = 0x4; // Reserved
+ final static private int OP_RES_0x5 = 0x5; // Reserved
+ final static private int OP_RES_0x6 = 0x6; // Reserved
+ final static private int OP_RES_0x7 = 0x7; // Reserved
+ final static private int OP_CLOSE = 0x8; // Connection close
+ final static private int OP_PING = 0x9; // Ping
+ final static private int OP_PONG = 0xA; // Pong
+ final static private int OP_RES_0xB = 0xB; // Reserved
+ final static private int OP_RES_0xC = 0xC; // Reserved
+ final static private int OP_RES_0xD = 0xD; // Reserved
+ final static private int OP_RES_0xE = 0xE; // Reserved
+ final static private int OP_RES_0xF = 0xF; // Reserved
+ final static private int FLAG_FINAL = 0x80; // Last frame
+ final static private int FLAG_MASKED = 0x80; // Payload is masked
+
+ final static private int CLOSURE_NORMAL = 1000;
+ final static private int CLOSURE_SHUTDOWN = 1001;
+ final static private int CLOSURE_PROTOCOL_ERROR = 1002;
+ final static private int CLOSURE_TOO_BIG = 1009;
+
+ final static private String _iceProtocol = "ice.zeroc.com";
+ final static private String _wsUUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+ final static java.nio.charset.Charset _ascii = java.nio.charset.Charset.forName("US-ASCII");
+}
diff --git a/java/src/IceWS/Util.java b/java/src/IceWS/Util.java
new file mode 100644
index 00000000000..0299d24f885
--- /dev/null
+++ b/java/src/IceWS/Util.java
@@ -0,0 +1,14 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+public final class Util
+{
+}
diff --git a/java/src/IceWS/WebSocketException.java b/java/src/IceWS/WebSocketException.java
new file mode 100644
index 00000000000..5691a5c8a9f
--- /dev/null
+++ b/java/src/IceWS/WebSocketException.java
@@ -0,0 +1,28 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceWS;
+
+final class WebSocketException extends java.lang.RuntimeException
+{
+ public WebSocketException(String reason)
+ {
+ super(reason);
+ }
+
+ public WebSocketException(String reason, Throwable cause)
+ {
+ super(reason, cause);
+ }
+
+ public WebSocketException(Throwable cause)
+ {
+ super(cause);
+ }
+}