WebSocket - reducing memory footprint of WebSocketFrame by not using boolean fields
This commit is contained in:
parent
176c7d22e4
commit
980effaede
|
@ -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
|
||||
|
||||
|
|
|
@ -101,6 +101,7 @@ public interface Frame
|
|||
*
|
||||
* @return true if final frame.
|
||||
*/
|
||||
// FIXME: remove
|
||||
public boolean isLast();
|
||||
|
||||
public boolean isMasked();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue