Skip to content

Commit

Permalink
Autobahn tests - still a lot to fix.
Browse files Browse the repository at this point in the history
  • Loading branch information
aslakhellesoy committed Oct 10, 2011
1 parent f27b8a6 commit b7b9624
Show file tree
Hide file tree
Showing 14 changed files with 251 additions and 220 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -13,3 +13,4 @@ pom.xml.releaseBackup


pom.xml.asc pom.xml.asc
package.fig package.fig
reports
5 changes: 5 additions & 0 deletions Makefile
Expand Up @@ -62,9 +62,14 @@ build/.tests-pass: build/$(LIBRARY)-tests.jar
java -cp dist/$(LIBRARY).jar:build/$(LIBRARY)-tests.jar:$(CLASSPATH) org.junit.runner.JUnitCore $(call extracttests,build/$(LIBRARY)-tests.jar) java -cp dist/$(LIBRARY).jar:build/$(LIBRARY)-tests.jar:$(CLASSPATH) org.junit.runner.JUnitCore $(call extracttests,build/$(LIBRARY)-tests.jar)
@touch $@ @touch $@


# Run Autobahn tests
autobahn:
PYTHONPATH=src/test/Autobahn/lib/python python src/test/Autobahn/testsuite/websockets/fuzzing_client.py

# Clean up # Clean up
clean: clean:
rm -rf build dist out rm -rf build dist out

.PHONY: clean .PHONY: clean


again: clean all again: clean all
Expand Down
6 changes: 1 addition & 5 deletions README.md
Expand Up @@ -94,10 +94,7 @@ We're using it to test Webbit.
Installing Autobahn Installing Autobahn


git submodule update --init git submodule update --init
pushd src/test/Autobahn/lib/python
easy_install twisted easy_install twisted
python setup.py install
popd


Running Autobahn tests Running Autobahn tests


Expand All @@ -107,8 +104,7 @@ In shell A:


In shell B: In shell B:


cd src/test/Autobahn/testsuite/websockets make autobahn
python fuzzing_client.py


More More
----------- -----------
Expand Down
39 changes: 39 additions & 0 deletions fuzzing_client_spec.json
@@ -0,0 +1,39 @@
{
"servers": [{"agent": "AutobahnServer", "hostname": "localhost", "port": 9000, "version": 13}],
"cases": [
"1.1.1",
"1.1.2",
"1.1.3",
"1.1.4",
"1.1.5",
"1.1.6",
"1.1.7",
"1.1.8",
"1.2.1",
"1.2.2",
"1.2.3",
"1.2.4",
"1.2.5",
"1.2.6",
"1.2.7",
"1.2.8",
"2.1",
"2.2",
"2.3",
"2.4",
"2.5",
"2.6",
"2.7",
"2.8",
"2.9",
"2.10",
"2.11",
"3.1",
"3.2",
"3.3",
"3.4",
"3.5",
"3.6",
"3.7"
]
}
202 changes: 95 additions & 107 deletions src/main/java/org/webbitserver/netty/Hybi10WebSocketFrameDecoder.java
@@ -1,40 +1,32 @@
package org.webbitserver.netty; package org.webbitserver.netty;


import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.CorruptedFrameException; import org.jboss.netty.handler.codec.frame.CorruptedFrameException;
import org.jboss.netty.handler.codec.frame.TooLongFrameException; import org.jboss.netty.handler.codec.frame.TooLongFrameException;
import org.jboss.netty.handler.codec.http.websocket.DefaultWebSocketFrame;
import org.jboss.netty.handler.codec.replay.ReplayingDecoder; import org.jboss.netty.handler.codec.replay.ReplayingDecoder;


import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;


import static java.lang.Integer.toHexString;

public class Hybi10WebSocketFrameDecoder extends ReplayingDecoder<Hybi10WebSocketFrameDecoder.State> { public class Hybi10WebSocketFrameDecoder extends ReplayingDecoder<Hybi10WebSocketFrameDecoder.State> {
private static final byte OPCODE_CONT = 0x0; private long framePayloadLen;
private static final byte OPCODE_TEXT = 0x1;
private static final byte OPCODE_BINARY = 0x2;
private static final byte OPCODE_CLOSE = 0x8;
private static final byte OPCODE_PING = 0x9;
private static final byte OPCODE_PONG = 0xA;

public static final int MAX_LENGTH = 16384;

private Byte fragmentOpcode;
private Byte opcode = null;
private int currentFrameLength;
private ChannelBuffer maskingKey; private ChannelBuffer maskingKey;
private List<ChannelBuffer> frames = new ArrayList<ChannelBuffer>(); private List<ChannelBuffer> frames = new ArrayList<ChannelBuffer>();


private boolean isServer = true;
private boolean requireMaskedClientFrames = true;
private boolean insideMessage;
private byte frameOpcode;
private Byte fragmentOpcode;
private boolean frameFin;
private int frameRsv;

public static enum State { public static enum State {
FRAME_START, FRAME_START,
PARSING_LENGTH,
MASKING_KEY, MASKING_KEY,
PARSING_LENGTH_2,
PARSING_LENGTH_3,
PAYLOAD PAYLOAD
} }


Expand All @@ -46,75 +38,92 @@ public Hybi10WebSocketFrameDecoder() {
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, State state) throws Exception { protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, State state) throws Exception {
switch (state) { switch (state) {
case FRAME_START: case FRAME_START:
// FIN, RSV, OPCODE
byte b = buffer.readByte(); byte b = buffer.readByte();
byte fin = (byte) (b & 0x80); frameFin = (b & 0x80) != 0;
byte reserved = (byte) (b & 0x70); frameRsv = (b & 0x70) >> 4;
byte opcode = (byte) (b & 0x0F); frameOpcode = (byte) (b & 0x0F);

// MASK, PAYLOAD LEN 1
b = buffer.readByte();
boolean frameMasked = (b & 0x80) != 0;
int framePayloadLen1 = (b & 0x7F);


if (reserved != 0) { if (frameRsv != 0) {
throw new CorruptedFrameException("Reserved bits set: " + toZeroPaddedBinaryString(b)); throw new CorruptedFrameException("RSV != 0 and no extension negotiated");
} }
if (!isOpcode(opcode)) {
throw new CorruptedFrameException("Invalid opcode " + toHexString(b)); if (isServer && requireMaskedClientFrames && !frameMasked) {
throw new CorruptedFrameException("unmasked client to server frame");
} }


if (fin == 0) { if (frameOpcode > 7) { // control frame (have MSB in opcode set)
if (fragmentOpcode == null) {
if (!isDataOpcode(opcode)) { // control frames MUST NOT be fragmented
throw new CorruptedFrameException("Fragmented frame with invalid opcode " + toHexString(opcode)); if (!frameFin) {
} throw new CorruptedFrameException("fragmented control frame");
fragmentOpcode = opcode;
} else if (opcode != OPCODE_CONT) {
throw new CorruptedFrameException("Continuation frame with invalid opcode " + toHexString(opcode));
} }
} else {
if (fragmentOpcode != null) { // control frames MUST have payload 125 octets or less
if (!isControlOpcode(opcode) && opcode != OPCODE_CONT) { if (framePayloadLen1 > 125) {
throw new CorruptedFrameException("Final frame with invalid opcode " + toHexString(opcode)); throw new CorruptedFrameException("control frame with payload length > 125 octets");
}
} else if (opcode == OPCODE_CONT) {
throw new CorruptedFrameException("Final frame with invalid opcode " + toHexString(opcode));
} }
this.opcode = opcode;
}


checkpoint(State.PARSING_LENGTH); // check for reserved control frame opcodes
case PARSING_LENGTH: if (!(frameOpcode == Opcodes.OPCODE_CLOSE || frameOpcode == Opcodes.OPCODE_PING || frameOpcode == Opcodes.OPCODE_PONG)) {
b = buffer.readByte(); throw new CorruptedFrameException("control frame using reserved opcode " + frameOpcode);
byte masked = (byte) (b & 0x80); }
if (masked == 0) {
throw new CorruptedFrameException("Unmasked frame received"); // close frame : if there is a body, the first two bytes of the body MUST be a 2-byte
// unsigned integer (in network byte order) representing a status code
if (frameOpcode == 8 && framePayloadLen1 == 1) {
throw new CorruptedFrameException("received close control frame with payload len 1");
}
} else { // data frame
// check for reserved data frame opcodes
if (!(frameOpcode == Opcodes.OPCODE_CONT || frameOpcode == Opcodes.OPCODE_TEXT || frameOpcode == Opcodes.OPCODE_BINARY)) {
throw new CorruptedFrameException("data frame using reserved opcode " + frameOpcode);
}

// // check opcode vs message fragmentation state 1/2
// if (!insideMessage && frameOpcode == OPCODE_CONT) {
// throw new CorruptedFrameException("received continuation data frame outside fragmented message");
// }
//
// // check opcode vs message fragmentation state 2/2
// if (insideMessage && frameOpcode != OPCODE_CONT) {
// throw new CorruptedFrameException("received non-continuation data frame while inside fragmented message");
// }
} }


int length = (byte) (b & 0x7F); int maskLen = frameMasked ? 4 : 0;

if (framePayloadLen1 == 126) {
framePayloadLen = buffer.readUnsignedShort();
if (framePayloadLen < 126) {
throw new CorruptedFrameException("invalid data frame length (not using minimal length encoding)");
}
} else if (framePayloadLen1 == 127) {
framePayloadLen = buffer.readLong();
// TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe just check if it's negative?


if (length < 126) { if (framePayloadLen < 65536) {
currentFrameLength = length; throw new CorruptedFrameException("invalid data frame length (not using minimal length encoding)");
checkpoint(State.MASKING_KEY); }
} else if (length == 126) { } else {
checkpoint(State.PARSING_LENGTH_2); framePayloadLen = framePayloadLen1;
} else if (length == 127) {
checkpoint(State.PARSING_LENGTH_3);
} }
return null;
case PARSING_LENGTH_2:
currentFrameLength = buffer.readShort() & 0xFFFF;
checkpoint(State.MASKING_KEY);
return null;
case PARSING_LENGTH_3:
currentFrameLength = buffer.readInt() & 0xFFFFFFFF;
checkpoint(State.MASKING_KEY); checkpoint(State.MASKING_KEY);
return null; return null;
case MASKING_KEY: case MASKING_KEY:
maskingKey = buffer.readBytes(4); maskingKey = buffer.readBytes(4);
checkpoint(State.PAYLOAD); checkpoint(State.PAYLOAD);
case PAYLOAD: case PAYLOAD:
ChannelBuffer frame = buffer.readBytes(currentFrameLength); ChannelBuffer frame = buffer.readBytes(toFrameLength(framePayloadLen));
checkpoint(State.FRAME_START);
unmask(frame); unmask(frame);


if (this.opcode == OPCODE_CONT) { if (frameOpcode == Opcodes.OPCODE_CONT) {
this.opcode = fragmentOpcode; frameOpcode = fragmentOpcode;
frames.add(frame); frames.add(frame);


frame = channel.getConfig().getBufferFactory().getBuffer(0); frame = channel.getConfig().getBufferFactory().getBuffer(0);
Expand All @@ -125,57 +134,36 @@ protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffe


this.fragmentOpcode = null; this.fragmentOpcode = null;
frames.clear(); frames.clear();
} } else {

checkpoint(State.FRAME_START);
if (this.opcode == OPCODE_TEXT) { if (frameOpcode == Opcodes.OPCODE_TEXT || frameOpcode == Opcodes.OPCODE_BINARY || frameOpcode == Opcodes.OPCODE_PONG) {
if (frame.readableBytes() > MAX_LENGTH) { return new HybiFrame(frameOpcode, frameFin, frameRsv, frame);
throw new TooLongFrameException(); } else if (frameOpcode == Opcodes.OPCODE_PING) {
channel.write(new HybiFrame(Opcodes.OPCODE_PONG, true, 0, frame));
return null;
} else if (frameOpcode == Opcodes.OPCODE_CLOSE) {
channel.write(new HybiFrame(Opcodes.OPCODE_CLOSE, true, 0, ChannelBuffers.buffer(0)));
channel.close();
return null;
} }
return new DefaultWebSocketFrame(0x00, frame);
} else if (this.opcode == OPCODE_BINARY) {
return new DefaultWebSocketFrame(0xFF, frame);
} else if (this.opcode == OPCODE_PING) {
channel.write(new Pong(0x00, frame));
return null;
} else if (this.opcode == OPCODE_PONG) {
return new Pong(0x00, frame);
} else if (this.opcode == OPCODE_CLOSE) {
// TODO
return null;
} }
default: default:
throw new Error("Shouldn't reach here."); throw new Error("Shouldn't reach here.");
} }
} }


private int toFrameLength(long l) throws TooLongFrameException {
if (l > Integer.MAX_VALUE) {
throw new TooLongFrameException("Length:" + l);
} else {
return (int) l;
}
}

private void unmask(ChannelBuffer frame) { private void unmask(ChannelBuffer frame) {
byte[] bytes = frame.array(); byte[] bytes = frame.array();
for (int i = 0; i < bytes.length; i++) { for (int i = 0; i < bytes.length; i++) {
frame.setByte(i, frame.getByte(i) ^ maskingKey.getByte(i % 4)); frame.setByte(i, frame.getByte(i) ^ maskingKey.getByte(i % 4));
} }
} }

private String toZeroPaddedBinaryString(byte b) {
return String.format("%8s", Integer.toBinaryString(b)).replace(" ", "0");
}

private boolean isOpcode(int opcode) {
return opcode == OPCODE_CONT ||
opcode == OPCODE_TEXT ||
opcode == OPCODE_BINARY ||
opcode == OPCODE_CLOSE ||
opcode == OPCODE_PING ||
opcode == OPCODE_PONG;
}

private boolean isControlOpcode(int opcode) {
return opcode == OPCODE_CLOSE ||
opcode == OPCODE_PING ||
opcode == OPCODE_PONG;
}

private boolean isDataOpcode(int opcode) {
return opcode == OPCODE_TEXT ||
opcode == OPCODE_BINARY;
}
} }

0 comments on commit b7b9624

Please sign in to comment.