diff options
author | Joakim Erdfelt | 2013-12-09 23:18:39 +0000 |
---|---|---|
committer | Joakim Erdfelt | 2013-12-09 23:20:14 +0000 |
commit | 48fe92d93930a3ae9e13bcf375b9cc367182f5d3 (patch) | |
tree | 5e6a632d2f64fe831c8a046c3bd58321cd495deb /jetty-websocket | |
parent | db777310b59fc2c0ecbdfbe852c2a8c18fa7442a (diff) | |
download | org.eclipse.jetty.project-48fe92d93930a3ae9e13bcf375b9cc367182f5d3.tar.gz org.eclipse.jetty.project-48fe92d93930a3ae9e13bcf375b9cc367182f5d3.tar.xz org.eclipse.jetty.project-48fe92d93930a3ae9e13bcf375b9cc367182f5d3.zip |
423185 - Update permessage-deflate for finalized spec
+ Adding support for new permessage-deflate parameters
+ Tested against pywebsocket (rev 790)
+ Tested against Chrome Canary 32
Diffstat (limited to 'jetty-websocket')
24 files changed, 680 insertions, 378 deletions
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java index 04c01217de..329dbea81c 100644 --- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java @@ -63,6 +63,16 @@ public class ExtensionConfig this.parameters = new HashMap<>(); } + /** + * Copy constructor + */ + public ExtensionConfig(ExtensionConfig copy) + { + this.name = copy.name; + this.parameters = new HashMap<>(); + this.parameters.putAll(copy.parameters); + } + public String getName() { return name; @@ -142,6 +152,11 @@ public class ExtensionConfig { parameters.put(key,value); } + + public void setParameter(String key) + { + parameters.put(key,null); + } @Override public String toString() diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java index a9cdd53d03..9f37813b1a 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java @@ -18,8 +18,7 @@ package org.eclipse.jetty.websocket.client.blockhead; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.*; import java.io.BufferedReader; import java.io.IOException; @@ -158,7 +157,7 @@ public class BlockheadServer // now echo them back. for (Frame frame : cap.getFrames()) { - write(frame); + write(WebSocketFrame.copy(frame).setMasked(false)); } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java index 4aee18cdc2..d4f5973dc5 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java @@ -18,10 +18,6 @@ package org.eclipse.jetty.websocket.common; -import org.eclipse.jetty.websocket.common.io.IOState; -import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; - - /** * Connection states as outlined in <a href="https://tools.ietf.org/html/rfc6455">RFC6455</a>. */ diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java index d825e28c0a..4f705f0c0f 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java @@ -22,7 +22,6 @@ import java.net.InetSocketAddress; import java.util.concurrent.Executor; import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.IncomingFrames; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java index 05cfc0ff3f..c6e2f977f2 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java @@ -24,9 +24,7 @@ import java.util.List; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.InvalidWebSocketException; -import org.eclipse.jetty.websocket.api.WebSocketListener; import org.eclipse.jetty.websocket.api.WebSocketPolicy; -import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * Create EventDriver implementations. diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java index f56fd1bb51..f60d07e143 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java @@ -64,9 +64,9 @@ public class WebSocketExtensionFactory extends ExtensionFactory if (ext instanceof AbstractExtension) { AbstractExtension aext = (AbstractExtension)ext; - aext.setConfig(config); aext.setPolicy(policy); aext.setBufferPool(bufferPool); + aext.setConfig(config); } return ext; } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java index b8948f396d..789f72eae5 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java @@ -48,10 +48,21 @@ public class PerMessageDeflateExtension extends AbstractExtension /** Tail Bytes per Spec */ private static final byte[] TAIL = new byte[] { 0x00, 0x00, (byte)0xFF, (byte)0xFF }; - private int bufferSize = 64 * 1024; + private ExtensionConfig configRequested; + private ExtensionConfig configNegotiated; private Deflater compressor; private Inflater decompressor; + private boolean incomingCompressed = false; + private boolean outgoingCompressed = false; + /** + * Context Takeover Control. + * <p> + * If true, the same LZ77 window is used between messages. Can be overridden with extension parameters. + */ + private boolean incomingContextTakeover = true; + private boolean outgoingContextTakeover = true; + @Override public String getName() { @@ -61,16 +72,27 @@ public class PerMessageDeflateExtension extends AbstractExtension @Override public synchronized void incomingFrame(Frame frame) { - if (OpCode.isControlFrame(frame.getOpCode()) || !frame.isRsv1()) + switch (frame.getOpCode()) { - // Cannot modify incoming control frames or ones with RSV1 set. - nextIncomingFrame(frame); - return; + case OpCode.BINARY: // fall-thru + case OpCode.TEXT: + incomingCompressed = frame.isRsv1(); + break; + case OpCode.CONTINUATION: + if (!incomingCompressed) + { + nextIncomingFrame(frame); + } + break; + default: + // All others are assumed to be control frames + nextIncomingFrame(frame); + return; } - if (!frame.hasPayload()) + if (!incomingCompressed || !frame.hasPayload()) { - // no payload? nothing to do. + // nothing to do with this frame nextIncomingFrame(frame); return; } @@ -78,9 +100,21 @@ public class PerMessageDeflateExtension extends AbstractExtension // Prime the decompressor ByteBuffer payload = frame.getPayload(); int inlen = payload.remaining(); - byte compressed[] = new byte[inlen + TAIL.length]; - payload.get(compressed,0,inlen); - System.arraycopy(TAIL,0,compressed,inlen,TAIL.length); + byte compressed[] = null; + + if (frame.isFin()) + { + compressed = new byte[inlen + TAIL.length]; + payload.get(compressed,0,inlen); + System.arraycopy(TAIL,0,compressed,inlen,TAIL.length); + incomingCompressed = false; + } + else + { + compressed = new byte[inlen]; + payload.get(compressed,0,inlen); + } + decompressor.setInput(compressed,0,compressed.length); // Since we don't track text vs binary vs continuation state, just grab whatever is the greater value. @@ -93,7 +127,7 @@ public class PerMessageDeflateExtension extends AbstractExtension // Perform decompression while (decompressor.getRemaining() > 0 && !decompressor.finished()) { - byte outbuf[] = new byte[Math.min(inlen * 2,bufferSize)]; + byte outbuf[] = new byte[inlen]; try { int len = decompressor.inflate(outbuf); @@ -174,7 +208,25 @@ public class PerMessageDeflateExtension extends AbstractExtension if (len > 0) { - outbuf.put(compressed,0,len - 4); + if (len > 4) + { + // Test for the 4 tail octets (0x00 0x00 0xff 0xff) + int idx = len - 4; + boolean found = true; + for (int n = 0; n < TAIL.length; n++) + { + if (compressed[idx + n] != TAIL[n]) + { + found = false; + break; + } + } + if (found) + { + len = len - 4; + } + } + outbuf.put(compressed,0,len); } BufferUtil.flipToFlush(outbuf,0); @@ -195,7 +247,7 @@ public class PerMessageDeflateExtension extends AbstractExtension } } - DataFrame out = new DataFrame(frame); + DataFrame out = new DataFrame(frame,outgoingCompressed); out.setRsv1(true); out.setBufferPool(getBufferPool()); out.setPayload(outbuf); @@ -211,14 +263,41 @@ public class PerMessageDeflateExtension extends AbstractExtension // pass through the callback nextOutgoingFrame(out,callback); } + + outgoingCompressed = !out.isFin(); } } } @Override + protected void nextIncomingFrame(Frame frame) + { + if (frame.isFin() && !incomingContextTakeover) + { + LOG.debug("Incoming Context Reset"); + decompressor.reset(); + } + + super.nextIncomingFrame(frame); + } + + @Override + protected void nextOutgoingFrame(Frame frame, WriteCallback callback) + { + if (frame.isFin() && !outgoingContextTakeover) + { + LOG.debug("Outgoing Context Reset"); + compressor.reset(); + } + + super.nextOutgoingFrame(frame,callback); + } + + @Override public void setConfig(final ExtensionConfig config) { - ExtensionConfig negotiated = new ExtensionConfig(config.getName()); + configRequested = new ExtensionConfig(config); + configNegotiated = new ExtensionConfig(config.getName()); boolean nowrap = true; compressor = new Deflater(Deflater.BEST_COMPRESSION,nowrap); @@ -229,25 +308,41 @@ public class PerMessageDeflateExtension extends AbstractExtension for (String key : config.getParameterKeys()) { key = key.trim(); - String value = config.getParameter(key,null); switch (key) { - case "c2s_max_window_bits": - negotiated.setParameter("s2c_max_window_bits",value); - break; - case "c2s_no_context_takeover": - negotiated.setParameter("s2c_no_context_takeover",value); + case "client_max_window_bits": // fallthru + case "server_max_window_bits": + // Not supported by Jetty + // Don't negotiate these parameters break; - case "s2c_max_window_bits": - negotiated.setParameter("c2s_max_window_bits",value); + case "client_no_context_takeover": + configNegotiated.setParameter("client_no_context_takeover"); + switch (getPolicy().getBehavior()) + { + case CLIENT: + incomingContextTakeover = false; + break; + case SERVER: + outgoingContextTakeover = false; + break; + } break; - case "s2c_no_context_takeover": - negotiated.setParameter("c2s_no_context_takeover",value); + case "server_no_context_takeover": + configNegotiated.setParameter("server_no_context_takeover"); + switch (getPolicy().getBehavior()) + { + case CLIENT: + outgoingContextTakeover = false; + break; + case SERVER: + incomingContextTakeover = false; + break; + } break; } } - super.setConfig(negotiated); + super.setConfig(configNegotiated); } @Override @@ -255,7 +350,8 @@ public class PerMessageDeflateExtension extends AbstractExtension { StringBuilder str = new StringBuilder(); str.append(this.getClass().getSimpleName()); - str.append('['); + str.append("[requested=").append(configRequested.getParameterizedName()); + str.append(",negotiated=").append(configNegotiated.getParameterizedName()); str.append(']'); return str.toString(); } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java index 3340abbcab..f8609da2b0 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java @@ -26,10 +26,8 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; -import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.ArrayQueue; -import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FutureWriteCallback.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FutureWriteCallback.java index 2f09bc9c63..fddc8816c4 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FutureWriteCallback.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FutureWriteCallback.java @@ -18,8 +18,6 @@ package org.eclipse.jetty.websocket.common.io; -import java.util.concurrent.Future; - import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/PayloadProcessor.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/PayloadProcessor.java index b3c60a04bc..80c5d739c7 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/PayloadProcessor.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/PayloadProcessor.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.common.io.payload; import java.nio.ByteBuffer; -import org.eclipse.jetty.websocket.api.BadPayloadException; import org.eclipse.jetty.websocket.api.extensions.Frame; /** diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java index 1557c7d2a1..8d5cde54c6 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java @@ -21,7 +21,6 @@ package org.eclipse.jetty.websocket.common.message; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.util.concurrent.ExecutionException; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; @@ -32,7 +31,6 @@ import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames; import org.eclipse.jetty.websocket.common.BlockingWriteCallback; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.frames.BinaryFrame; -import org.eclipse.jetty.websocket.common.io.FutureWriteCallback; /** * Support for writing a single WebSocket BINARY message via a {@link OutputStream} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java index 37c065afb8..c31a750a47 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java @@ -21,9 +21,7 @@ package org.eclipse.jetty.websocket.common.message; import java.io.IOException; import java.io.InputStreamReader; import java.nio.ByteBuffer; -import java.nio.charset.Charset; - -import org.eclipse.jetty.util.StringUtil; +import java.nio.charset.StandardCharsets; /** * Support class for reading a (single) WebSocket TEXT message via a Reader. @@ -36,7 +34,7 @@ public class MessageReader extends InputStreamReader implements MessageAppender public MessageReader(MessageInputStream stream) { - super(stream,StringUtil.__UTF8_CHARSET); + super(stream,StandardCharsets.UTF_8); this.stream = stream; } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/Utf8CharBuffer.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/Utf8CharBuffer.java index 9d634fbe50..d5730d47dc 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/Utf8CharBuffer.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/Utf8CharBuffer.java @@ -20,10 +20,9 @@ package org.eclipse.jetty.websocket.common.message; import java.nio.ByteBuffer; import java.nio.CharBuffer; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.Utf8Appendable; /** @@ -31,8 +30,6 @@ import org.eclipse.jetty.util.Utf8Appendable; */ public class Utf8CharBuffer extends Utf8Appendable { - private static final Charset UTF8 = StringUtil.__UTF8_CHARSET; - /** * Convenience method to wrap a ByteBuffer with a {@link Utf8CharBuffer} * @@ -55,7 +52,7 @@ public class Utf8CharBuffer extends Utf8Appendable public void append(char[] cbuf, int offset, int size) { - append(BufferUtil.toDirectBuffer(new String(cbuf,offset,size),UTF8)); + append(BufferUtil.toDirectBuffer(new String(cbuf,offset,size),StandardCharsets.UTF_8)); } public void append(int c) @@ -80,7 +77,7 @@ public class Utf8CharBuffer extends Utf8Appendable buffer.position(0); // get byte buffer - ByteBuffer bb = UTF8.encode(buffer); + ByteBuffer bb = StandardCharsets.UTF_8.encode(buffer); // restor settings buffer.limit(limit); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java index 6252d9e21f..eb0b9a698c 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java @@ -76,10 +76,9 @@ public class GeneratorParserRoundtripTest @Test public void testParserAndGeneratorMasked() throws Exception { - WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); ByteBufferPool bufferPool = new MappedByteBufferPool(); - Generator gen = new Generator(policy,bufferPool); - Parser parser = new Parser(policy,bufferPool); + Generator gen = new Generator(WebSocketPolicy.newClientPolicy(),bufferPool); + Parser parser = new Parser(WebSocketPolicy.newServerPolicy(),bufferPool); IncomingFramesCapture capture = new IncomingFramesCapture(); parser.setIncomingFramesHandler(capture); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java index 2d0a3c57ab..2c9a71e24c 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java @@ -18,8 +18,7 @@ package org.eclipse.jetty.websocket.common; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.Matchers.*; import java.nio.ByteBuffer; import java.util.Arrays; diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java index 80c4dbc7f5..94f139aff1 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java @@ -18,17 +18,16 @@ package org.eclipse.jetty.websocket.common.ab; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.Arrays; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.common.ByteBufferAssert; -import org.eclipse.jetty.websocket.common.Generator; import org.eclipse.jetty.websocket.common.IncomingFramesCapture; import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.Parser; @@ -52,7 +51,7 @@ public class TestABCase1_1 int length = 125; byte buf[] = new byte[length]; Arrays.fill(buf,(byte)'*'); - String text = new String(buf,StringUtil.__UTF8_CHARSET); + String text = new String(buf,StandardCharsets.UTF_8); Frame textFrame = new TextFrame().setPayload(text); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java index 21fbda4d7a..a295bcba20 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java @@ -18,7 +18,7 @@ package org.eclipse.jetty.websocket.common.ab; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import java.nio.ByteBuffer; @@ -27,7 +27,6 @@ import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.common.ByteBufferAssert; -import org.eclipse.jetty.websocket.common.Generator; import org.eclipse.jetty.websocket.common.IncomingFramesCapture; import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.Parser; diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtensionTest.java new file mode 100644 index 0000000000..080aba2cb2 --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtensionTest.java @@ -0,0 +1,44 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.extensions; + +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.MappedByteBufferPool; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.TestName; + +public abstract class AbstractExtensionTest +{ + @Rule + public TestName testname = new TestName(); + + private static ByteBufferPool bufferPool; + protected static ExtensionTool clientExtensions; + protected static ExtensionTool serverExtensions; + + @BeforeClass + public static void init() + { + bufferPool = new MappedByteBufferPool(); + clientExtensions = new ExtensionTool(WebSocketPolicy.newClientPolicy(),bufferPool); + serverExtensions = new ExtensionTool(WebSocketPolicy.newServerPolicy(),bufferPool); + } +} diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionTool.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionTool.java new file mode 100644 index 0000000000..0999d5207c --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionTool.java @@ -0,0 +1,136 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.extensions; + +import static org.hamcrest.Matchers.*; + +import java.nio.ByteBuffer; +import java.util.Collections; + +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.api.extensions.Extension; +import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; +import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory; +import org.eclipse.jetty.websocket.api.extensions.Frame; +import org.eclipse.jetty.websocket.common.ByteBufferAssert; +import org.eclipse.jetty.websocket.common.IncomingFramesCapture; +import org.eclipse.jetty.websocket.common.Parser; +import org.eclipse.jetty.websocket.common.UnitParser; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; +import org.junit.Assert; + +public class ExtensionTool +{ + public class Tester + { + private String requestedExtParams; + private ExtensionConfig extConfig; + private Extension ext; + private Parser parser; + private IncomingFramesCapture capture; + + private Tester(String parameterizedExtension) + { + this.requestedExtParams = parameterizedExtension; + this.extConfig = ExtensionConfig.parse(parameterizedExtension); + Class<?> extClass = factory.getExtension(extConfig.getName()); + Assert.assertThat("extClass", extClass, notNullValue()); + + this.parser = new UnitParser(policy); + } + + public String getRequestedExtParams() + { + return requestedExtParams; + } + + public void assertNegotiated(String expectedNegotiation) + { + this.ext = (Extension)factory.newInstance(extConfig); + + this.capture = new IncomingFramesCapture(); + this.ext.setNextIncomingFrames(capture); + + this.parser.configureFromExtensions(Collections.singletonList(ext)); + this.parser.setIncomingFramesHandler(ext); + } + + public void parseIncomingHex(String... rawhex) + { + int parts = rawhex.length; + byte net[]; + + for (int i = 0; i < parts; i++) + { + String hex = rawhex[i].replaceAll("\\s*(0x)?",""); + net = TypeUtil.fromHexString(hex); + parser.parse(ByteBuffer.wrap(net)); + } + } + + public void assertHasFrames(String... textFrames) + { + Frame frames[] = new Frame[textFrames.length]; + for (int i = 0; i < frames.length; i++) + { + frames[i] = new TextFrame().setPayload(textFrames[i]); + } + assertHasFrames(frames); + } + + public void assertHasFrames(Frame... expectedFrames) + { + int expectedCount = expectedFrames.length; + capture.assertFrameCount(expectedCount); + + for (int i = 0; i < expectedCount; i++) + { + WebSocketFrame actual = capture.getFrames().pop(); + + String prefix = String.format("frame[%d]",i); + Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(expectedFrames[i].getOpCode())); + Assert.assertThat(prefix + ".fin",actual.isFin(),is(expectedFrames[i].isFin())); + Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); + Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false)); + Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false)); + + ByteBuffer expected = expectedFrames[i].getPayload().slice(); + Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining())); + ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice()); + } + } + } + + private final WebSocketPolicy policy; + private final ExtensionFactory factory; + + public ExtensionTool(WebSocketPolicy policy, ByteBufferPool bufferPool) + { + this.policy = policy; + this.factory = new WebSocketExtensionFactory(policy,bufferPool); + } + + public Tester newTester(String parameterizedExtension) + { + return new Tester(parameterizedExtension); + } +} diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java index 8b413cb80a..f0662fe530 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java @@ -18,9 +18,7 @@ package org.eclipse.jetty.websocket.common.extensions.compress; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import java.io.IOException; import java.nio.ByteBuffer; @@ -40,24 +38,20 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.common.ByteBufferAssert; import org.eclipse.jetty.websocket.common.Generator; -import org.eclipse.jetty.websocket.common.Hex; import org.eclipse.jetty.websocket.common.IncomingFramesCapture; import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.OutgoingNetworkBytesCapture; import org.eclipse.jetty.websocket.common.Parser; import org.eclipse.jetty.websocket.common.UnitParser; import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.extensions.AbstractExtensionTest; +import org.eclipse.jetty.websocket.common.extensions.ExtensionTool.Tester; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.junit.Assert; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; -public class DeflateFrameExtensionTest +public class DeflateFrameExtensionTest extends AbstractExtensionTest { - @Rule - public TestName testname = new TestName(); - private void assertIncoming(byte[] raw, String... expectedTextDatas) { WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); @@ -129,58 +123,90 @@ public class DeflateFrameExtensionTest @Test public void testBlockheadClient_HelloThere() { - // Captured from Blockhead Client - "Hello" then "There" via unit test - String hello = "c18700000000f248cdc9c90700"; - String there = "c187000000000ac9482d4a0500"; - byte rawbuf[] = Hex.asByteArray(hello + there); - assertIncoming(rawbuf,"Hello","There"); + Tester tester = serverExtensions.newTester("deflate-frame"); + + tester.assertNegotiated("deflate-frame"); + + tester.parseIncomingHex(// Captured from Blockhead Client - "Hello" then "There" via unit test + "c18700000000f248cdc9c90700", // "Hello" + "c187000000000ac9482d4a0500" // "There" + ); + + tester.assertHasFrames("Hello","There"); } @Test public void testChrome20_Hello() { - // Captured from Chrome 20.x - "Hello" (sent from browser) - byte rawbuf[] = Hex.asByteArray("c187832b5c11716391d84a2c5c"); - assertIncoming(rawbuf,"Hello"); + Tester tester = serverExtensions.newTester("deflate-frame"); + + tester.assertNegotiated("deflate-frame"); + + tester.parseIncomingHex(// Captured from Chrome 20.x - "Hello" (sent from browser) + "c187832b5c11716391d84a2c5c" // "Hello" + ); + + tester.assertHasFrames("Hello"); } @Test public void testChrome20_HelloThere() { - // Captured from Chrome 20.x - "Hello" then "There" (sent from browser) - String hello = "c1877b1971db8951bc12b21e71"; - String there = "c18759edc8f4532480d913e8c8"; - byte rawbuf[] = Hex.asByteArray(hello + there); - assertIncoming(rawbuf,"Hello","There"); + Tester tester = serverExtensions.newTester("deflate-frame"); + + tester.assertNegotiated("deflate-frame"); + + tester.parseIncomingHex(// Captured from Chrome 20.x - "Hello" then "There" (sent from browser) + "c1877b1971db8951bc12b21e71", // "Hello" + "c18759edc8f4532480d913e8c8" // There + ); + + tester.assertHasFrames("Hello","There"); } @Test public void testChrome20_Info() { - // Captured from Chrome 20.x - "info:" (sent from browser) - byte rawbuf[] = Hex.asByteArray("c187ca4def7f0081a4b47d4fef"); - assertIncoming(rawbuf,"info:"); + Tester tester = serverExtensions.newTester("deflate-frame"); + + tester.assertNegotiated("deflate-frame"); + + tester.parseIncomingHex(// Captured from Chrome 20.x - "info:" (sent from browser) + "c187ca4def7f0081a4b47d4fef" // example payload + ); + + tester.assertHasFrames("info:"); } @Test public void testChrome20_TimeTime() { - // Captured from Chrome 20.x - "time:" then "time:" once more (sent from browser) - String time1 = "c18782467424a88fb869374474"; - String time2 = "c1853cfda17f16fcb07f3c"; - byte rawbuf[] = Hex.asByteArray(time1 + time2); - assertIncoming(rawbuf,"time:","time:"); + Tester tester = serverExtensions.newTester("deflate-frame"); + + tester.assertNegotiated("deflate-frame"); + + tester.parseIncomingHex(// Captured from Chrome 20.x - "time:" then "time:" once more (sent from browser) + "c18782467424a88fb869374474", // "time:" + "c1853cfda17f16fcb07f3c" // "time:" + ); + + tester.assertHasFrames("time:","time:"); } @Test public void testPyWebSocket_TimeTimeTime() { - // Captured from Pywebsocket (r781) - "time:" sent 3 times. - String time1 = "c1876b100104" + "41d9cd49de1201"; - String time2 = "c1852ae3ff01" + "00e2ee012a"; - String time3 = "c18435558caa" + "37468caa"; - byte rawbuf[] = Hex.asByteArray(time1 + time2 + time3); - assertIncoming(rawbuf,"time:","time:","time:"); + Tester tester = serverExtensions.newTester("deflate-frame"); + + tester.assertNegotiated("deflate-frame"); + + tester.parseIncomingHex(// Captured from Pywebsocket (r781) - "time:" sent 3 times. + "c1876b100104" + "41d9cd49de1201", // "time:" + "c1852ae3ff01" + "00e2ee012a", // "time:" + "c18435558caa" + "37468caa" // "time:" + ); + + tester.assertHasFrames("time:","time:","time:"); } @Test @@ -202,11 +228,6 @@ public class DeflateFrameExtensionTest List<String> actual = capture.getCaptured(); - for (String entry : actual) - { - System.err.printf("actual: \"%s\"%n",entry); - } - Assert.assertThat("Compressed Payloads",actual,contains(expected)); } diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java index 07f2b18b63..b6bab5c79f 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java @@ -18,337 +18,185 @@ package org.eclipse.jetty.websocket.common.extensions.compress; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.common.ByteBufferAssert; -import org.eclipse.jetty.websocket.common.Hex; import org.eclipse.jetty.websocket.common.IncomingFramesCapture; import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.OutgoingFramesCapture; -import org.eclipse.jetty.websocket.common.Parser; -import org.eclipse.jetty.websocket.common.UnitParser; import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.extensions.AbstractExtensionTest; +import org.eclipse.jetty.websocket.common.extensions.ExtensionTool.Tester; +import org.eclipse.jetty.websocket.common.frames.ContinuationFrame; import org.eclipse.jetty.websocket.common.frames.PingFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.junit.Assert; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; -public class PerMessageDeflateExtensionTest +/** + * Client side behavioral tests for permessage-deflate extension. + * <p> + * See: http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-15 + */ +public class PerMessageDeflateExtensionTest extends AbstractExtensionTest { - @Rule - public TestName testname = new TestName(); - - private void assertIncoming(byte[] raw, String... expectedTextDatas) + /** + * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15. + * <p> + * Section 8.2.3.4: Using a DEFLATE Block with BFINAL Set to 1 + */ + @Test + public void testDraft15_DeflateBlockWithBFinal1() { - WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); - - PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); - ext.setBufferPool(new MappedByteBufferPool()); - ext.setPolicy(policy); + Tester tester = clientExtensions.newTester("permessage-deflate"); - ExtensionConfig config = ExtensionConfig.parse("permessage-deflate; c2s_max_window_bits"); - ext.setConfig(config); - - // Setup capture of incoming frames - IncomingFramesCapture capture = new IncomingFramesCapture(); - - // Wire up stack - ext.setNextIncomingFrames(capture); - - Parser parser = new UnitParser(policy); - parser.configureFromExtensions(Collections.singletonList(ext)); - parser.setIncomingFramesHandler(ext); - - parser.parse(ByteBuffer.wrap(raw)); - - int len = expectedTextDatas.length; - capture.assertFrameCount(len); - capture.assertHasFrame(OpCode.TEXT,len); + tester.assertNegotiated("permessage-deflate"); - for (int i = 0; i < len; i++) - { - WebSocketFrame actual = capture.getFrames().get(i); - String prefix = "Frame[" + i + "]"; - Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT)); - Assert.assertThat(prefix + ".fin",actual.isFin(),is(true)); - Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point - Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false)); - Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false)); + tester.parseIncomingHex(// 1 message + "0xc1 0x08", // header + "0xf3 0x48 0xcd 0xc9 0xc9 0x07 0x00 0x00" // example payload + ); - ByteBuffer expected = BufferUtil.toBuffer(expectedTextDatas[i],StringUtil.__UTF8_CHARSET); - Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining())); - ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice()); - } + tester.assertHasFrames("Hello"); } - private void assertDraftExample(String hexStr, String expectedStr) + /** + * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15. + * <p> + * Section 8.2.3.3: Using a DEFLATE Block with No Compression + */ + @Test + public void testDraft15_DeflateBlockWithNoCompression() { - WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); + Tester tester = clientExtensions.newTester("permessage-deflate"); - // Setup extension - PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); - ext.setBufferPool(new MappedByteBufferPool()); - ext.setPolicy(policy); - ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); - ext.setConfig(config); + tester.assertNegotiated("permessage-deflate"); - // Setup capture of incoming frames - IncomingFramesCapture capture = new IncomingFramesCapture(); + tester.parseIncomingHex(// 1 message / no compression + "0xc1 0x0b 0x00 0x05 0x00 0xfa 0xff 0x48 0x65 0x6c 0x6c 0x6f 0x00" // example frame + ); - // Wire up stack - ext.setNextIncomingFrames(capture); - - // Receive frame - String hex = hexStr.replaceAll("\\s*0x",""); - byte net[] = TypeUtil.fromHexString(hex); - TextFrame frame = new TextFrame(); - frame.setRsv1(true); - frame.setPayload(ByteBuffer.wrap(net)); - - // Send frame into stack - ext.incomingFrame(frame); - - // Verify captured frames. - capture.assertFrameCount(1); - capture.assertHasFrame(OpCode.TEXT,1); - - WebSocketFrame actual = capture.getFrames().pop(); - - String prefix = "frame"; - Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT)); - Assert.assertThat(prefix + ".fin",actual.isFin(),is(true)); - Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point - Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false)); - Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false)); - - ByteBuffer expected = BufferUtil.toBuffer(expectedStr, StandardCharsets.UTF_8); - Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining())); - ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice()); + tester.assertHasFrames("Hello"); } - private void assertDraft12Example(String hexStrCompleteFrame, String... expectedStrs) + /** + * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15. + * <p> + * Section 8.2.3.1: A message compressed using 1 compressed DEFLATE block + */ + @Test + public void testDraft15_Hello_UnCompressedBlock() { - WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); + Tester tester = clientExtensions.newTester("permessage-deflate"); - // Setup extension - PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); - ext.setBufferPool(new MappedByteBufferPool()); - ext.setPolicy(policy); - ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); - ext.setConfig(config); + tester.assertNegotiated("permessage-deflate"); - // Setup capture of incoming frames - IncomingFramesCapture capture = new IncomingFramesCapture(); + tester.parseIncomingHex(//basic, 1 block, compressed with 0 compression level (aka, uncompressed). + "0xc1 0x07 0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00" // example frame + ); - // Wire up stack - ext.setNextIncomingFrames(capture); - - // Receive frame - String hex = hexStrCompleteFrame.replaceAll("\\s*0x",""); - byte net[] = TypeUtil.fromHexString(hex); - - Parser parser = new UnitParser(policy); - parser.configureFromExtensions(Collections.singletonList(ext)); - parser.setIncomingFramesHandler(ext); - parser.parse(ByteBuffer.wrap(net)); - - // Verify captured frames. - int expectedCount = expectedStrs.length; - capture.assertFrameCount(expectedCount); - capture.assertHasFrame(OpCode.TEXT,expectedCount); - - for (int i = 0; i < expectedCount; i++) - { - WebSocketFrame actual = capture.getFrames().pop(); - - String prefix = String.format("frame[%d]",i); - Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT)); - Assert.assertThat(prefix + ".fin",actual.isFin(),is(true)); - Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point - Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false)); - Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false)); - - ByteBuffer expected = BufferUtil.toBuffer(expectedStrs[i],StringUtil.__UTF8_CHARSET); - Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining())); - ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice()); - } + tester.assertHasFrames("Hello"); } /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01. + * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15. + * <p> + * Section 8.2.3.1: A message compressed using 1 compressed DEFLATE block (with fragmentation) */ @Test - public void testDraft01_Hello_UnCompressedBlock() + public void testDraft15_Hello_UnCompressedBlock_Fragmented() { - StringBuilder hex = new StringBuilder(); - // basic, 1 block, compressed with 0 compression level (aka, uncompressed). - hex.append("0x00 0x05 0x00 0xfa 0xff 0x48 0x65 0x6c 0x6c 0x6f 0x00"); - assertDraftExample(hex.toString(),"Hello"); - } + Tester tester = clientExtensions.newTester("permessage-deflate"); - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.1 - */ - @Test - public void testDraft12_Hello_UnCompressedBlock() - { - StringBuilder hex = new StringBuilder(); - // basic, 1 block, compressed with 0 compression level (aka, uncompressed). - hex.append("0xc1 0x07 0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00"); - assertDraft12Example(hex.toString(),"Hello"); - } + tester.assertNegotiated("permessage-deflate"); - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.2 - */ - @Test - public void testDraft12_Hello_NoSharingLZ77SlidingWindow() - { - StringBuilder hex = new StringBuilder(); - // message 1 - hex.append("0xc1 0x07"); // (HEADER added for this test) - hex.append("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00"); - // message 2 - hex.append("0xc1 0x07"); // (HEADER added for this test) - hex.append("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00"); - assertDraft12Example(hex.toString(),"Hello","Hello"); - } - - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.2 - */ - @Test - public void testDraft12_Hello_SharingLZ77SlidingWindow() - { - StringBuilder hex = new StringBuilder(); - // message 1 - hex.append("0xc1 0x07"); // (HEADER added for this test) - hex.append("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00"); - // message 2 - hex.append("0xc1 0x05"); // (HEADER added for this test) - hex.append("0xf2 0x00 0x11 0x00 0x00"); - assertDraft12Example(hex.toString(),"Hello","Hello"); - } + tester.parseIncomingHex(// basic, 1 block, compressed with 0 compression level (aka, uncompressed). + // Fragment 1 + "0x41 0x03 0xf2 0x48 0xcd", + // Fragment 2 + "0x80 0x04 0xc9 0xc9 0x07 0x00"); - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.3 - */ - @Test - public void testDraft12_Hello_NoCompressionBlock() - { - StringBuilder hex = new StringBuilder(); - // basic, 1 block, compressed with no compression. - hex.append("0xc1 0x0b 0x00 0x05 0x00 0xfa 0xff 0x48 0x65 0x6c 0x6c 0x6f 0x00"); - assertDraft12Example(hex.toString(),"Hello"); + tester.assertHasFrames( + new TextFrame().setPayload("He").setFin(false), + new ContinuationFrame().setPayload("llo").setFin(true)); } /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.4 + * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15. + * <p> + * Section 8.2.3.2: Sharing LZ77 Sliding Window */ @Test - public void testDraft12_Hello_Bfinal1() + public void testDraft15_SharingL77SlidingWindow_ContextTakeover() { - StringBuilder hex = new StringBuilder(); - // basic, 1 block, compressed with BFINAL set to 1. - hex.append("0xc1 0x08"); // (HEADER added for this test) - hex.append("0xf3 0x48 0xcd 0xc9 0xc9 0x07 0x00 0x00"); - assertDraft12Example(hex.toString(),"Hello"); - } + Tester tester = clientExtensions.newTester("permessage-deflate"); - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.5 - */ - @Test - public void testDraft12_Hello_TwoDeflateBlocks() - { - StringBuilder hex = new StringBuilder(); - hex.append("0xc1 0x0d"); // (HEADER added for this test) - // 2 deflate blocks - hex.append("0xf2 0x48 0x05 0x00 0x00 0x00 0xff 0xff 0xca 0xc9 0xc9 0x07 0x00"); - assertDraft12Example(hex.toString(),"Hello"); - } + tester.assertNegotiated("permessage-deflate"); - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01. - */ - @Test - public void testDraft01_OneCompressedBlock() - { - // basic, 1 block, compressed. - assertDraftExample("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00","Hello"); - } + tester.parseIncomingHex( // context takeover (2 messages) + // message 1 + "0xc1 0x07", // (HEADER added for this test) + "0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00", + // message 2 + "0xc1 0x07", // (HEADER added for this test) + "0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00"); - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01. - */ - @Test - public void testDraft01_TwoCompressedBlocks() - { - StringBuilder hex = new StringBuilder(); - // BFINAL 0, BTYPE 1, contains "He" - hex.append("0xf2 0x48 0x05 0x00"); - // BFINAL 0, BTYPE 0, no compression, empty block - hex.append("0x00 0x00 0xff 0xff"); - // Block containing "llo" - hex.append("0xca 0xc9 0xc9 0x07 0x00"); - assertDraftExample(hex.toString(),"Hello"); + tester.assertHasFrames("Hello","Hello"); } /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01. + * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15. + * <p> + * Section 8.2.3.2: Sharing LZ77 Sliding Window */ @Test - public void testDraft01_TwoCompressedBlocks_BFinal1() + public void testDraft15_SharingL77SlidingWindow_NoContextTakeover() { - StringBuilder hex = new StringBuilder(); - // Compressed with BFINAL 1 - hex.append("0xf3 0x48 0xcd 0xc9 0xc9 0x07 0x00"); - // last octet at BFINAL 0 and BTYPE 0 - hex.append("0x00"); - assertDraftExample(hex.toString(),"Hello"); + Tester tester = clientExtensions.newTester("permessage-deflate"); + + tester.assertNegotiated("permessage-deflate"); + + tester.parseIncomingHex(// 2 message, shared LZ77 window + // message 1 + "0xc1 0x07", // (HEADER added for this test) + "0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00", + // message 2 + "0xc1 0x05", // (HEADER added for this test) + "0xf2 0x00 0x11 0x00 0x00" + ); + + tester.assertHasFrames("Hello","Hello"); } /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01. + * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15. + * <p> + * Section 8.2.3.5: Two DEFLATE Blocks in 1 Message */ @Test - public void testDraft01_TwoCompressedBlocks_UsingSlidingWindow() + public void testDraft15_TwoDeflateBlocksOneMessage() { - StringBuilder hex = new StringBuilder(); - // basic, 1 block, compressed. - hex.append("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00"); - // (HACK!) BFINAL 0, BTYPE 0, no compression, empty block - hex.append("0x00 0x00 0xff 0xff"); - // if allowed, smaller sized compression using LZ77 sliding window - hex.append("0xf2 0x00 0x11 0x00 0x00"); - assertDraftExample(hex.toString(),"HelloHello"); - } + Tester tester = clientExtensions.newTester("permessage-deflate"); - @Test - public void testPyWebSocket_ToraToraTora() - { - // Captured from Pywebsocket (r781) - "tora" sent 3 times. - String tora1 = "c186b0c7fe48" + "9a0ed102b4c7"; - String tora2 = "c185ccb6cb50" + "e6b7a950cc"; - String tora3 = "c1847b9aac69" + "79fbac69"; - byte rawbuf[] = Hex.asByteArray(tora1 + tora2 + tora3); - assertIncoming(rawbuf,"tora","tora","tora"); + tester.assertNegotiated("permessage-deflate"); + + tester.parseIncomingHex(// 1 message, 1 frame, 2 deflate blocks + "0xc1 0x0d", // (HEADER added for this test) + "0xf2 0x48 0x05 0x00 0x00 0x00 0xff 0xff 0xca 0xc9 0xc9 0x07 0x00" + ); + + tester.assertHasFrames("Hello"); } /** @@ -360,7 +208,7 @@ public class PerMessageDeflateExtensionTest PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); ext.setBufferPool(new MappedByteBufferPool()); ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("permessage-compress"); + ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); ext.setConfig(config); // Setup capture of incoming frames @@ -397,7 +245,7 @@ public class PerMessageDeflateExtensionTest PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); ext.setBufferPool(new MappedByteBufferPool()); ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("permessage-compress"); + ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); ext.setConfig(config); // Setup capture of incoming frames @@ -452,7 +300,7 @@ public class PerMessageDeflateExtensionTest PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); ext.setBufferPool(new MappedByteBufferPool()); ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("permessage-compress"); + ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); ext.setConfig(config); // Setup capture of outgoing frames @@ -481,4 +329,76 @@ public class PerMessageDeflateExtensionTest Assert.assertThat("Frame.payloadLength",actual.getPayloadLength(),is(expected.remaining())); ByteBufferAssert.assertEquals("Frame.payload",expected,actual.getPayload().slice()); } + + @Test + public void testPyWebSocket_Client_NoContextTakeover_ThreeOra() + { + Tester tester = clientExtensions.newTester("permessage-deflate; client_max_window_bits; client_no_context_takeover"); + + tester.assertNegotiated("permessage-deflate"); + + // Captured from Pywebsocket (r790) - 3 messages with similar parts. + + tester.parseIncomingHex( // context takeover (3 messages) + "c1 09 0a c9 2f 4a 0c 01 62 00 00", // ToraTora + "c1 0b 72 2c c9 2f 4a 74 cb 01 12 00 00", // AtoraFlora + "c1 0b 0a c8 c8 c9 2f 4a 0c 01 62 00 00" // PhloraTora + ); + + tester.assertHasFrames("ToraTora","AtoraFlora","PhloraTora"); + } + + @Test + public void testPyWebSocket_Client_ToraToraTora() + { + Tester tester = clientExtensions.newTester("permessage-deflate; client_max_window_bits"); + + tester.assertNegotiated("permessage-deflate"); + + // Captured from Pywebsocket (r790) - "tora" sent 3 times. + + tester.parseIncomingHex( // context takeover (3 messages) + "c1 06 2a c9 2f 4a 04 00", // tora 1 + "c1 05 2a 01 62 00 00", // tora 2 + "c1 04 02 61 00 00" // tora 3 + ); + + tester.assertHasFrames("tora","tora","tora"); + } + + @Test + public void testPyWebSocket_Server_NoContextTakeover_ThreeOra() + { + Tester tester = serverExtensions.newTester("permessage-deflate; client_max_window_bits; client_no_context_takeover"); + + tester.assertNegotiated("permessage-deflate"); + + // Captured from Pywebsocket (r790) - 3 messages with similar parts. + + tester.parseIncomingHex( // context takeover (3 messages) + "c1 89 88 bc 1b b1 82 75 34 fb 84 bd 79 b1 88", // ToraTora + "c1 8b 50 86 88 b2 22 aa 41 9d 1a f2 43 b3 42 86 88", // AtoraFlora + "c1 8b e2 3e 05 53 e8 f6 cd 9a cd 74 09 52 80 3e 05" // PhloraTora + ); + + tester.assertHasFrames("ToraTora","AtoraFlora","PhloraTora"); + } + + @Test + public void testPyWebSocket_Server_ToraToraTora() + { + Tester tester = serverExtensions.newTester("permessage-deflate; client_max_window_bits"); + + tester.assertNegotiated("permessage-deflate"); + + // Captured from Pywebsocket (r790) - "tora" sent 3 times. + + tester.parseIncomingHex( // context takeover (3 messages) + "c1 86 69 39 fe 91 43 f0 d1 db 6d 39", // tora 1 + "c1 85 2d f3 eb 96 07 f2 89 96 2d", // tora 2 + "c1 84 53 ad a5 34 51 cc a5 34" // tora 3 + ); + + tester.assertHasFrames("tora","tora","tora"); + } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java index 2e928206aa..cdabe0713d 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java @@ -18,8 +18,7 @@ package org.eclipse.jetty.websocket.server; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import java.util.concurrent.TimeUnit; @@ -32,7 +31,6 @@ import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; public class FrameCompressionExtensionTest @@ -53,7 +51,6 @@ public class FrameCompressionExtensionTest } @Test - @Ignore("Bug 395444") public void testDeflateFrameExtension() throws Exception { BlockheadClient client = new BlockheadClient(server.getServerUri()); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java new file mode 100644 index 0000000000..2de874f568 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java @@ -0,0 +1,97 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.server; + +import static org.hamcrest.Matchers.*; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; +import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; +import org.eclipse.jetty.websocket.server.helper.EchoServlet; +import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class PerMessageDeflateExtensionTest +{ + private static SimpleServletServer server; + + @BeforeClass + public static void startServer() throws Exception + { + server = new SimpleServletServer(new EchoServlet()); + server.start(); + } + + @AfterClass + public static void stopServer() + { + server.stop(); + } + + /** + * Default configuration for permessage-deflate + */ + @Test + public void testPerMessgeDeflateDefault() throws Exception + { + BlockheadClient client = new BlockheadClient(server.getServerUri()); + client.clearExtensions(); + client.addExtensions("permessage-deflate"); + client.setProtocols("echo"); + + try + { + // Make sure the read times out if there are problems with the implementation + client.setTimeout(TimeUnit.SECONDS,1); + client.connect(); + client.sendStandardRequest(); + HttpResponse resp = client.expectUpgradeResponse(); + + Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("permessage-deflate")); + + String msg = "Hello"; + + // Client sends first message + client.write(new TextFrame().setPayload(msg)); + + IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); + WebSocketFrame frame = capture.getFrames().poll(); + Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); + + // Client sends second message + client.clearCaptured(); + msg = "There"; + client.write(new TextFrame().setPayload(msg)); + + capture = client.readFrames(1,TimeUnit.SECONDS,1); + frame = capture.getFrames().poll(); + Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); + } + finally + { + client.close(); + } + } +} diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties index f3275637f7..644823c1ba 100644 --- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties @@ -2,8 +2,8 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG -org.eclipse.jetty.websocket.LEVEL=WARN -# org.eclipse.jetty.websocket.LEVEL=WARN +# org.eclipse.jetty.websocket.LEVEL=DEBUG +org.eclipse.jetty.websocket.LEVEL=INFO # org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG # org.eclipse.jetty.websocket.server.ab.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG @@ -14,7 +14,7 @@ org.eclipse.jetty.websocket.LEVEL=WARN ### Show state changes on BrowserDebugTool # -- LEAVE THIS AT DEBUG LEVEL -- -org.eclipse.jetty.websocket.server.browser.LEVEL=WARN +org.eclipse.jetty.websocket.server.browser.LEVEL=DEBUG ### Disabling intentional error out of RFCSocket org.eclipse.jetty.websocket.server.helper.RFCSocket.LEVEL=OFF |