// // ======================================================================== // Copyright (c) 1995-2017 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; import static org.hamcrest.Matchers.is; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; 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.common.frames.ContinuationFrame; import org.eclipse.jetty.websocket.common.frames.DataFrame; import org.eclipse.jetty.websocket.common.frames.PingFrame; import org.eclipse.jetty.websocket.common.frames.PongFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.common.test.UnitGenerator; import org.eclipse.jetty.websocket.common.test.UnitParser; import org.eclipse.jetty.websocket.common.util.Hex; 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<WebSocketFrame> send = new ArrayList<>(); send.add(new TextFrame().setPayload("fragment1").setFin(false)); send.add(new ContinuationFrame().setPayload("fragment2").setFin(true)); send.add(new ContinuationFrame().setPayload("fragment3").setFin(false)); // bad frame send.add(new TextFrame().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.parseQuietly(completeBuf); capture.assertErrorCount(1); capture.assertHasFrame(OpCode.TEXT,1); capture.assertHasFrame(OpCode.CONTINUATION,1); } /** * Similar to the server side 5.18 testcase. Text message fragmented as 2 frames, both as opcode=TEXT */ @Test public void testParseCase5_18() { List<WebSocketFrame> send = new ArrayList<>(); send.add(new TextFrame().setPayload("fragment1").setFin(false)); send.add(new TextFrame().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.parseQuietly(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<WebSocketFrame> send = new ArrayList<>(); send.add(new TextFrame().setPayload("f1").setFin(false)); send.add(new ContinuationFrame().setPayload(",f2").setFin(false)); send.add(new PingFrame().setPayload("pong-1")); send.add(new ContinuationFrame().setPayload(",f3").setFin(false)); send.add(new ContinuationFrame().setPayload(",f4").setFin(false)); send.add(new PingFrame().setPayload("pong-2")); send.add(new ContinuationFrame().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.parseQuietly(completeBuf); capture.assertErrorCount(0); capture.assertHasFrame(OpCode.TEXT,1); capture.assertHasFrame(OpCode.CONTINUATION,4); 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<WebSocketFrame> send = new ArrayList<>(); send.add(new PongFrame().setPayload("ping")); send.add(new TextFrame().setPayload("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<WebSocketFrame> send = new ArrayList<>(); int textCount = 0; int continuationCount = 0; int len = msg.length; boolean continuation = false; byte mini[]; for (int i = 0; i < len; i++) { DataFrame frame = null; if (continuation) { frame = new ContinuationFrame(); continuationCount++; } else { frame = new TextFrame(); textCount++; } mini = new byte[1]; mini[0] = msg[i]; frame.setPayload(ByteBuffer.wrap(mini)); boolean isLast = (i >= (len - 1)); frame.setFin(isLast); send.add(frame); continuation = 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,textCount); capture.assertHasFrame(OpCode.CONTINUATION,continuationCount); capture.assertHasFrame(OpCode.CLOSE,1); } @Test public void testParseNothing() { ByteBuffer buf = ByteBuffer.allocate(16); // Put nothing in the buffer. buf.flip(); WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); Parser parser = new UnitParser(policy); IncomingFramesCapture capture = new IncomingFramesCapture(); parser.setIncomingFramesHandler(capture); parser.parse(buf); capture.assertNoErrors(); Assert.assertThat("Frame Count",capture.getFrames().size(),is(0)); } @Test public void testWindowedParseLargeFrame() { // Create frames byte payload[] = new byte[65536]; Arrays.fill(payload,(byte)'*'); List<WebSocketFrame> frames = new ArrayList<>(); TextFrame text = new TextFrame(); text.setPayload(ByteBuffer.wrap(payload)); text.setMask(Hex.asByteArray("11223344")); frames.add(text); frames.add(new CloseInfo(StatusCode.NORMAL).asFrame()); // Build up raw (network bytes) buffer ByteBuffer networkBytes = UnitGenerator.generate(frames); // Parse, in 4096 sized windows WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); Parser parser = new UnitParser(policy); IncomingFramesCapture capture = new IncomingFramesCapture(); parser.setIncomingFramesHandler(capture); while (networkBytes.remaining() > 0) { ByteBuffer window = networkBytes.slice(); int windowSize = Math.min(window.remaining(),4096); window.limit(windowSize); parser.parse(window); networkBytes.position(networkBytes.position() + windowSize); } capture.assertNoErrors(); Assert.assertThat("Frame Count",capture.getFrames().size(),is(2)); WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("Frame[0].opcode",frame.getOpCode(),is(OpCode.TEXT)); ByteBuffer actualPayload = frame.getPayload(); Assert.assertThat("Frame[0].payload.length",actualPayload.remaining(),is(payload.length)); // Should be all '*' characters (if masking is correct) for (int i = actualPayload.position(); i < actualPayload.remaining(); i++) { Assert.assertThat("Frame[0].payload[i]",actualPayload.get(i),is((byte)'*')); } } }