diff options
Diffstat (limited to 'java/src')
-rw-r--r-- | java/src/IceInternal/Buffer.java | 28 | ||||
-rw-r--r-- | java/src/IceInternal/IPEndpointI.java | 6 | ||||
-rw-r--r-- | java/src/IceInternal/TcpEndpointI.java | 6 | ||||
-rw-r--r-- | java/src/IceInternal/UdpEndpointI.java | 6 | ||||
-rw-r--r-- | java/src/IceSSL/EndpointI.java | 6 | ||||
-rw-r--r-- | java/src/IceWS/AcceptorI.java | 56 | ||||
-rw-r--r-- | java/src/IceWS/ConnectorI.java | 74 | ||||
-rw-r--r-- | java/src/IceWS/EndpointFactoryI.java | 54 | ||||
-rw-r--r-- | java/src/IceWS/EndpointI.java | 302 | ||||
-rw-r--r-- | java/src/IceWS/HttpParser.java | 735 | ||||
-rw-r--r-- | java/src/IceWS/Instance.java | 18 | ||||
-rw-r--r-- | java/src/IceWS/PluginFactory.java | 32 | ||||
-rw-r--r-- | java/src/IceWS/PluginI.java | 38 | ||||
-rw-r--r-- | java/src/IceWS/TransceiverI.java | 1502 | ||||
-rw-r--r-- | java/src/IceWS/Util.java | 14 | ||||
-rw-r--r-- | java/src/IceWS/WebSocketException.java | 28 |
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); + } +} |