move a test around, add in the basics of a frame builder and some test cases using it as an example, need to decide to complete the builder with masking or smoke it

This commit is contained in:
Jesse McConnell 2012-07-05 18:38:46 -05:00
parent 8fa35533b6
commit 23c6c81615
6 changed files with 628 additions and 21 deletions

View File

@ -14,7 +14,7 @@
<modules>
<module>websocket-core</module>
<!-- <module>websocket-client</module> -->
<module>websocket-client</module>
<module>websocket-server</module>
</modules>

View File

@ -0,0 +1,234 @@
package org.eclipse.jetty.websocket.frames;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.PolicyViolationException;
import org.eclipse.jetty.websocket.protocol.OpCode;
public class FrameBuilder
{
public static FrameBuilder binaryFrame()
{
return new FrameBuilder(new BaseFrame(OpCode.BINARY));
}
public static FrameBuilder closeFrame()
{
return new FrameBuilder(new BaseFrame(OpCode.CLOSE));
}
public static FrameBuilder continuationFrame()
{
return new FrameBuilder(new BaseFrame(OpCode.CONTINUATION));
}
public static FrameBuilder pingFrame()
{
return new FrameBuilder(new BaseFrame(OpCode.PING));
}
public static FrameBuilder pongFrame()
{
return new FrameBuilder(new BaseFrame(OpCode.PONG));
}
public static FrameBuilder textFrame()
{
return new FrameBuilder(new BaseFrame(OpCode.TEXT));
}
private BaseFrame frame;
public FrameBuilder(BaseFrame frame)
{
this.frame = frame;
this.frame.setFin(true); // default
}
public byte[] asByteArray()
{
return BufferUtil.toArray(asByteBuffer());
}
public ByteBuffer asByteBuffer()
{
ByteBuffer buffer = ByteBuffer.allocate(frame.getPayloadLength() + 32 );
byte b;
// Setup fin thru opcode
b = 0x00;
if (frame.isFin())
{
b |= 0x80; // 1000_0000
}
if (frame.isRsv1())
{
b |= 0x40; // 0100_0000
// TODO: extensions can negotiate this (somehow)
throw new PolicyViolationException("RSV1 not allowed to be set");
}
if (frame.isRsv2())
{
b |= 0x20; // 0010_0000
// TODO: extensions can negotiate this (somehow)
throw new PolicyViolationException("RSV2 not allowed to be set");
}
if (frame.isRsv3())
{
b |= 0x10;
// TODO: extensions can negotiate this (somehow)
throw new PolicyViolationException("RSV3 not allowed to be set");
}
byte opcode = frame.getOpCode().getCode();
if (frame.isContinuation())
{
// Continuations are not the same OPCODE
opcode = OpCode.CONTINUATION.getCode();
}
b |= opcode & 0x0F;
buffer.put(b);
// is masked
b = 0x00;
b |= (frame.isMasked()?0x80:0x00);
// payload lengths
int payloadLength = frame.getPayloadLength();
/*
* if length is over 65535 then its a 7 + 64 bit length
*/
if (payloadLength > 0xFF_FF)
{
// we have a 64 bit length
b |= 0x7F;
buffer.put(b); // indicate 8 byte length
buffer.put((byte)0); //
buffer.put((byte)0); // anything over an
buffer.put((byte)0); // int is just
buffer.put((byte)0); // intsane!
buffer.put((byte)((payloadLength >> 24) & 0xFF));
buffer.put((byte)((payloadLength >> 16) & 0xFF));
buffer.put((byte)((payloadLength >> 8) & 0xFF));
buffer.put((byte)(payloadLength & 0xFF));
}
/*
* if payload is ge 126 we have a 7 + 16 bit length
*/
else if (payloadLength >= 0x7E)
{
b |= 0x7E;
buffer.put(b); // indicate 2 byte length
buffer.put((byte)(payloadLength >> 8));
buffer.put((byte)(payloadLength & 0xFF));
}
/*
* we have a 7 bit length
*/
else
{
b |= (payloadLength & 0x7F);
buffer.put(b);
}
// masking key
if (frame.isMasked())
{
// TODO: figure out maskgen
buffer.put(frame.getMask());
}
// now the payload itself
// call back into masking check/method on this class?
// remember the position
int positionPrePayload = buffer.position();
// generate payload
if (frame.getPayloadLength() > 0)
{
BufferUtil.put(frame.getPayload(),buffer);
}
int positionPostPayload = buffer.position();
// mask it if needed
if (frame.isMasked())
{
// move back to remembered position.
int size = positionPostPayload - positionPrePayload;
byte[] mask = frame.getMask();
int pos;
for (int i = 0; i < size; i++)
{
pos = positionPrePayload + i;
// Mask each byte by its absolute position in the bytebuffer
buffer.put(pos,(byte)(buffer.get(pos) ^ mask[i % 4]));
}
}
BufferUtil.flipToFlush(buffer,0);
return buffer;
}
public BaseFrame asFrame()
{
return frame;
}
public FrameBuilder isFin( boolean fin )
{
frame.setFin(fin);
return this;
}
public FrameBuilder isRsv1(boolean rsv1)
{
frame.setRsv1(rsv1);
return this;
}
public FrameBuilder isRsv2(boolean rsv2)
{
frame.setRsv2(rsv2);
return this;
}
public FrameBuilder isRsv3(boolean rsv3)
{
frame.setRsv3(rsv3);
return this;
}
public FrameBuilder withMask(byte[] mask)
{
frame.setMasked(true);
frame.setMask(mask);
return this;
}
public FrameBuilder withPayload(byte[] bytes)
{
frame.setPayload(bytes);
return this;
}
public FrameBuilder withPayload(ByteBuffer payload)
{
frame.setPayload(payload);
return this;
}
}

View File

@ -0,0 +1,58 @@
package org.eclipse.jetty.websocket.frames;
import org.junit.Assert;
import org.junit.Test;
public class FrameBuilderTest
{
public void testSimpleAsFrame()
{
PingFrame frame = (PingFrame)FrameBuilder.pingFrame().asFrame();
Assert.assertTrue(frame instanceof PingFrame);
}
@Test
public void testSimpleInvalidCloseFrameBuilder()
{
byte[] actual = FrameBuilder.closeFrame().isFin(false).asByteArray();
byte[] expected = new byte[]
{ (byte)0x08, (byte)0x00 };
Assert.assertArrayEquals(expected,actual);
}
@Test
public void testSimpleInvalidPingFrameBuilder()
{
byte[] actual = FrameBuilder.pingFrame().isFin(false).asByteArray();
byte[] expected = new byte[]
{ (byte)0x09, (byte)0x00 };
Assert.assertArrayEquals(expected,actual);
}
@Test
public void testSimpleValidCloseFrame()
{
byte[] actual = FrameBuilder.closeFrame().asByteArray();
byte[] expected = new byte[]
{ (byte)0x88, (byte)0x00 };
Assert.assertArrayEquals(expected,actual);
}
@Test
public void testSimpleValidPingFrame()
{
byte[] actual = FrameBuilder.pingFrame().asByteArray();
byte[] expected = new byte[]
{ (byte)0x89, (byte)0x00 };
Assert.assertArrayEquals(expected,actual);
}
}

View File

@ -13,7 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*******************************************************************************/
package org.eclipse.jetty.websocket;
package org.eclipse.jetty.websocket.generator;
/**
*/

View File

@ -13,13 +13,16 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.ByteBufferAssert;
import org.eclipse.jetty.websocket.protocol.OpCode;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.frames.BaseFrame;
import org.eclipse.jetty.websocket.frames.CloseFrame;
import org.eclipse.jetty.websocket.frames.FrameBuilder;
import org.eclipse.jetty.websocket.frames.PingFrame;
import org.eclipse.jetty.websocket.frames.PongFrame;
import org.eclipse.jetty.websocket.frames.TextFrame;
import org.eclipse.jetty.websocket.generator.FrameGenerator;
import org.eclipse.jetty.websocket.protocol.OpCode;
import org.eclipse.jetty.websocket.server.SimpleServletServer;
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
import org.eclipse.jetty.websocket.server.WebSocketServlet;
@ -63,14 +66,14 @@ public class TestABCase5
}
// echo the message back.
try
{
getConnection().write(message);
}
catch (IOException e)
{
e.printStackTrace(System.err);
}
//try
// {
// getConnection().write(message);
// }
//catch (IOException e)
// {
// e.printStackTrace(System.err);
// }
}
}
@ -128,10 +131,46 @@ public class TestABCase5
client.writeRaw(buf2);
// Read frame (hopefully text frame)
// Read frame
Queue<BaseFrame> frames = client.readFrames(1,TimeUnit.MILLISECONDS,500);
CloseFrame closeFrame = (CloseFrame)frames.remove();
Assert.assertThat("CloseFrame.status code",closeFrame.getStatusCode(),is(1002));
BaseFrame frame = (BaseFrame)frames.remove();
Assert.assertTrue("frame should be close frame", frame instanceof CloseFrame);
Assert.assertThat("CloseFrame.status code",((CloseFrame)frame).getStatusCode(),is(1002));
}
finally
{
client.close();
}
}
@Test
public void testCase5_1PingIn2PacketsWithBuilder() throws Exception
{
BlockheadClient client = new BlockheadClient(server.getServerUri());
try
{
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
String fragment1 = "fragment1";
ByteBuffer frame1 = FrameBuilder.pingFrame().isFin(false).withPayload(fragment1.getBytes()).asByteBuffer();
client.writeRaw(frame1);
String fragment2 = "fragment2";
ByteBuffer frame2 = FrameBuilder.pingFrame().withPayload(fragment2.getBytes()).asByteBuffer();
client.writeRaw(frame2);
// Read frame
Queue<BaseFrame> frames = client.readFrames(1,TimeUnit.MILLISECONDS,500);
BaseFrame frame = (BaseFrame)frames.remove();
Assert.assertTrue("frame should be close frame", frame instanceof CloseFrame);
Assert.assertThat("CloseFrame.status code",((CloseFrame)frame).getStatusCode(),is(1002));
}
finally
{
@ -178,10 +217,13 @@ public class TestABCase5
client.writeRaw(buf2);
// Read frame (hopefully text frame)
// Read frame
Queue<BaseFrame> frames = client.readFrames(1,TimeUnit.MILLISECONDS,500);
CloseFrame closeFrame = (CloseFrame)frames.remove();
Assert.assertThat("CloseFrame.status code",closeFrame.getStatusCode(),is(1002));
BaseFrame frame = (BaseFrame)frames.remove();
Assert.assertTrue("frame should be close frame", frame instanceof CloseFrame);
Assert.assertThat("CloseFrame.status code",((CloseFrame)frame).getStatusCode(),is(1002));
}
finally
{
@ -190,9 +232,45 @@ public class TestABCase5
}
@Test
public void testCase5_2PongIn2PacketsWithBuilder() throws Exception
{
BlockheadClient client = new BlockheadClient(server.getServerUri());
try
{
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
String fragment1 = "fragment1";
ByteBuffer frame1 = FrameBuilder.pongFrame().isFin(false).withPayload(fragment1.getBytes()).asByteBuffer();
client.writeRaw(frame1);
String fragment2 = "fragment2";
ByteBuffer frame2 = FrameBuilder.continuationFrame().isFin(false).withPayload(fragment2.getBytes()).asByteBuffer();
client.writeRaw(frame2);
// Read frame
Queue<BaseFrame> frames = client.readFrames(1,TimeUnit.MILLISECONDS,500);
BaseFrame frame = (BaseFrame)frames.remove();
Assert.assertTrue("frame should be close frame", frame instanceof CloseFrame);
Assert.assertThat("CloseFrame.status code",((CloseFrame)frame).getStatusCode(),is(1002));
}
finally
{
client.close();
}
}
@Test
@Ignore ("not re-assembling the strings yet on server side echo")
@Ignore ("not supported in implementation yet, requires server side message aggregation")
public void testCase5_3TextIn2Packets() throws Exception
{
BlockheadClient client = new BlockheadClient(server.getServerUri());
@ -231,10 +309,204 @@ public class TestABCase5
client.writeRaw(buf2);
// Read frame (hopefully text frame)
// Read frame
Queue<BaseFrame> frames = client.readFrames(1,TimeUnit.MILLISECONDS,500);
TextFrame textFrame = (TextFrame)frames.remove();
Assert.assertThat("TextFrame.payload",textFrame.getPayloadUTF8(),is(fragment1 + fragment2));
BaseFrame frame = (BaseFrame)frames.remove();
Assert.assertTrue("frame should be text frame", frame instanceof TextFrame);
Assert.assertThat("TextFrame.payload",((TextFrame)frame).getPayloadUTF8(),is(fragment1 + fragment2));
}
finally
{
client.close();
}
}
@Test
@Ignore ("not supported in implementation yet, requires server side message aggregation")
public void testCase5_6TextPingRemainingText() throws Exception
{
BlockheadClient client = new BlockheadClient(server.getServerUri());
try
{
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
// Send a text packet
ByteBuffer buf = ByteBuffer.allocate(FrameGenerator.OVERHEAD + 2);
BufferUtil.clearToFill(buf);
String fragment1 = "fragment1";
buf.put((byte)(0x00 | OpCode.TEXT.getCode()));
byte b = 0x00; // no masking
b |= fragment1.length() & 0x7F;
buf.put(b);
buf.put(fragment1.getBytes());
BufferUtil.flipToFlush(buf,0);
client.writeRaw(buf);
// Send a ping with payload
ByteBuffer pingBuf = ByteBuffer.allocate(FrameGenerator.OVERHEAD + 2);
BufferUtil.clearToFill(pingBuf);
String pingPayload = "ping payload";
pingBuf.put((byte)(0x00 | OpCode.PING.getCode()));
b = 0x00; // no masking
b |= pingPayload.length() & 0x7F;
pingBuf.put(b);
pingBuf.put(pingPayload.getBytes());
BufferUtil.flipToFlush(pingBuf,0);
client.writeRaw(buf);
// Send remaining text as continuation
ByteBuffer buf2 = ByteBuffer.allocate(FrameGenerator.OVERHEAD + 2);
BufferUtil.clearToFill(buf2);
String fragment2 = "fragment2";
buf2.put((byte)(0x80 | OpCode.CONTINUATION.getCode()));
b = 0x00; // no masking
b |= fragment2.length() & 0x7F;
buf2.put(b);
buf2.put(fragment2.getBytes());
BufferUtil.flipToFlush(buf2,0);
client.writeRaw(buf2);
// Should be 2 frames, pong frame followed by combined echo'd text frame
Queue<BaseFrame> frames = client.readFrames(2,TimeUnit.MILLISECONDS,500);
BaseFrame frame = frames.remove();
Assert.assertTrue("first frame should be pong frame", frame instanceof PongFrame );
ByteBuffer payload1 = ByteBuffer.allocate(pingPayload.length());
payload1.flip();
ByteBufferAssert.assertEquals("payloads should be equal" , payload1, ((PongFrame)frame).getPayload() );
frame = (BaseFrame)frames.remove();
Assert.assertTrue("second frame should be text frame", frame instanceof TextFrame );
Assert.assertThat("TextFrame.payload",((TextFrame)frame).getPayloadUTF8(),is(fragment1 + fragment2));
}
finally
{
client.close();
}
}
@Test
@Ignore ("not supported in implementation yet, requires server side message aggregation")
public void testCase5_6TextPingRemainingTextWithBuilder() throws Exception
{
BlockheadClient client = new BlockheadClient(server.getServerUri());
try
{
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
// Send a text packet
String textPayload1 = "fragment1";
ByteBuffer frame1 = FrameBuilder.textFrame().isFin(false).withPayload(textPayload1.getBytes()).asByteBuffer();
BufferUtil.flipToFlush(frame1,0);
client.writeRaw(frame1);
// Send a ping with payload
String pingPayload = "ping payload";
ByteBuffer frame2 = FrameBuilder.pingFrame().withPayload(pingPayload.getBytes()).asByteBuffer();
BufferUtil.flipToFlush(frame2,0);
client.writeRaw(frame2);
// Send remaining text as continuation
String textPayload2 = "fragment2";
ByteBuffer frame3 = FrameBuilder.continuationFrame().withPayload(textPayload2.getBytes()).asByteBuffer();
BufferUtil.flipToFlush(frame3,0);
client.writeRaw(frame3);
// Should be 2 frames, pong frame followed by combined echo'd text frame
Queue<BaseFrame> frames = client.readFrames(2,TimeUnit.MILLISECONDS,500);
BaseFrame frame = frames.remove();
Assert.assertTrue("first frame should be pong frame", frame instanceof PongFrame );
ByteBuffer payload1 = ByteBuffer.allocate(pingPayload.length());
payload1.flip();
ByteBufferAssert.assertEquals("payloads should be equal" , payload1, ((PongFrame)frame).getPayload() );
frame = (BaseFrame)frames.remove();
Assert.assertTrue("second frame should be text frame", frame instanceof TextFrame );
Assert.assertThat("TextFrame.payload",((TextFrame)frame).getPayloadUTF8(),is(textPayload1 + textPayload2));
}
finally
{
client.close();
}
}
@Test
@Ignore ("AB tests have chop concepts currently unsupported by test...I think, also the string being returns is not Bad Continuation")
public void testCase5_9BadContinuation() throws Exception
{
BlockheadClient client = new BlockheadClient(server.getServerUri());
try
{
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
// Send a text packet
ByteBuffer buf = ByteBuffer.allocate(FrameGenerator.OVERHEAD + 2);
BufferUtil.clearToFill(buf);
String fragment1 = "fragment";
// continutation w / FIN
buf.put((byte)(0x80 | OpCode.CONTINUATION.getCode()));
byte b = 0x00; // no masking
b |= fragment1.length() & 0x7F;
buf.put(b);
buf.put(fragment1.getBytes());
BufferUtil.flipToFlush(buf,0);
client.writeRaw(buf);
// Read frame
Queue<BaseFrame> frames = client.readFrames(1,TimeUnit.MILLISECONDS,500);
BaseFrame frame = (BaseFrame)frames.remove();
Assert.assertTrue("frame should be close frame", frame instanceof CloseFrame);
Assert.assertThat("CloseFrame.status code",((CloseFrame)frame).getStatusCode(),is(1002));
Assert.assertThat("CloseFrame.reason", ((CloseFrame)frame).getReason(),is("Bad Continuation") ); // TODO put close reasons into public strings in impl someplace
}
finally
{

View File

@ -16,6 +16,7 @@ import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.frames.BaseFrame;
import org.eclipse.jetty.websocket.frames.CloseFrame;
import org.eclipse.jetty.websocket.frames.FrameBuilder;
import org.eclipse.jetty.websocket.generator.FrameGenerator;
import org.eclipse.jetty.websocket.protocol.OpCode;
import org.eclipse.jetty.websocket.server.SimpleServletServer;
@ -168,4 +169,46 @@ public class TestABCase7_9
}
}
/**
* Test the requirement of issuing
*/
@Test
public void testCase7_9_XInvalidCloseStatusCodesWithBuilder() throws Exception
{
BlockheadClient client = new BlockheadClient(server.getServerUri());
try
{
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
ByteBuffer frame = FrameBuilder.closeFrame().withMask(new byte[]
{ 0x44, 0x44, 0x44, 0x44 }).asByteBuffer();
ByteBuffer buf = ByteBuffer.allocate(FrameGenerator.OVERHEAD + 2);
BufferUtil.clearToFill(buf);
// Create Close Frame manually, as we are testing the server's behavior of a bad client.
buf.put((byte)(0x80 | OpCode.CLOSE.getCode()));
buf.put((byte)(0x80 | 2));
byte mask[] = new byte[]
{ 0x44, 0x44, 0x44, 0x44 };
buf.put(mask);
int position = buf.position();
buf.putChar((char)this.invalidStatusCode);
remask(buf,position,mask);
BufferUtil.flipToFlush(buf,0);
client.writeRaw(buf);
// Read frame (hopefully text frame)
Queue<BaseFrame> frames = client.readFrames(1,TimeUnit.MILLISECONDS,500);
CloseFrame closeFrame = (CloseFrame)frames.remove();
Assert.assertThat("CloseFrame.status code",closeFrame.getStatusCode(),is(1002));
}
finally
{
client.close();
}
}
}