diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java index 62f943c61af..671e8135581 100644 --- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java @@ -20,10 +20,12 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.Set; @@ -44,7 +46,6 @@ import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.modelmbean.ModelMBean; -import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; @@ -223,25 +224,25 @@ public class ObjectMBean implements DynamicMBean { // Start with blank lazy lists attributes etc. String desc=null; - Object attributes=null; - Object constructors=null; - Object operations=null; - Object notifications=null; + List attributes = new ArrayList(); + List constructors = new ArrayList(); + List operations = new ArrayList(); + List notifications = new ArrayList(); // Find list of classes that can influence the mbean Class o_class=_managed.getClass(); - Object influences = findInfluences(null, _managed.getClass()); + List> influences = findInfluences(new ArrayList>(), _managed.getClass()); - LOG.debug("Influence Count: " + LazyList.size(influences) ); + LOG.debug("Influence Count: " + influences.size() ); // Process Type Annotations ManagedObject primary = o_class.getAnnotation( ManagedObject.class); desc = primary.value(); // For each influence - for (int i=0;i oClass = (Class)LazyList.get(influences, i); + Class oClass = influences.get(i); ManagedObject typeAnnotation = oClass.getAnnotation( ManagedObject.class); @@ -263,7 +264,7 @@ public class ObjectMBean implements DynamicMBean if ( fieldAnnotation != null ) { LOG.debug("Field Annotation found for: " + field.getName() ); - attributes=LazyList.add(attributes, defineAttribute(field.getName(), fieldAnnotation)); + attributes.add( defineAttribute(field.getName(), fieldAnnotation)); } } @@ -277,7 +278,7 @@ public class ObjectMBean implements DynamicMBean { // TODO sort out how a proper name could get here, its a method name as an attribute at this point. LOG.debug("Attribute Annotation found for: " + method.getName() ); - attributes=LazyList.add(attributes,defineAttribute(method.getName(),methodAttributeAnnotation)); + attributes.add(defineAttribute(method.getName(),methodAttributeAnnotation)); } ManagedOperation methodOperationAnnotation = method.getAnnotation(ManagedOperation.class); @@ -285,7 +286,7 @@ public class ObjectMBean implements DynamicMBean if (methodOperationAnnotation != null) { LOG.debug("Method Annotation found for: " + method.getName()); - operations = LazyList.add(operations,defineOperation(method,methodOperationAnnotation)); + operations.add(defineOperation(method,methodOperationAnnotation)); } } @@ -298,10 +299,10 @@ public class ObjectMBean implements DynamicMBean _info = new MBeanInfo(o_class.getName(), desc, - (MBeanAttributeInfo[])LazyList.toArray(attributes, MBeanAttributeInfo.class), - (MBeanConstructorInfo[])LazyList.toArray(constructors, MBeanConstructorInfo.class), - (MBeanOperationInfo[])LazyList.toArray(operations, MBeanOperationInfo.class), - (MBeanNotificationInfo[])LazyList.toArray(notifications, MBeanNotificationInfo.class)); + (MBeanAttributeInfo[])attributes.toArray(new MBeanAttributeInfo[attributes.size()]), + (MBeanConstructorInfo[])constructors.toArray(new MBeanConstructorInfo[constructors.size()]), + (MBeanOperationInfo[])operations.toArray(new MBeanOperationInfo[operations.size()]), + (MBeanNotificationInfo[])notifications.toArray(new MBeanNotificationInfo[notifications.size()])); } } catch(RuntimeException e) @@ -513,12 +514,12 @@ public class ObjectMBean implements DynamicMBean } } - private static Object findInfluences(Object influences, Class aClass) - { + private static List> findInfluences(List> influences, Class aClass) + { if (aClass!=null) { // This class is an influence - influences=LazyList.add(influences,aClass); + influences.add(aClass); // check for mbean influence ManagedObject mo = aClass.getAnnotation(ManagedObject.class); @@ -532,7 +533,7 @@ public class ObjectMBean implements DynamicMBean Class mbean = Class.forName(clazz); LOG.debug("MBean Influence found for " + aClass.getSimpleName() ); - influences = LazyList.add(influences, mbean); + influences.add(mbean); } catch ( ClassNotFoundException cnfe ) { @@ -548,6 +549,7 @@ public class ObjectMBean implements DynamicMBean for (int i=0;ifs!=null && i * This method is used as a replacement of {@link Buffer#flip()}. diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/extensions/fragment/FragmentExtension.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/extensions/fragment/FragmentExtension.java index b4210a17993..325841baac7 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/extensions/fragment/FragmentExtension.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/extensions/fragment/FragmentExtension.java @@ -29,7 +29,7 @@ public class FragmentExtension extends Extension private int maxLength = -1; @Override - public void output(C context, Callback callback, WebSocketFrame frame) throws IOException + public void output(C context, Callback callback, final WebSocketFrame frame) throws IOException { if (frame.isControlFrame()) { diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/AbstractWebSocketConnection.java index 5fe68ec9ba9..9a0f970d639 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/AbstractWebSocketConnection.java @@ -106,6 +106,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } flushing = false; } + flush(); } @Override @@ -288,7 +289,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp else if (filled < 0) { LOG.debug("read - EOF Reached"); - disconnect(false); + // disconnect(false); // FIXME Simone says this is bad return -1; } else @@ -376,15 +377,9 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp try { endpoint.write(frameBytes.context,frameBytes,buffer); - long count = writes.incrementAndGet(); - if ((count % 10) == 0) - { - LOG.info("Server wrote {} ByteBuffers",count); - } } catch (Throwable t) { - LOG.debug(t); frameBytes.failed(frameBytes.context,t); } } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/ControlFrameBytes.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/ControlFrameBytes.java index 2bb77153026..0aebfd7a0b8 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/ControlFrameBytes.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/ControlFrameBytes.java @@ -18,11 +18,14 @@ package org.eclipse.jetty.websocket.io; import java.nio.ByteBuffer; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.protocol.OpCode; import org.eclipse.jetty.websocket.protocol.WebSocketFrame; public class ControlFrameBytes extends FrameBytes { + private static final Logger LOG = Log.getLogger(ControlFrameBytes.class); private ByteBuffer buffer; public ControlFrameBytes(AbstractWebSocketConnection connection, Callback callback, C context, WebSocketFrame frame) @@ -32,6 +35,7 @@ public class ControlFrameBytes extends FrameBytes @Override public void completed(C context) { + LOG.debug("completed() - frame: {}",frame); connection.getBufferPool().release(buffer); super.completed(context); @@ -39,7 +43,7 @@ public class ControlFrameBytes extends FrameBytes if (frame.getOpCode() == OpCode.CLOSE) { // Disconnect the connection (no more packets/frames) - connection.disconnect(false); + connection.disconnect(true); // FIXME (should only shutdown output here) } } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/FrameBytes.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/FrameBytes.java index a1260f5f440..c52e8a50cf7 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/FrameBytes.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/FrameBytes.java @@ -57,7 +57,7 @@ public abstract class FrameBytes implements Callback, Runnable { if (LOG.isDebugEnabled()) { - LOG.debug("completed({})",context); + LOG.debug("completed({}) - {}",context,this.getClass().getName()); } cancelTask(); connection.complete(this); diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Parser.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Parser.java index fa49a5e7055..eead9e91541 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Parser.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Parser.java @@ -49,6 +49,7 @@ public class Parser private int cursor = 0; // Frame private WebSocketFrame frame; + private WebSocketFrame priorDataFrame; private byte lastDataOpcode; // payload specific private ByteBuffer payload; @@ -177,8 +178,15 @@ public class Parser { LOG.debug("{} Parsed Frame: {}",policy.getBehavior(),frame); notifyFrame(frame); + if (frame.isDataFrame() && frame.isFin()) + { + priorDataFrame = null; + } + else + { + priorDataFrame = frame; + } } - } catch (WebSocketException e) { @@ -267,20 +275,35 @@ public class Parser throw new ProtocolException("RSV3 not allowed to be set"); } - if (OpCode.isControlFrame(opcode) && !fin) - { - throw new ProtocolException("Fragmented Control Frame [" + OpCode.name(opcode) + "]"); - } + boolean isContinuation = false; - if (opcode == OpCode.CONTINUATION) + if (OpCode.isControlFrame(opcode)) { - if (frame == null) + // control frame validation + if (!fin) { - throw new ProtocolException("Fragment continuation frame without prior !FIN"); + throw new ProtocolException("Fragmented Control Frame [" + OpCode.name(opcode) + "]"); + } + } + else if (opcode == OpCode.CONTINUATION) + { + isContinuation = true; + // continuation validation + if (priorDataFrame == null) + { + throw new ProtocolException("CONTINUATION frame without prior !FIN"); } // Be careful to use the original opcode opcode = lastDataOpcode; } + else if (OpCode.isDataFrame(opcode)) + { + // data validation + if ((priorDataFrame != null) && (!priorDataFrame.isFin())) + { + throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION"); + } + } // base framing flags frame = new WebSocketFrame(); @@ -289,6 +312,7 @@ public class Parser frame.setRsv2(rsv2); frame.setRsv3(rsv3); frame.setOpCode(opcode); + frame.setContinuation(isContinuation); if (frame.isDataFrame()) { @@ -392,8 +416,8 @@ public class Parser case MASK_BYTES: { byte b = buffer.get(); + frame.getMask()[4 - cursor] = b; --cursor; - frame.getMask()[cursor] = b; if (cursor == 0) { // special case for empty payloads (no more bytes left in buffer) @@ -435,7 +459,7 @@ public class Parser * the payload buffer * @return true if payload is done reading, false if incomplete */ - public boolean parsePayload(ByteBuffer buffer) + private boolean parsePayload(ByteBuffer buffer) { if (payloadLength == 0) { @@ -449,6 +473,7 @@ public class Parser getPolicy().assertValidPayloadLength(payloadLength); frame.assertValid(); payload = ByteBuffer.allocate(payloadLength); + BufferUtil.clearToFill(payload); } BufferUtil.put(buffer,payload); @@ -457,14 +482,18 @@ public class Parser { BufferUtil.flipToFlush(payload,0); + LOG.debug("PreMask: {}",BufferUtil.toDetailString(payload)); // demask (if needed) if (frame.isMasked()) { byte mask[] = frame.getMask(); + int offset; + int start = payload.position(); int end = payload.limit(); - for (int i = payload.position(); i < end; i++) + for (int i = start; i < end; i++) { - payload.put(i,(byte)(payload.get(i) ^ mask[i % 4])); + offset = (i - start); + payload.put(i,(byte)(payload.get(i) ^ mask[offset % 4])); } } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/WebSocketFrame.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/WebSocketFrame.java index 78459b17c39..b4f7b035ea6 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/WebSocketFrame.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/WebSocketFrame.java @@ -113,6 +113,7 @@ public class WebSocketFrame implements Frame */ public WebSocketFrame(byte opcode) { + reset(); this.opcode = opcode; } @@ -126,7 +127,7 @@ public class WebSocketFrame implements Frame */ public WebSocketFrame(WebSocketFrame copy) { - fin = copy.rsv1; + fin = copy.fin; rsv1 = copy.rsv2; rsv2 = copy.rsv2; rsv3 = copy.rsv3; diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/DeflateFrameExtensionTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/DeflateFrameExtensionTest.java index 90ba98fe304..2136f6d8633 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/DeflateFrameExtensionTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/DeflateFrameExtensionTest.java @@ -232,7 +232,7 @@ public class DeflateFrameExtensionTest Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT)); Assert.assertThat(prefix + ".fin",actual.isFin(),is(true)); - Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),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)); diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/FragmentExtensionTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/FragmentExtensionTest.java index d22c966b1f5..0fc69b6e0e3 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/FragmentExtensionTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/FragmentExtensionTest.java @@ -88,7 +88,7 @@ public class FragmentExtensionTest quote.add("a single experiment can prove me wrong."); quote.add("-- Albert Einstein"); - // Manually compress frame and pass into extension + // Manually create frame and pass into extension for (String q : quote) { WebSocketFrame frame = WebSocketFrame.text(q); diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/GeneratorTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/GeneratorTest.java index 3b1b0e60c85..c184dab6664 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/GeneratorTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/GeneratorTest.java @@ -16,19 +16,13 @@ import org.junit.Test; public class GeneratorTest { - /** * Prevent regression of masking of many packets. */ @Test public void testManyMasked() { - byte[] MASK = - { 0x11, 0x22, 0x33, 0x44 }; - int pingCount = 1000; - - // the generator - Generator generator = new UnitGenerator(); + int pingCount = 10; // Prepare frames List send = new ArrayList<>(); @@ -39,40 +33,15 @@ public class GeneratorTest } send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - // Generate into single bytebuffer - int buflen = 0; - for (WebSocketFrame f : send) - { - buflen += f.getPayloadLength() + Generator.OVERHEAD; - } - ByteBuffer completeBuf = ByteBuffer.allocate(buflen); - BufferUtil.clearToFill(completeBuf); - - // Generate frames - for (WebSocketFrame f : send) - { - f.setMask(MASK); // make sure we have mask set - ByteBuffer slice = f.getPayload().slice(); - BufferUtil.put(generator.generate(f),completeBuf); - f.setPayload(slice); - } - BufferUtil.flipToFlush(completeBuf,0); + ByteBuffer completeBuf = UnitGenerator.generate(send); // Parse complete buffer (5 bytes at a time) - WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); - Parser parser = new Parser(policy); + UnitParser parser = new UnitParser(); IncomingFramesCapture capture = new IncomingFramesCapture(); parser.setIncomingFramesHandler(capture); int segmentSize = 5; - while (completeBuf.remaining() > 0) - { - ByteBuffer part = completeBuf.slice(); - int len = Math.min(segmentSize,part.remaining()); - part.limit(part.position() + len); - parser.parse(part); - completeBuf.position(completeBuf.position() + len); - } + parser.parseSlowly(completeBuf,segmentSize); // Assert validity of frame int frameCount = send.size(); diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/ParserTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/ParserTest.java index 3fc300d2a85..34f78fd1834 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/ParserTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/ParserTest.java @@ -18,15 +18,151 @@ package org.eclipse.jetty.websocket.protocol; import static org.hamcrest.Matchers.*; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketPolicy; -import org.eclipse.jetty.websocket.protocol.Parser; import org.junit.Assert; import org.junit.Test; public class ParserTest { + /** + * Similar to the server side 5.15 testcase. A normal 2 fragment text text message, followed by another continuation. + */ + @Test + public void testParseCase5_15() + { + List send = new ArrayList<>(); + send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment1").setFin(false)); + send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment2").setFin(true)); + send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment3").setFin(false)); // bad frame + send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment4").setFin(true)); + send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); + + ByteBuffer completeBuf = UnitGenerator.generate(send); + UnitParser parser = new UnitParser(); + IncomingFramesCapture capture = new IncomingFramesCapture(); + parser.setIncomingFramesHandler(capture); + parser.parse(completeBuf); + + capture.assertErrorCount(1); + capture.assertHasFrame(OpCode.TEXT,2); + } + + /** + * Similar to the server side 5.18 testcase. Text message fragmented as 2 frames, both as opcode=TEXT + */ + @Test + public void testParseCase5_18() + { + List send = new ArrayList<>(); + send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment1").setFin(false)); + send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment2").setFin(true)); // bad frame, must be continuation + send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); + + ByteBuffer completeBuf = UnitGenerator.generate(send); + UnitParser parser = new UnitParser(); + IncomingFramesCapture capture = new IncomingFramesCapture(); + parser.setIncomingFramesHandler(capture); + parser.parse(completeBuf); + + capture.assertErrorCount(1); + capture.assertHasFrame(OpCode.TEXT,1); // fragment 1 + } + + /** + * Similar to the server side 5.19 testcase. + * text message, send in 5 frames/fragments, with 2 pings in the mix. + */ + @Test + public void testParseCase5_19() + { + List send = new ArrayList<>(); + send.add(new WebSocketFrame(OpCode.TEXT).setPayload("f1").setFin(false)); + send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f2").setFin(false)); + send.add(new WebSocketFrame(OpCode.PING).setPayload("pong-1")); + send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f3").setFin(false)); + send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f4").setFin(false)); + send.add(new WebSocketFrame(OpCode.PING).setPayload("pong-2")); + send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f5").setFin(true)); + send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); + + ByteBuffer completeBuf = UnitGenerator.generate(send); + UnitParser parser = new UnitParser(); + IncomingFramesCapture capture = new IncomingFramesCapture(); + parser.setIncomingFramesHandler(capture); + parser.parse(completeBuf); + + capture.assertErrorCount(0); + capture.assertHasFrame(OpCode.TEXT,5); + capture.assertHasFrame(OpCode.CLOSE,1); + capture.assertHasFrame(OpCode.PING,2); + } + + /** + * Similar to the server side 5.6 testcase. pong, then text, then close frames. + */ + @Test + public void testParseCase5_6() + { + List send = new ArrayList<>(); + send.add(WebSocketFrame.pong().setPayload("ping")); + send.add(WebSocketFrame.text("hello, world")); + send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); + + ByteBuffer completeBuf = UnitGenerator.generate(send); + UnitParser parser = new UnitParser(); + IncomingFramesCapture capture = new IncomingFramesCapture(); + parser.setIncomingFramesHandler(capture); + parser.parse(completeBuf); + + capture.assertErrorCount(0); + capture.assertHasFrame(OpCode.TEXT,1); + capture.assertHasFrame(OpCode.CLOSE,1); + capture.assertHasFrame(OpCode.PONG,1); + } + + /** + * Similar to the server side 6.2.3 testcase. Lots of small 1 byte UTF8 Text frames, representing 1 overall text message. + */ + @Test + public void testParseCase6_2_3() + { + String utf8 = "Hello-\uC2B5@\uC39F\uC3A4\uC3BC\uC3A0\uC3A1-UTF-8!!"; + byte msg[] = StringUtil.getUtf8Bytes(utf8); + + List send = new ArrayList<>(); + int len = msg.length; + byte opcode = OpCode.TEXT; + byte mini[]; + for (int i = 0; i < len; i++) + { + WebSocketFrame frame = new WebSocketFrame(opcode); + mini = new byte[1]; + mini[0] = msg[i]; + frame.setPayload(mini); + boolean isLast = (i >= (len - 1)); + frame.setFin(isLast); + send.add(frame); + opcode = OpCode.CONTINUATION; + } + send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); + + ByteBuffer completeBuf = UnitGenerator.generate(send); + UnitParser parser = new UnitParser(); + IncomingFramesCapture capture = new IncomingFramesCapture(); + parser.setIncomingFramesHandler(capture); + parser.parse(completeBuf); + + capture.assertErrorCount(0); + capture.assertHasFrame(OpCode.TEXT,len); + capture.assertHasFrame(OpCode.CLOSE,1); + } + @Test public void testParseNothing() { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/UnitGenerator.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/UnitGenerator.java index 9493da489f2..7b0b6087a0e 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/UnitGenerator.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/UnitGenerator.java @@ -15,7 +15,11 @@ //======================================================================== package org.eclipse.jetty.websocket.protocol; +import java.nio.ByteBuffer; +import java.util.List; + import org.eclipse.jetty.io.StandardByteBufferPool; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.websocket.api.WebSocketPolicy; /** @@ -23,6 +27,37 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy; */ public class UnitGenerator extends Generator { + public static ByteBuffer generate(List frames) + { + // Create non-symmetrical mask (shows mask bytes order issues) + byte[] MASK = + { 0x11, 0x22, 0x33, 0x44 }; + + // the generator + Generator generator = new UnitGenerator(); + + // Generate into single bytebuffer + int buflen = 0; + for (WebSocketFrame f : frames) + { + buflen += f.getPayloadLength() + Generator.OVERHEAD; + } + ByteBuffer completeBuf = ByteBuffer.allocate(buflen); + BufferUtil.clearToFill(completeBuf); + + // Generate frames + for (WebSocketFrame f : frames) + { + f.setMask(MASK); // make sure we have mask set + ByteBuffer slice = f.getPayload().slice(); + BufferUtil.put(generator.generate(f),completeBuf); + f.setPayload(slice); + } + + BufferUtil.flipToFlush(completeBuf,0); + return completeBuf; + } + public UnitGenerator() { super(WebSocketPolicy.newServerPolicy(),new StandardByteBufferPool()); diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/UnitParser.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/UnitParser.java new file mode 100644 index 00000000000..860f02da43d --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/UnitParser.java @@ -0,0 +1,29 @@ +package org.eclipse.jetty.websocket.protocol; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.websocket.api.WebSocketPolicy; + +public class UnitParser extends Parser +{ + public UnitParser() + { + super(WebSocketPolicy.newServerPolicy()); + } + + private void parsePartial(ByteBuffer buf, int numBytes) + { + int len = Math.min(numBytes,buf.remaining()); + byte arr[] = new byte[len]; + buf.get(arr,0,len); + this.parse(ByteBuffer.wrap(arr)); + } + + public void parseSlowly(ByteBuffer buf, int segmentSize) + { + while (buf.remaining() > 0) + { + parsePartial(buf,segmentSize); + } + } +} diff --git a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties index fbf69445511..c100df43afe 100644 --- a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties @@ -1,2 +1,3 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.websocket.LEVEL=WARN \ No newline at end of file +org.eclipse.jetty.websocket.LEVEL=WARN +# org.eclipse.jetty.websocket.protocol.Parser.LEVEL=DEBUG \ No newline at end of file diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/DeflateExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/DeflateExtensionTest.java index 03ef8bcc21c..3f854d4ecdb 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/DeflateExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/DeflateExtensionTest.java @@ -47,7 +47,7 @@ public class DeflateExtensionTest } @Test - @Ignore /* FIXME */ + @Ignore("Not yet working") 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/WebSocketServletRFCTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java index 2044fe97a01..25cb7cba131 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java @@ -233,6 +233,7 @@ public class WebSocketServletRFCTest } @Test + @Ignore("Should be moved to Fuzzer") public void testMaxBinarySize() throws Exception { BlockheadClient client = new BlockheadClient(server.getServerUri()); @@ -274,6 +275,7 @@ public class WebSocketServletRFCTest } @Test + @Ignore("Should be moved to Fuzzer") public void testMaxTextSize() throws Exception { BlockheadClient client = new BlockheadClient(server.getServerUri()); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java index 2bcc408ea1d..90ed4497ffa 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java @@ -23,7 +23,6 @@ import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.protocol.CloseInfo; import org.eclipse.jetty.websocket.protocol.WebSocketFrame; import org.eclipse.jetty.websocket.server.ab.Fuzzer.SendMode; -import org.junit.Ignore; import org.junit.Test; public class TestABCase1 extends AbstractABCase @@ -184,7 +183,6 @@ public class TestABCase1 extends AbstractABCase * Echo 65535 byte TEXT message (uses medium 2 byte payload length) */ @Test - @Ignore /* FIXME */ public void testCase1_1_6() throws Exception { byte payload[] = new byte[65535]; @@ -216,7 +214,6 @@ public class TestABCase1 extends AbstractABCase * Echo 65536 byte TEXT message (uses large 8 byte payload length) */ @Test - @Ignore /* FIXME */ public void testCase1_1_7() throws Exception { byte payload[] = new byte[65536]; @@ -252,7 +249,6 @@ public class TestABCase1 extends AbstractABCase * This is done to test the parsing together of the frame on the server side. */ @Test - @Ignore /* FIXME */ public void testCase1_1_8() throws Exception { byte payload[] = new byte[65536]; @@ -438,7 +434,6 @@ public class TestABCase1 extends AbstractABCase * Echo 65535 byte BINARY message (uses medium 2 byte payload length) */ @Test - @Ignore /* FIXME */ public void testCase1_2_6() throws Exception { byte payload[] = new byte[65535]; @@ -470,7 +465,6 @@ public class TestABCase1 extends AbstractABCase * Echo 65536 byte BINARY message (uses large 8 byte payload length) */ @Test - @Ignore /* FIXME */ public void testCase1_2_7() throws Exception { byte payload[] = new byte[65536]; @@ -506,7 +500,6 @@ public class TestABCase1 extends AbstractABCase * This is done to test the parsing together of the frame on the server side. */ @Test - @Ignore /* FIXME */ public void testCase1_2_8() throws Exception { byte payload[] = new byte[65536]; diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java index 5d9f12eec04..bf66c78cac3 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java @@ -26,7 +26,6 @@ import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.protocol.CloseInfo; import org.eclipse.jetty.websocket.protocol.OpCode; import org.eclipse.jetty.websocket.protocol.WebSocketFrame; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -242,7 +241,6 @@ public class TestABCase5 extends AbstractABCase * Send text fragmented properly in 2 frames, then continuation!fin, then text unfragmented. */ @Test - @Ignore /* FIXME */ public void testCase5_15() throws Exception { List send = new ArrayList<>(); @@ -253,7 +251,7 @@ public class TestABCase5 extends AbstractABCase send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); List expect = new ArrayList<>(); - send.add(WebSocketFrame.text("fragment1fragment2")); + expect.add(WebSocketFrame.text("fragment1fragment2")); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); Fuzzer fuzzer = new Fuzzer(this); @@ -338,7 +336,6 @@ public class TestABCase5 extends AbstractABCase * text message fragmented in 2 frames, both frames as opcode=TEXT */ @Test - @Ignore /* FIXME */ public void testCase5_18() throws Exception { List send = new ArrayList<>(); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java index 08d60fc3734..2719cbc19c7 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java @@ -37,6 +37,27 @@ import org.junit.runner.RunWith; @RunWith(AdvancedRunner.class) public class TestABCase6 extends AbstractABCase { + /** + * Split a message byte array into a series of fragments (frames + continuations) of 1 byte message contents each. + */ + protected void fragmentText(List frames, byte msg[]) + { + int len = msg.length; + byte opcode = OpCode.TEXT; + byte mini[]; + for (int i = 0; i < len; i++) + { + WebSocketFrame frame = new WebSocketFrame(opcode); + mini = new byte[1]; + mini[0] = msg[i]; + frame.setPayload(mini); + boolean isLast = (i >= (len - 1)); + frame.setFin(isLast); + frames.add(frame); + opcode = OpCode.CONTINUATION; + } + } + /** * text message, 1 frame, 0 length */ @@ -198,18 +219,7 @@ public class TestABCase6 extends AbstractABCase byte msg[] = StringUtil.getUtf8Bytes(utf8); List send = new ArrayList<>(); - int len = msg.length; - byte opcode = OpCode.TEXT; - byte mini[]; - for (int i = 0; i < len; i++) - { - WebSocketFrame frame = new WebSocketFrame(opcode); - mini = new byte[1]; - mini[0] = msg[i]; - frame.setPayload(mini); - frame.setFin(!(i < (len - 1))); - send.add(frame); - } + fragmentText(send,msg); send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); List expect = new ArrayList<>(); @@ -239,18 +249,7 @@ public class TestABCase6 extends AbstractABCase byte msg[] = Hex.asByteArray("CEBAE1BDB9CF83CEBCCEB5"); List send = new ArrayList<>(); - int len = msg.length; - byte opcode = OpCode.TEXT; - byte mini[]; - for (int i = 0; i < len; i++) - { - WebSocketFrame frame = new WebSocketFrame(opcode); - mini = new byte[1]; - mini[0] = msg[i]; - frame.setPayload(mini); - frame.setFin(!(i < (len - 1))); - send.add(frame); - } + fragmentText(send,msg); send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); List expect = new ArrayList<>(); @@ -309,18 +308,7 @@ public class TestABCase6 extends AbstractABCase byte invalid[] = Hex.asByteArray("CEBAE1BDB9CF83CEBCCEB5EDA080656469746564"); List send = new ArrayList<>(); - int len = invalid.length; - byte opcode = OpCode.TEXT; - byte mini[]; - for (int i = 0; i < len; i++) - { - WebSocketFrame frame = new WebSocketFrame(opcode); - mini = new byte[1]; - mini[0] = invalid[i]; - frame.setPayload(mini); - frame.setFin(!(i < (len - 1))); - send.add(frame); - } + fragmentText(send,invalid); send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); List expect = new ArrayList<>(); 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 16107507754..880ca590b89 100644 --- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties @@ -1,5 +1,6 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog org.eclipse.jetty.LEVEL=WARN +# org.eclipse.jetty.io.ChannelEndPoint.LEVEL=INFO org.eclipse.jetty.server.LEVEL=WARN # org.eclipse.jetty.websocket.LEVEL=WARN org.eclipse.jetty.websocket.server.helper.RFCSocket.LEVEL=OFF @@ -10,6 +11,8 @@ org.eclipse.jetty.websocket.server.helper.RFCSocket.LEVEL=OFF # org.eclipse.jetty.util.thread.QueuedThreadPool.LEVEL=DEBUG # org.eclipse.jetty.io.SelectorManager.LEVEL=INFO # org.eclipse.jetty.websocket.LEVEL=DEBUG +# org.eclipse.jetty.websocket.io.FrameBytes.LEVEL=DEBUG +# org.eclipse.jetty.websocket.io.ControlFrameBytes.LEVEL=DEBUG # org.eclipse.jetty.websocket.driver.WebSocketEventDriver.LEVEL=DEBUG # org.eclipse.jetty.websocket.extensions.LEVEL=DEBUG # org.eclipse.jetty.websocket.protocol.Generator.LEVEL=INFO