WebSocket - reducing memory footprint of WebSocketFrame by not using boolean fields

This commit is contained in:
Joakim Erdfelt 2013-08-13 11:34:05 -07:00
parent 176c7d22e4
commit 980effaede
8 changed files with 111 additions and 138 deletions

View File

@ -2,7 +2,7 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=WARN
# org.eclipse.jetty.websocket.LEVEL=DEBUG
org.eclipse.jetty.websocket.LEVEL=INFO
# org.eclipse.jetty.websocket.LEVEL=INFO
# org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG

View File

@ -101,6 +101,7 @@ public interface Frame
*
* @return true if final frame.
*/
// FIXME: remove
public boolean isLast();
public boolean isMasked();

View File

@ -218,7 +218,7 @@ public class Generator
}
if (frame.isRsv3())
{
b |= 0x10;
b |= 0x10; // 0001_0000
}
// NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons

View File

@ -85,12 +85,19 @@ public class WebSocketFrame implements Frame
return new WebSocketFrame(OpCode.TEXT).setPayload(msg);
}
// FIXME: make each boolean/bit part of 1 byte (instead of multiple booleans) to save memory
private boolean fin = true;
private boolean rsv1 = false;
private boolean rsv2 = false;
private boolean rsv3 = false;
protected byte opcode = OpCode.UNDEFINED;
/**
* Combined FIN + RSV1 + RSV2 + RSV3 + OpCode byte.
* <p>
* <pre>
* 1000_0000 (0x80) = fin
* 0100_0000 (0x40) = rsv1
* 0010_0000 (0x20) = rsv2
* 0001_0000 (0x10) = rsv3
* 0000_1111 (0x0F) = opcode
* </pre>
*/
protected byte finRsvOp;
private boolean masked = false;
private byte mask[];
/**
@ -138,11 +145,13 @@ public class WebSocketFrame implements Frame
else
{
// Copy manually
fin = frame.isFin();
rsv1 = frame.isRsv1();
rsv2 = frame.isRsv2();
rsv3 = frame.isRsv3();
opcode = frame.getType().getOpCode();
finRsvOp = 0x00;
finRsvOp |= frame.isFin() ? 0x80 : 0x00;
finRsvOp |= frame.isRsv1() ? 0x40 : 0x00;
finRsvOp |= frame.isRsv2() ? 0x20 : 0x00;
finRsvOp |= frame.isRsv3() ? 0x10 : 0x00;
finRsvOp |= frame.getOpCode() & 0x0F;
type = frame.getType();
masked = frame.isMasked();
mask = null;
@ -187,7 +196,7 @@ public class WebSocketFrame implements Frame
public void assertValid()
{
if (OpCode.isControlFrame(opcode))
if (isControlFrame())
{
if (getPayloadLength() > WebSocketFrame.MAX_CONTROL_PAYLOAD)
{
@ -195,22 +204,22 @@ public class WebSocketFrame implements Frame
+ MAX_CONTROL_PAYLOAD + "]");
}
if (fin == false)
if ((finRsvOp & 0x80) == 0)
{
throw new ProtocolException("Cannot have FIN==false on Control frames");
}
if (rsv1 == true)
if ((finRsvOp & 0x40) != 0)
{
throw new ProtocolException("Cannot have RSV1==true on Control frames");
}
if (rsv2 == true)
if ((finRsvOp & 0x20) != 0)
{
throw new ProtocolException("Cannot have RSV2==true on Control frames");
}
if (rsv3 == true)
if ((finRsvOp & 0x10) != 0)
{
throw new ProtocolException("Cannot have RSV3==true on Control frames");
}
@ -224,11 +233,7 @@ public class WebSocketFrame implements Frame
private final void copy(WebSocketFrame copy, ByteBuffer payload)
{
fin = copy.fin;
rsv1 = copy.rsv1;
rsv2 = copy.rsv2;
rsv3 = copy.rsv3;
opcode = copy.opcode;
finRsvOp = copy.finRsvOp;
type = copy.type;
masked = copy.masked;
mask = null;
@ -278,7 +283,7 @@ public class WebSocketFrame implements Frame
{
return false;
}
if (fin != other.fin)
if (finRsvOp != other.finRsvOp)
{
return false;
}
@ -290,22 +295,6 @@ public class WebSocketFrame implements Frame
{
return false;
}
if (opcode != other.opcode)
{
return false;
}
if (rsv1 != other.rsv1)
{
return false;
}
if (rsv2 != other.rsv2)
{
return false;
}
if (rsv3 != other.rsv3)
{
return false;
}
return true;
}
@ -336,7 +325,7 @@ public class WebSocketFrame implements Frame
@Override
public final byte getOpCode()
{
return opcode;
return (byte)(finRsvOp & 0x0F);
}
/**
@ -386,13 +375,8 @@ public class WebSocketFrame implements Frame
result = (prime * result) + (continuation?1231:1237);
result = (prime * result) + continuationIndex;
result = (prime * result) + ((data == null)?0:data.hashCode());
result = (prime * result) + (fin?1231:1237);
result = (prime * result) + finRsvOp;
result = (prime * result) + Arrays.hashCode(mask);
result = (prime * result) + (masked?1231:1237);
result = (prime * result) + opcode;
result = (prime * result) + (rsv1?1231:1237);
result = (prime * result) + (rsv2?1231:1237);
result = (prime * result) + (rsv3?1231:1237);
return result;
}
@ -410,29 +394,30 @@ public class WebSocketFrame implements Frame
public boolean isControlFrame()
{
return OpCode.isControlFrame(opcode);
return OpCode.isControlFrame(getOpCode());
}
public boolean isDataFrame()
{
return OpCode.isDataFrame(opcode);
return OpCode.isDataFrame(getOpCode());
}
@Override
public boolean isFin()
{
return fin;
return (byte)(finRsvOp & 0x80) != 0;
}
@Override
public boolean isLast()
{
return fin;
return isFin();
}
// FIXME: remove
public boolean isLastFrame()
{
return fin;
return isFin();
}
@Override
@ -444,19 +429,19 @@ public class WebSocketFrame implements Frame
@Override
public boolean isRsv1()
{
return rsv1;
return (byte)(finRsvOp & 0x40) != 0;
}
@Override
public boolean isRsv2()
{
return rsv2;
return (byte)(finRsvOp & 0x20) != 0;
}
@Override
public boolean isRsv3()
{
return rsv3;
return (byte)(finRsvOp & 0x10) != 0;
}
/**
@ -494,11 +479,7 @@ public class WebSocketFrame implements Frame
public void reset()
{
fin = true;
rsv1 = false;
rsv2 = false;
rsv3 = false;
opcode = -1;
finRsvOp = (byte) 0x80; // FIN (!RSV, opcode 0)
masked = false;
data = null;
payloadLength = 0;
@ -521,7 +502,8 @@ public class WebSocketFrame implements Frame
public WebSocketFrame setFin(boolean fin)
{
this.fin = fin;
// set bit 1
this.finRsvOp = (byte)((finRsvOp & 0x7F) | (fin? 0x80:0x00));
return this;
}
@ -540,7 +522,7 @@ public class WebSocketFrame implements Frame
public WebSocketFrame setOpCode(byte op)
{
this.opcode = op;
this.finRsvOp = (byte)((finRsvOp & 0xF0) | (op & 0x0F));
if (op == OpCode.UNDEFINED)
{
@ -567,7 +549,7 @@ public class WebSocketFrame implements Frame
return this;
}
if (OpCode.isControlFrame(opcode))
if (isControlFrame())
{
if (buf.length > WebSocketFrame.MAX_CONTROL_PAYLOAD)
{
@ -594,7 +576,7 @@ public class WebSocketFrame implements Frame
return this;
}
if (OpCode.isControlFrame(opcode))
if (isControlFrame())
{
if (len > WebSocketFrame.MAX_CONTROL_PAYLOAD)
{
@ -625,7 +607,7 @@ public class WebSocketFrame implements Frame
return this;
}
if (OpCode.isControlFrame(opcode))
if (isControlFrame())
{
if (buf.remaining() > WebSocketFrame.MAX_CONTROL_PAYLOAD)
{
@ -646,19 +628,22 @@ public class WebSocketFrame implements Frame
public WebSocketFrame setRsv1(boolean rsv1)
{
this.rsv1 = rsv1;
// set bit 2
this.finRsvOp = (byte)((finRsvOp & 0xBF) | (rsv1? 0x40:0x00));
return this;
}
public WebSocketFrame setRsv2(boolean rsv2)
{
this.rsv2 = rsv2;
// set bit 3
this.finRsvOp = (byte)((finRsvOp & 0xDF) | (rsv2? 0x20:0x00));
return this;
}
public WebSocketFrame setRsv3(boolean rsv3)
{
this.rsv3 = rsv3;
// set bit 4
this.finRsvOp = (byte)((finRsvOp & 0xEF) | (rsv3? 0x10:0x00));
return this;
}
@ -666,14 +651,14 @@ public class WebSocketFrame implements Frame
public String toString()
{
StringBuilder b = new StringBuilder();
b.append(OpCode.name(opcode));
b.append(OpCode.name((byte)(finRsvOp & 0x0F)));
b.append('[');
b.append("len=").append(payloadLength);
b.append(",fin=").append(fin);
b.append(",fin=").append((finRsvOp & 0x80)!=0);
b.append(",rsv=");
b.append(rsv1?'1':'.');
b.append(rsv2?'1':'.');
b.append(rsv3?'1':'.');
b.append(((finRsvOp&0x40)!=0)?'1':'.');
b.append(((finRsvOp&0x20)!=0)?'1':'.');
b.append(((finRsvOp&0x10)!=0)?'1':'.');
b.append(",masked=").append(masked);
b.append(",continuation=").append(continuation);
b.append(",remaining=").append(remaining());

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket.common;
import static org.hamcrest.Matchers.*;
import java.nio.ByteBuffer;
import org.eclipse.jetty.io.ByteBufferPool;
@ -26,6 +28,7 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
@ -58,16 +61,19 @@ public class WebSocketFrameTest
ByteBufferAssert.assertEquals(message,expected,actual);
}
private void assertFrameHex(String message, String expectedHex, ByteBuffer actual)
{
String actualHex = Hex.asHex(actual);
Assert.assertThat("Generated Frame:" + message,actualHex,is(expectedHex));
}
@Test
public void testLaxInvalidClose()
{
WebSocketFrame frame = new WebSocketFrame(OpCode.CLOSE).setFin(false);
ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
ByteBuffer expected = ByteBuffer.allocate(2);
expected.put((byte)0x08);
expected.put((byte)0x00);
assertEqual("Lax Invalid Close Frame",expected,actual);
String expected = "0800";
assertFrameHex("Lax Invalid Close Frame",expected,actual);
}
@Test
@ -75,11 +81,8 @@ public class WebSocketFrameTest
{
WebSocketFrame frame = new WebSocketFrame(OpCode.PING).setFin(false);
ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
ByteBuffer expected = ByteBuffer.allocate(2);
expected.put((byte)0x09);
expected.put((byte)0x00);
assertEqual("Lax Invalid Ping Frame",expected,actual);
String expected = "0900";
assertFrameHex("Lax Invalid Ping Frame",expected,actual);
}
@Test
@ -87,13 +90,8 @@ public class WebSocketFrameTest
{
CloseInfo close = new CloseInfo(StatusCode.NORMAL);
ByteBuffer actual = generateWholeFrame(strictGenerator,close.asFrame());
ByteBuffer expected = ByteBuffer.allocate(4);
expected.put((byte)0x88);
expected.put((byte)0x02);
expected.put((byte)0x03);
expected.put((byte)0xE8);
assertEqual("Strict Valid Close Frame",expected,actual);
String expected = "880203E8";
assertFrameHex("Strict Valid Close Frame",expected,actual);
}
@Test
@ -101,10 +99,40 @@ public class WebSocketFrameTest
{
WebSocketFrame frame = new WebSocketFrame(OpCode.PING);
ByteBuffer actual = generateWholeFrame(strictGenerator,frame);
ByteBuffer expected = ByteBuffer.allocate(2);
expected.put((byte)0x89);
expected.put((byte)0x00);
assertEqual("Strict Valid Ping Frame",expected,actual);
String expected = "8900";
assertFrameHex("Strict Valid Ping Frame",expected,actual);
}
@Test
public void testRsv1()
{
WebSocketFrame frame = new WebSocketFrame(OpCode.TEXT);
frame.setPayload("Hi");
frame.setRsv1(true);
ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
String expected = "C1024869";
assertFrameHex("Lax Text Frame with RSV1",expected,actual);
}
@Test
public void testRsv2()
{
WebSocketFrame frame = new WebSocketFrame(OpCode.TEXT);
frame.setPayload("Hi");
frame.setRsv2(true);
ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
String expected = "A1024869";
assertFrameHex("Lax Text Frame with RSV2",expected,actual);
}
@Test
public void testRsv3()
{
WebSocketFrame frame = new WebSocketFrame(OpCode.TEXT);
frame.setPayload("Hi");
frame.setRsv3(true);
ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
String expected = "91024869";
assertFrameHex("Lax Text Frame with RSV3",expected,actual);
}
}

View File

@ -44,7 +44,7 @@ public class TestABCase4 extends AbstractABCase
public BadFrame(byte opcode)
{
super();
super.opcode = opcode;
super.finRsvOp = (byte)((finRsvOp & 0xF0) | (opcode & 0x0F));
// NOTE: Not setting Frame.Type intentionally
}
}

View File

@ -361,47 +361,6 @@ public class TestABCase7 extends AbstractABCase
}
}
/**
* close with invalid payload (124 byte reason) (exceeds total allowed control frame payload bytes)
*/
@Test
public void testCase7_3_6() throws Exception
{
ByteBuffer payload = ByteBuffer.allocate(256);
BufferUtil.clearToFill(payload);
payload.put((byte)0xE8);
payload.put((byte)0x03);
byte reason[] = new byte[124]; // too big
Arrays.fill(reason,(byte)'!');
payload.put(reason);
BufferUtil.flipToFlush(payload,0);
List<WebSocketFrame> send = new ArrayList<>();
WebSocketFrame close = new WebSocketFrame();
close.setPayload(payload);
close.setOpCode(OpCode.CLOSE); // set opcode after payload (to prevent early bad payload detection)
send.add(close);
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
try
{
enableStacks(Parser.class,false);
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectNoMoreFrames();
}
finally
{
enableStacks(Parser.class,true);
fuzzer.close();
}
}
/**
* close with invalid UTF8 in payload
*/

View File

@ -2,7 +2,7 @@ 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=DEBUG
# org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
# org.eclipse.jetty.websocket.server.ab.LEVEL=DEBUG