Implemented parser and generator for GO_AWAY frame.

This commit is contained in:
Simone Bordet 2014-06-05 22:28:39 +02:00
parent 81538c9b59
commit 7a347e267f
13 changed files with 448 additions and 16 deletions

View File

@ -0,0 +1,48 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.frames;
public class GoAwayFrame
{
private final int lastStreamId;
private final int error;
private final byte[] payload;
public GoAwayFrame(int lastStreamId, int error, byte[] payload)
{
this.lastStreamId = lastStreamId;
this.error = error;
this.payload = payload;
}
public int getLastStreamId()
{
return lastStreamId;
}
public int getError()
{
return error;
}
public byte[] getPayload()
{
return payload;
}
}

View File

@ -36,8 +36,35 @@ public class Generator
this.byteBufferPool = byteBufferPool;
}
public Result generateGoAway(int lastStreamId, int error, byte[] payload)
{
if (lastStreamId < 0)
throw new IllegalArgumentException("Invalid last stream id: " + lastStreamId);
Result result = new Result(byteBufferPool);
int length = 4 + 4 + (payload != null ? payload.length : 0);
ByteBuffer header = generateHeader(FrameType.GO_AWAY, length, 0, 0);
header.putInt(lastStreamId);
header.putInt(error);
if (payload != null)
{
header.put(payload);
}
BufferUtil.flipToFlush(header, 0);
result.add(header, true);
return result;
}
public Result generatePing(byte[] payload, boolean reply)
{
if (payload.length != 8)
throw new IllegalArgumentException("Invalid payload length: " + payload.length);
Result result = new Result(byteBufferPool);
ByteBuffer header = generateHeader(FrameType.PING, 8, reply ? 0x01 : 0x00, 0);
@ -91,7 +118,7 @@ public class Generator
return result;
}
public Result generateContent(int streamId, int paddingLength, ByteBuffer data, boolean last, boolean compress)
public Result generateData(int streamId, int paddingLength, ByteBuffer data, boolean last, boolean compress)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
@ -110,7 +137,7 @@ public class Generator
// Can we fit just one frame ?
if (dataLength + paddingBytes + paddingLength <= DataFrame.MAX_LENGTH)
{
generateFrame(result, streamId, paddingBytes, paddingLength, data, last, compress);
generateData(result, streamId, paddingBytes, paddingLength, data, last, compress);
}
else
{
@ -126,13 +153,13 @@ public class Generator
data.limit(Math.min(dataBytesPerFrame * i, limit));
ByteBuffer slice = data.slice();
data.position(data.limit());
generateFrame(result, streamId, paddingBytes, paddingLength, slice, i == frames && last, compress);
generateData(result, streamId, paddingBytes, paddingLength, slice, i == frames && last, compress);
}
}
return result;
}
private void generateFrame(Result result, int streamId, int paddingBytes, int paddingLength, ByteBuffer data, boolean last, boolean compress)
private void generateData(Result result, int streamId, int paddingBytes, int paddingLength, ByteBuffer data, boolean last, boolean compress)
{
int length = paddingBytes + data.remaining() + paddingLength;

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.http2.parser;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.PingFrame;
import org.eclipse.jetty.http2.frames.PriorityFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
@ -77,7 +78,7 @@ public abstract class BodyParser
headerParser.reset();
}
protected boolean notifyDataFrame(DataFrame frame)
protected boolean notifyData(DataFrame frame)
{
try
{
@ -90,7 +91,7 @@ public abstract class BodyParser
}
}
protected boolean notifyPriorityFrame(PriorityFrame frame)
protected boolean notifyPriority(PriorityFrame frame)
{
try
{
@ -103,7 +104,7 @@ public abstract class BodyParser
}
}
protected boolean notifyResetFrame(ResetFrame frame)
protected boolean notifyReset(ResetFrame frame)
{
try
{
@ -116,7 +117,7 @@ public abstract class BodyParser
}
}
protected boolean notifyPingFrame(PingFrame frame)
protected boolean notifyPing(PingFrame frame)
{
try
{
@ -129,6 +130,19 @@ public abstract class BodyParser
}
}
protected boolean notifyGoAway(GoAwayFrame frame)
{
try
{
return listener.onGoAway(frame);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener " + listener, x);
return false;
}
}
public enum Result
{
PENDING, ASYNC, COMPLETE

View File

@ -135,7 +135,7 @@ public class DataBodyParser extends BodyParser
{
boolean end = isEndStream();
DataFrame frame = new DataFrame(getStreamId(), buffer, fragment ? false : end);
return notifyDataFrame(frame);
return notifyData(frame);
}
private enum State

View File

@ -0,0 +1,166 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.parser;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
public class GoAwayBodyParser extends BodyParser
{
private State state = State.LAST_STREAM_ID;
private int cursor;
private int lastStreamId;
private int error;
private byte[] payload;
public GoAwayBodyParser(HeaderParser headerParser, Parser.Listener listener)
{
super(headerParser, listener);
}
@Override
protected void reset()
{
super.reset();
state = State.LAST_STREAM_ID;
cursor = 0;
lastStreamId = 0;
error = 0;
payload = null;
}
@Override
public Result parse(ByteBuffer buffer)
{
while (buffer.hasRemaining())
{
switch (state)
{
case LAST_STREAM_ID:
{
if (buffer.remaining() >= 4)
{
lastStreamId = buffer.getInt();
lastStreamId &= 0x7F_FF_FF_FF;
state = State.ERROR;
}
else
{
state = State.LAST_STREAM_ID_BYTES;
cursor = 4;
}
break;
}
case LAST_STREAM_ID_BYTES:
{
int currByte = buffer.get() & 0xFF;
--cursor;
lastStreamId += currByte << (8 * cursor);
if (cursor == 0)
{
lastStreamId &= 0x7F_FF_FF_FF;
state = State.ERROR;
}
break;
}
case ERROR:
{
if (buffer.remaining() >= 4)
{
error = buffer.getInt();
state = State.PAYLOAD;
int payloadLength = getBodyLength() - 4 - 4;
if (payloadLength == 0)
{
return onGoAway(lastStreamId, error, null);
}
}
else
{
state = State.ERROR_BYTES;
cursor = 4;
}
break;
}
case ERROR_BYTES:
{
int currByte = buffer.get() & 0xFF;
--cursor;
error += currByte << (8 * cursor);
if (cursor == 0)
{
state = State.PAYLOAD;
int payloadLength = getBodyLength() - 4 - 4;
if (payloadLength == 0)
{
return onGoAway(lastStreamId, error, null);
}
}
break;
}
case PAYLOAD:
{
int payloadLength = getBodyLength() - 4 - 4;
payload = new byte[payloadLength];
if (buffer.remaining() >= payloadLength)
{
buffer.get(payload);
return onGoAway(lastStreamId, error, payload);
}
else
{
state = State.PAYLOAD_BYTES;
cursor = payloadLength;
}
break;
}
case PAYLOAD_BYTES:
{
payload[payload.length - cursor] = buffer.get();
--cursor;
if (cursor == 0)
{
return onGoAway(lastStreamId, error, payload);
}
break;
}
default:
{
throw new IllegalStateException();
}
}
}
return Result.PENDING;
}
private Result onGoAway(int lastStreamId, int error, byte[] payload)
{
GoAwayFrame frame = new GoAwayFrame(lastStreamId, error, payload);
reset();
return notifyGoAway(frame) ? Result.ASYNC : Result.COMPLETE;
}
private enum State
{
LAST_STREAM_ID, LAST_STREAM_ID_BYTES, ERROR, ERROR_BYTES, PAYLOAD, PAYLOAD_BYTES
}
}

View File

@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.PingFrame;
import org.eclipse.jetty.http2.frames.PriorityFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
@ -39,6 +40,7 @@ public class Parser
bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener);
bodyParsers[FrameType.RST_STREAM.getType()] = new ResetBodyParser(headerParser, listener);
bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener);
bodyParsers[FrameType.GO_AWAY.getType()] = new GoAwayBodyParser(headerParser, listener);
}
private void reset()
@ -96,6 +98,8 @@ public class Parser
public boolean onPing(PingFrame frame);
public boolean onGoAway(GoAwayFrame frame);
public static class Adapter implements Listener
{
@Override
@ -121,6 +125,12 @@ public class Parser
{
return false;
}
@Override
public boolean onGoAway(GoAwayFrame frame)
{
return false;
}
}
}

View File

@ -87,7 +87,7 @@ public class PingBodyParser extends BodyParser
{
PingFrame frame = new PingFrame(payload, hasFlag(0x1));
reset();
return notifyPingFrame(frame) ? Result.ASYNC : Result.COMPLETE;
return notifyPing(frame) ? Result.ASYNC : Result.COMPLETE;
}
private enum State

View File

@ -92,7 +92,7 @@ public class PriorityBodyParser extends BodyParser
case WEIGHT:
{
int weight = buffer.get() & 0xFF;
return onPriority(weight);
return onPriority(streamId, weight, exclusive);
}
default:
{
@ -103,11 +103,11 @@ public class PriorityBodyParser extends BodyParser
return Result.PENDING;
}
private Result onPriority(int weight)
private Result onPriority(int streamId, int weight, boolean exclusive)
{
PriorityFrame frame = new PriorityFrame(streamId, getStreamId(), weight, exclusive);
reset();
return notifyPriorityFrame(frame) ? Result.ASYNC : Result.COMPLETE;
return notifyPriority(frame) ? Result.ASYNC : Result.COMPLETE;
}
private enum State

View File

@ -1,3 +1,21 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.parser;
import java.nio.ByteBuffer;
@ -68,7 +86,7 @@ public class ResetBodyParser extends BodyParser
{
ResetFrame frame = new ResetFrame(getStreamId(), error);
reset();
return notifyResetFrame(frame) ? Result.ASYNC : Result.COMPLETE;
return notifyReset(frame) ? Result.ASYNC : Result.COMPLETE;
}
private enum State

View File

@ -118,7 +118,7 @@ public class DataGenerateParseTest
Generator.Result result = new Generator.Result(byteBufferPool);
for (int j = 1; j <= data.length; ++j)
{
result = result.merge(generator.generateContent(13, paddingLength, data[j - 1].slice(), j == data.length, false));
result = result.merge(generator.generateData(13, paddingLength, data[j - 1].slice(), j == data.length, false));
}
Parser parser = new Parser(new Parser.Listener.Adapter()
@ -146,7 +146,7 @@ public class DataGenerateParseTest
{
Generator generator = new Generator(byteBufferPool);
Generator.Result result = generator.generateContent(13, 1024, ByteBuffer.wrap(largeContent).slice(), true, false);
Generator.Result result = generator.generateData(13, 1024, ByteBuffer.wrap(largeContent).slice(), true, false);
final List<DataFrame> frames = new ArrayList<>();
Parser parser = new Parser(new Parser.Listener.Adapter()

View File

@ -0,0 +1,113 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.frames;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.junit.Assert;
import org.junit.Test;
public class GoAwayGenerateParseTest
{
private final ByteBufferPool byteBufferPool = new MappedByteBufferPool();
@Test
public void testGenerateParse() throws Exception
{
Generator generator = new Generator(byteBufferPool);
int lastStreamId = 13;
int error = 17;
// Iterate a few times to be sure generator and parser are properly reset.
final List<GoAwayFrame> frames = new ArrayList<>();
for (int i = 0; i < 2; ++i)
{
Generator.Result result = generator.generateGoAway(lastStreamId, error, null);
Parser parser = new Parser(new Parser.Listener.Adapter()
{
@Override
public boolean onGoAway(GoAwayFrame frame)
{
frames.add(frame);
return false;
}
});
frames.clear();
for (ByteBuffer buffer : result.getByteBuffers())
{
while (buffer.hasRemaining())
{
parser.parse(buffer);
}
}
}
Assert.assertEquals(1, frames.size());
GoAwayFrame frame = frames.get(0);
Assert.assertEquals(lastStreamId, frame.getLastStreamId());
Assert.assertEquals(error, frame.getError());
Assert.assertNull(frame.getPayload());
}
@Test
public void testGenerateParseOneByteAtATime() throws Exception
{
Generator generator = new Generator(byteBufferPool);
int lastStreamId = 13;
int error = 17;
byte[] payload = new byte[16];
new Random().nextBytes(payload);
final List<GoAwayFrame> frames = new ArrayList<>();
Generator.Result result = generator.generateGoAway(lastStreamId, error, payload);
Parser parser = new Parser(new Parser.Listener.Adapter()
{
@Override
public boolean onGoAway(GoAwayFrame frame)
{
frames.add(frame);
return false;
}
});
for (ByteBuffer buffer : result.getByteBuffers())
{
while (buffer.hasRemaining())
{
parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
}
}
Assert.assertEquals(1, frames.size());
GoAwayFrame frame = frames.get(0);
Assert.assertEquals(lastStreamId, frame.getLastStreamId());
Assert.assertEquals(error, frame.getError());
Assert.assertArrayEquals(payload, frame.getPayload());
}
}

View File

@ -1,3 +1,21 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.frames;
import java.nio.ByteBuffer;

View File

@ -1,3 +1,21 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.frames;
import java.nio.ByteBuffer;