WebSocket - fixing windowed parse issue with masked frames
This commit is contained in:
parent
307fdced91
commit
c548cba71c
|
@ -195,7 +195,7 @@ public class Generator
|
|||
assertFrameValid(frame);
|
||||
|
||||
ByteBuffer buffer = bufferPool.acquire(OVERHEAD,true);
|
||||
BufferUtil.flipToFill(buffer);
|
||||
BufferUtil.clearToFill(buffer);
|
||||
|
||||
/*
|
||||
* start the generation process
|
||||
|
|
|
@ -248,10 +248,6 @@ public abstract class WebSocketFrame implements Frame
|
|||
@Override
|
||||
public byte[] getMask()
|
||||
{
|
||||
if (!masked)
|
||||
{
|
||||
throw new IllegalStateException("Frame is not masked");
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,24 +42,31 @@ public class DeMaskProcessor implements PayloadProcessor
|
|||
int remaining;
|
||||
while ((remaining = end - start) > 0)
|
||||
{
|
||||
if (remaining >= 4 && offset == 0)
|
||||
if (remaining >= 4 && (offset % 4) == 0)
|
||||
{
|
||||
payload.putInt(start, payload.getInt(start) ^ maskInt);
|
||||
start +=4;
|
||||
payload.putInt(start,payload.getInt(start) ^ maskInt);
|
||||
start += 4;
|
||||
offset += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
payload.put(start, (byte)(payload.get(start) ^ maskBytes[offset & 3]));
|
||||
payload.put(start,(byte)(payload.get(start) ^ maskBytes[offset & 3]));
|
||||
++start;
|
||||
++offset;
|
||||
}
|
||||
}
|
||||
maskOffset = offset;
|
||||
}
|
||||
|
||||
public void reset(byte mask[])
|
||||
{
|
||||
this.maskBytes = mask;
|
||||
this.maskOffset = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(Frame frame)
|
||||
{
|
||||
this.maskBytes = frame.isMasked() ? frame.getMask() : null;
|
||||
this.maskOffset = 0;
|
||||
reset(frame.getMask());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import static org.hamcrest.Matchers.*;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
@ -202,4 +203,49 @@ public class ParserTest
|
|||
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().pop();
|
||||
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)'*'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 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.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
public class RawFrameBuilder
|
||||
{
|
||||
public static void putOpFin(ByteBuffer buf, byte opcode, boolean fin)
|
||||
{
|
||||
byte b = 0x00;
|
||||
if (fin)
|
||||
{
|
||||
b |= 0x80;
|
||||
}
|
||||
b |= opcode & 0x0F;
|
||||
buf.put(b);
|
||||
}
|
||||
|
||||
public static void putLengthAndMask(ByteBuffer buf, int length, byte mask[])
|
||||
{
|
||||
if (mask != null)
|
||||
{
|
||||
Assert.assertThat("Mask.length",mask.length,is(4));
|
||||
putLength(buf,length,(mask != null));
|
||||
buf.put(mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
putLength(buf,length,false);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] mask(final byte[] data, final byte mask[])
|
||||
{
|
||||
Assert.assertThat("Mask.length",mask.length,is(4));
|
||||
int len = data.length;
|
||||
byte ret[] = new byte[len];
|
||||
System.arraycopy(data,0,ret,0,len);
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
ret[i] ^= mask[i % 4];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void putLength(ByteBuffer buf, int length, boolean masked)
|
||||
{
|
||||
if (length < 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Length cannot be negative");
|
||||
}
|
||||
byte b = (masked?(byte)0x80:0x00);
|
||||
|
||||
// write the uncompressed length
|
||||
if (length > 0xFF_FF)
|
||||
{
|
||||
buf.put((byte)(b | 0x7F));
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)((length >> 24) & 0xFF));
|
||||
buf.put((byte)((length >> 16) & 0xFF));
|
||||
buf.put((byte)((length >> 8) & 0xFF));
|
||||
buf.put((byte)(length & 0xFF));
|
||||
}
|
||||
else if (length >= 0x7E)
|
||||
{
|
||||
buf.put((byte)(b | 0x7E));
|
||||
buf.put((byte)(length >> 8));
|
||||
buf.put((byte)(length & 0xFF));
|
||||
}
|
||||
else
|
||||
{
|
||||
buf.put((byte)(b | length));
|
||||
}
|
||||
}
|
||||
|
||||
public static void putMask(ByteBuffer buf, byte mask[])
|
||||
{
|
||||
Assert.assertThat("Mask.length",mask.length,is(4));
|
||||
buf.put(mask);
|
||||
}
|
||||
|
||||
public static void putPayloadLength(ByteBuffer buf, int length)
|
||||
{
|
||||
putLength(buf,length,true);
|
||||
}
|
||||
}
|
|
@ -18,15 +18,21 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.common.io.payload;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.common.ByteBufferAssert;
|
||||
import org.eclipse.jetty.websocket.common.Hex;
|
||||
import org.eclipse.jetty.websocket.common.UnitGenerator;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||
import org.eclipse.jetty.websocket.common.frames.TextFrame;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class DeMaskProcessorTest
|
||||
|
@ -41,7 +47,6 @@ public class DeMaskProcessorTest
|
|||
|
||||
WebSocketFrame frame = WebSocketFrame.text(message);
|
||||
frame.setMask(TypeUtil.fromHexString("11223344"));
|
||||
// frame.setMask(TypeUtil.fromHexString("00000000"));
|
||||
|
||||
ByteBuffer buf = UnitGenerator.generate(frame);
|
||||
LOG.debug("Buf: {}",BufferUtil.toDetailString(buf));
|
||||
|
@ -55,4 +60,50 @@ public class DeMaskProcessorTest
|
|||
|
||||
ByteBufferAssert.assertEquals("DeMasked Text Payload",message,payload);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeMaskTextSliced()
|
||||
{
|
||||
final byte msgChar = '*';
|
||||
final int messageSize = 25;
|
||||
|
||||
byte message[] = new byte[messageSize];
|
||||
Arrays.fill(message,msgChar);
|
||||
|
||||
TextFrame frame = new TextFrame();
|
||||
frame.setPayload(ByteBuffer.wrap(message));
|
||||
frame.setMask(Hex.asByteArray("11223344"));
|
||||
|
||||
ByteBuffer buf = UnitGenerator.generate(frame);
|
||||
LOG.debug("Buf: {}",BufferUtil.toDetailString(buf));
|
||||
ByteBuffer payload = buf.slice();
|
||||
payload.position(6); // where payload starts
|
||||
|
||||
LOG.debug("Payload: {}",BufferUtil.toDetailString(payload));
|
||||
LOG.debug("Pre-Processed: {}",Hex.asHex(payload));
|
||||
|
||||
DeMaskProcessor demask = new DeMaskProcessor();
|
||||
demask.reset(frame);
|
||||
ByteBuffer slice1 = payload.slice();
|
||||
ByteBuffer slice2 = payload.slice();
|
||||
|
||||
// slice at non-multiple of 4, but also where last buffer remaining
|
||||
// is more than 4.
|
||||
int slicePoint = 7;
|
||||
slice1.limit(slicePoint);
|
||||
slice2.position(slicePoint);
|
||||
|
||||
Assert.assertThat("Slices are setup right",slice1.remaining() + slice2.remaining(),is(messageSize));
|
||||
|
||||
demask.process(slice1);
|
||||
demask.process(slice2);
|
||||
|
||||
LOG.debug("Post-Processed: {}",Hex.asHex(payload));
|
||||
|
||||
Assert.assertThat("Payload.remaining",payload.remaining(),is(messageSize));
|
||||
for (int i = payload.position(); i < payload.limit(); i++)
|
||||
{
|
||||
Assert.assertThat("payload[" + i + "]",payload.get(i),is(msgChar));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.eclipse.jetty.websocket.api.WebSocketException;
|
|||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.common.util.TextUtil;
|
||||
|
||||
/**
|
||||
* Simple Echo WebSocket, using async writes of echo
|
||||
|
@ -38,15 +39,6 @@ public class ABSocket
|
|||
|
||||
private Session session;
|
||||
|
||||
private String abbreviate(String message)
|
||||
{
|
||||
if (message.length() > 80)
|
||||
{
|
||||
return '"' + message.substring(0,80) + "\"...";
|
||||
}
|
||||
return '"' + message + '"';
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onBinary(byte buf[], int offset, int len)
|
||||
{
|
||||
|
@ -74,7 +66,7 @@ public class ABSocket
|
|||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("onText() size={}, msg={}",message.length(),abbreviate(message));
|
||||
LOG.debug("onText() size={}, msg={}",message.length(),TextUtil.hint(message));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,6 @@ public abstract class AbstractABCase
|
|||
|
||||
protected static final byte FIN = (byte)0x80;
|
||||
protected static final byte NOFIN = 0x00;
|
||||
private static final byte MASKED_BIT = (byte)0x80;
|
||||
protected static final byte[] MASK =
|
||||
{ 0x12, 0x34, 0x56, 0x78 };
|
||||
|
||||
|
@ -192,57 +191,22 @@ public abstract class AbstractABCase
|
|||
return server;
|
||||
}
|
||||
|
||||
protected byte[] masked(final byte[] data)
|
||||
public static byte[] masked(final byte[] data)
|
||||
{
|
||||
int len = data.length;
|
||||
byte ret[] = new byte[len];
|
||||
System.arraycopy(data,0,ret,0,len);
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
ret[i] ^= MASK[i % 4];
|
||||
}
|
||||
return ret;
|
||||
return RawFrameBuilder.mask(data,MASK);
|
||||
}
|
||||
|
||||
private void putLength(ByteBuffer buf, int length, boolean masked)
|
||||
public static void putLength(ByteBuffer buf, int length, boolean masked)
|
||||
{
|
||||
if (length < 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Length cannot be negative");
|
||||
}
|
||||
byte b = (masked?MASKED_BIT:0x00);
|
||||
|
||||
// write the uncompressed length
|
||||
if (length > 0xFF_FF)
|
||||
{
|
||||
buf.put((byte)(b | 0x7F));
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)((length >> 24) & 0xFF));
|
||||
buf.put((byte)((length >> 16) & 0xFF));
|
||||
buf.put((byte)((length >> 8) & 0xFF));
|
||||
buf.put((byte)(length & 0xFF));
|
||||
}
|
||||
else if (length >= 0x7E)
|
||||
{
|
||||
buf.put((byte)(b | 0x7E));
|
||||
buf.put((byte)(length >> 8));
|
||||
buf.put((byte)(length & 0xFF));
|
||||
}
|
||||
else
|
||||
{
|
||||
buf.put((byte)(b | length));
|
||||
}
|
||||
RawFrameBuilder.putLength(buf,length,masked);
|
||||
}
|
||||
|
||||
public void putMask(ByteBuffer buf)
|
||||
public static void putMask(ByteBuffer buf)
|
||||
{
|
||||
buf.put(MASK);
|
||||
}
|
||||
|
||||
public void putPayloadLength(ByteBuffer buf, int length)
|
||||
public static void putPayloadLength(ByteBuffer buf, int length)
|
||||
{
|
||||
putLength(buf,length,true);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 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.server.ab;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
public class RawFrameBuilder
|
||||
{
|
||||
public static void putOpFin(ByteBuffer buf, byte opcode, boolean fin)
|
||||
{
|
||||
byte b = 0x00;
|
||||
if (fin)
|
||||
{
|
||||
b |= 0x80;
|
||||
}
|
||||
b |= opcode & 0x0F;
|
||||
buf.put(b);
|
||||
}
|
||||
|
||||
public static void putLengthAndMask(ByteBuffer buf, int length, byte mask[])
|
||||
{
|
||||
if (mask != null)
|
||||
{
|
||||
Assert.assertThat("Mask.length",mask.length,is(4));
|
||||
putLength(buf,length,(mask != null));
|
||||
buf.put(mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
putLength(buf,length,false);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] mask(final byte[] data, final byte mask[])
|
||||
{
|
||||
Assert.assertThat("Mask.length",mask.length,is(4));
|
||||
int len = data.length;
|
||||
byte ret[] = new byte[len];
|
||||
System.arraycopy(data,0,ret,0,len);
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
ret[i] ^= mask[i % 4];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void putLength(ByteBuffer buf, int length, boolean masked)
|
||||
{
|
||||
if (length < 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Length cannot be negative");
|
||||
}
|
||||
byte b = (masked?(byte)0x80:0x00);
|
||||
|
||||
// write the uncompressed length
|
||||
if (length > 0xFF_FF)
|
||||
{
|
||||
buf.put((byte)(b | 0x7F));
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)0x00);
|
||||
buf.put((byte)((length >> 24) & 0xFF));
|
||||
buf.put((byte)((length >> 16) & 0xFF));
|
||||
buf.put((byte)((length >> 8) & 0xFF));
|
||||
buf.put((byte)(length & 0xFF));
|
||||
}
|
||||
else if (length >= 0x7E)
|
||||
{
|
||||
buf.put((byte)(b | 0x7E));
|
||||
buf.put((byte)(length >> 8));
|
||||
buf.put((byte)(length & 0xFF));
|
||||
}
|
||||
else
|
||||
{
|
||||
buf.put((byte)(b | length));
|
||||
}
|
||||
}
|
||||
|
||||
public static void putMask(ByteBuffer buf, byte mask[])
|
||||
{
|
||||
Assert.assertThat("Mask.length",mask.length,is(4));
|
||||
buf.put(mask);
|
||||
}
|
||||
|
||||
public static void putPayloadLength(ByteBuffer buf, int length)
|
||||
{
|
||||
putLength(buf,length,true);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import java.util.List;
|
|||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.eclipse.jetty.websocket.common.CloseInfo;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||
import org.eclipse.jetty.websocket.common.frames.TextFrame;
|
||||
import org.eclipse.jetty.websocket.server.ab.Fuzzer.SendMode;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -236,11 +237,11 @@ public class TestABCase1 extends AbstractABCase
|
|||
ByteBuffer buf = ByteBuffer.wrap(payload);
|
||||
|
||||
List<WebSocketFrame> send = new ArrayList<>();
|
||||
send.add(WebSocketFrame.text().setPayload(buf));
|
||||
send.add(new TextFrame().setPayload(buf));
|
||||
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
|
||||
|
||||
List<WebSocketFrame> expect = new ArrayList<>();
|
||||
expect.add(WebSocketFrame.text().setPayload(clone(buf)));
|
||||
expect.add(new TextFrame().setPayload(clone(buf)));
|
||||
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
|
||||
|
||||
Fuzzer fuzzer = new Fuzzer(this);
|
||||
|
|
Loading…
Reference in New Issue