Implemented parser and generator for SETTINGS frame.
This commit is contained in:
parent
54577057bb
commit
2a485be6c1
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.util.Map;
|
||||
|
||||
public class SettingsFrame
|
||||
{
|
||||
private final Map<Integer, Integer> settings;
|
||||
private final boolean reply;
|
||||
|
||||
public SettingsFrame(Map<Integer, Integer> settings, boolean reply)
|
||||
{
|
||||
this.settings = settings;
|
||||
this.reply = reply;
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getSettings()
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
public boolean isReply()
|
||||
{
|
||||
return reply;
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.http2.generator;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.FrameType;
|
||||
|
@ -118,6 +119,24 @@ public class Generator
|
|||
return result;
|
||||
}
|
||||
|
||||
public Result generateSettings(Map<Integer, Integer> settings, boolean reply)
|
||||
{
|
||||
Result result = new Result(byteBufferPool);
|
||||
|
||||
ByteBuffer header = generateHeader(FrameType.SETTINGS, 5 * settings.size(), reply ? 0x01 : 0x00, 0);
|
||||
|
||||
for (Map.Entry<Integer, Integer> entry : settings.entrySet())
|
||||
{
|
||||
header.put(entry.getKey().byteValue());
|
||||
header.putInt(entry.getValue());
|
||||
}
|
||||
|
||||
BufferUtil.flipToFlush(header, 0);
|
||||
result.add(header, true);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Result generatePing(byte[] payload, boolean reply)
|
||||
{
|
||||
if (payload.length != 8)
|
||||
|
|
|
@ -25,6 +25,7 @@ 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;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -44,6 +45,12 @@ public abstract class BodyParser
|
|||
|
||||
public abstract Result parse(ByteBuffer buffer);
|
||||
|
||||
protected boolean emptyBody()
|
||||
{
|
||||
notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_frame");
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean hasFlag(int bit)
|
||||
{
|
||||
return headerParser.hasFlag(bit);
|
||||
|
@ -74,11 +81,6 @@ public abstract class BodyParser
|
|||
return headerParser.getLength();
|
||||
}
|
||||
|
||||
protected void reset()
|
||||
{
|
||||
headerParser.reset();
|
||||
}
|
||||
|
||||
protected boolean notifyData(DataFrame frame)
|
||||
{
|
||||
try
|
||||
|
@ -118,6 +120,19 @@ public abstract class BodyParser
|
|||
}
|
||||
}
|
||||
|
||||
protected boolean notifySettings(SettingsFrame frame)
|
||||
{
|
||||
try
|
||||
{
|
||||
return listener.onSettings(frame);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean notifyPing(PingFrame frame)
|
||||
{
|
||||
try
|
||||
|
@ -157,6 +172,20 @@ public abstract class BodyParser
|
|||
}
|
||||
}
|
||||
|
||||
protected Result notifyConnectionFailure(int error, String reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onConnectionFailure(error, reason);
|
||||
return Result.ASYNC;
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
return Result.ASYNC;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Result
|
||||
{
|
||||
PENDING, ASYNC, COMPLETE
|
||||
|
|
|
@ -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.util.BufferUtil;
|
||||
|
||||
public class DataBodyParser extends BodyParser
|
||||
{
|
||||
|
@ -33,15 +34,19 @@ public class DataBodyParser extends BodyParser
|
|||
super(headerParser, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reset()
|
||||
private void reset()
|
||||
{
|
||||
super.reset();
|
||||
state = State.PREPARE;
|
||||
paddingLength = 0;
|
||||
length = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean emptyBody()
|
||||
{
|
||||
return onData(BufferUtil.EMPTY_BUFFER, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result parse(ByteBuffer buffer)
|
||||
{
|
||||
|
@ -133,8 +138,7 @@ public class DataBodyParser extends BodyParser
|
|||
|
||||
private boolean onData(ByteBuffer buffer, boolean fragment)
|
||||
{
|
||||
boolean end = isEndStream();
|
||||
DataFrame frame = new DataFrame(getStreamId(), buffer, fragment ? false : end);
|
||||
DataFrame frame = new DataFrame(getStreamId(), buffer, !fragment && isEndStream());
|
||||
return notifyData(frame);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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;
|
||||
|
||||
public interface ErrorCode
|
||||
{
|
||||
public static final int NO_ERROR = 0;
|
||||
public static final int PROTOCOL_ERROR = 1;
|
||||
public static final int INTERNAL_ERROR = 2;
|
||||
public static final int FLOW_CONTROL_ERROR = 3;
|
||||
public static final int SETTINGS_TIMEOUT_ERROR = 4;
|
||||
public static final int STREAM_CLOSED_ERROR = 5;
|
||||
public static final int FRAME_SIZE_ERROR = 6;
|
||||
public static final int REFUSED_STREAM_ERROR = 7;
|
||||
public static final int CANCEL_STREAM_ERROR = 8;
|
||||
public static final int COMPRESSION_ERROR = 9;
|
||||
public static final int HTTP_CONNECT_ERROR = 10;
|
||||
public static final int ENHANCE_YOUR_CALM_ERROR = 11;
|
||||
public static final int INADEQUATE_SECURITY_ERROR = 12;
|
||||
}
|
|
@ -26,7 +26,6 @@ public class GoAwayBodyParser extends BodyParser
|
|||
{
|
||||
private State state = State.LAST_STREAM_ID;
|
||||
private int cursor;
|
||||
|
||||
private int lastStreamId;
|
||||
private int error;
|
||||
private byte[] payload;
|
||||
|
@ -36,13 +35,10 @@ public class GoAwayBodyParser extends BodyParser
|
|||
super(headerParser, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reset()
|
||||
private void reset()
|
||||
{
|
||||
super.reset();
|
||||
state = State.LAST_STREAM_ID;
|
||||
cursor = 0;
|
||||
|
||||
lastStreamId = 0;
|
||||
error = 0;
|
||||
payload = null;
|
||||
|
|
|
@ -43,6 +43,14 @@ public class HeaderParser
|
|||
streamId = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the header bytes in the given {@code buffer}; only the header
|
||||
* bytes are consumed, therefore the buffer may contain unconsumed bytes.
|
||||
*
|
||||
* @param buffer the buffer to parse
|
||||
* @return true if a whole header was parsed, false if not enough header
|
||||
* bytes were present in the buffer
|
||||
*/
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
|
|
|
@ -26,20 +26,27 @@ 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;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class Parser
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(Parser.class);
|
||||
|
||||
private final HeaderParser headerParser = new HeaderParser();
|
||||
private final BodyParser[] bodyParsers = new BodyParser[FrameType.values().length];
|
||||
private final Listener listener;
|
||||
private State state = State.HEADER;
|
||||
private BodyParser bodyParser;
|
||||
|
||||
public Parser(Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener);
|
||||
bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener);
|
||||
bodyParsers[FrameType.RST_STREAM.getType()] = new ResetBodyParser(headerParser, listener);
|
||||
bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener);
|
||||
bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener);
|
||||
bodyParsers[FrameType.GO_AWAY.getType()] = new GoAwayBodyParser(headerParser, listener);
|
||||
bodyParsers[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateBodyParser(headerParser, listener);
|
||||
|
@ -47,37 +54,67 @@ public class Parser
|
|||
|
||||
private void reset()
|
||||
{
|
||||
headerParser.reset();
|
||||
state = State.HEADER;
|
||||
}
|
||||
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
while (true)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case HEADER:
|
||||
{
|
||||
if (headerParser.parse(buffer))
|
||||
{
|
||||
int type = headerParser.getFrameType();
|
||||
bodyParser = bodyParsers[type];
|
||||
state = State.BODY;
|
||||
}
|
||||
if (!headerParser.parse(buffer))
|
||||
return false;
|
||||
state = State.BODY;
|
||||
break;
|
||||
}
|
||||
case BODY:
|
||||
{
|
||||
BodyParser.Result result = bodyParser.parse(buffer);
|
||||
if (result == BodyParser.Result.ASYNC)
|
||||
int type = headerParser.getFrameType();
|
||||
if (type < 0 || type >= bodyParsers.length)
|
||||
{
|
||||
// The content will be processed asynchronously, stop parsing;
|
||||
// the asynchronous operation will eventually resume parsing.
|
||||
return true;
|
||||
notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "unknown_frame_type_" + type);
|
||||
return false;
|
||||
}
|
||||
else if (result == BodyParser.Result.COMPLETE)
|
||||
BodyParser bodyParser = bodyParsers[type];
|
||||
if (headerParser.getLength() == 0)
|
||||
{
|
||||
boolean async = bodyParser.emptyBody();
|
||||
reset();
|
||||
if (async)
|
||||
return true;
|
||||
if (!buffer.hasRemaining())
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
BodyParser.Result result = bodyParser.parse(buffer);
|
||||
switch (result)
|
||||
{
|
||||
case PENDING:
|
||||
{
|
||||
// Not enough bytes.
|
||||
return false;
|
||||
}
|
||||
case ASYNC:
|
||||
{
|
||||
// The content will be processed asynchronously, stop parsing;
|
||||
// the asynchronous operation will eventually resume parsing.
|
||||
return true;
|
||||
}
|
||||
case COMPLETE:
|
||||
{
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -87,7 +124,18 @@ public class Parser
|
|||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void notifyConnectionFailure(int error, String reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onConnectionFailure(error, reason);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener
|
||||
|
@ -98,12 +146,16 @@ public class Parser
|
|||
|
||||
public boolean onReset(ResetFrame frame);
|
||||
|
||||
public boolean onSettings(SettingsFrame frame);
|
||||
|
||||
public boolean onPing(PingFrame frame);
|
||||
|
||||
public boolean onGoAway(GoAwayFrame frame);
|
||||
|
||||
public boolean onWindowUpdate(WindowUpdateFrame frame);
|
||||
|
||||
public void onConnectionFailure(int error, String reason);
|
||||
|
||||
public static class Adapter implements Listener
|
||||
{
|
||||
@Override
|
||||
|
@ -124,6 +176,12 @@ public class Parser
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSettings(SettingsFrame frame)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPing(PingFrame frame)
|
||||
{
|
||||
|
@ -141,6 +199,11 @@ public class Parser
|
|||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailure(int error, String reason)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,10 +33,8 @@ public class PingBodyParser extends BodyParser
|
|||
super(headerParser, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reset()
|
||||
private void reset()
|
||||
{
|
||||
super.reset();
|
||||
state = State.PAYLOAD;
|
||||
cursor = 0;
|
||||
payload = null;
|
||||
|
|
|
@ -26,7 +26,6 @@ public class PriorityBodyParser extends BodyParser
|
|||
{
|
||||
private State state = State.EXCLUSIVE;
|
||||
private int cursor;
|
||||
|
||||
private boolean exclusive;
|
||||
private int streamId;
|
||||
|
||||
|
@ -35,13 +34,10 @@ public class PriorityBodyParser extends BodyParser
|
|||
super(headerParser, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reset()
|
||||
private void reset()
|
||||
{
|
||||
super.reset();
|
||||
state = State.EXCLUSIVE;
|
||||
cursor = 0;
|
||||
|
||||
exclusive = false;
|
||||
streamId = 0;
|
||||
}
|
||||
|
|
|
@ -33,10 +33,8 @@ public class ResetBodyParser extends BodyParser
|
|||
super(headerParser, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reset()
|
||||
private void reset()
|
||||
{
|
||||
super.reset();
|
||||
state = State.ERROR;
|
||||
cursor = 0;
|
||||
error = 0;
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
|
||||
public class SettingsBodyParser extends BodyParser
|
||||
{
|
||||
private State state = State.PREPARE;
|
||||
private int cursor;
|
||||
private int length;
|
||||
private int settingId;
|
||||
private int settingValue;
|
||||
private Map<Integer, Integer> settings;
|
||||
|
||||
public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener)
|
||||
{
|
||||
super(headerParser, listener);
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.PREPARE;
|
||||
cursor = 0;
|
||||
length = 0;
|
||||
settingId = 0;
|
||||
settingValue = 0;
|
||||
settings = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean emptyBody()
|
||||
{
|
||||
return onSettings(new HashMap<Integer, Integer>()) == Result.ASYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PREPARE:
|
||||
{
|
||||
length = getBodyLength();
|
||||
settings = new HashMap<>();
|
||||
state = State.SETTING_ID;
|
||||
if (length == 0)
|
||||
{
|
||||
return onSettings(settings);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SETTING_ID:
|
||||
{
|
||||
settingId = buffer.get() & 0xFF;
|
||||
state = State.SETTING_VALUE;
|
||||
--length;
|
||||
if (length == 0)
|
||||
{
|
||||
return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_settings");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SETTING_VALUE:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
settingValue = buffer.getInt();
|
||||
settings.put(settingId, settingValue);
|
||||
state = State.SETTING_ID;
|
||||
length -= 4;
|
||||
if (length == 0)
|
||||
{
|
||||
return onSettings(settings);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor = 4;
|
||||
settingValue = 0;
|
||||
state = State.SETTING_VALUE_BYTES;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SETTING_VALUE_BYTES:
|
||||
{
|
||||
int currByte = buffer.get() & 0xFF;
|
||||
--cursor;
|
||||
settingValue += currByte << (8 * cursor);
|
||||
--length;
|
||||
if (cursor > 0 && length == 0)
|
||||
{
|
||||
return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_settings");
|
||||
}
|
||||
if (cursor == 0)
|
||||
{
|
||||
settings.put(settingId, settingValue);
|
||||
state = State.SETTING_ID;
|
||||
if (length == 0)
|
||||
{
|
||||
return onSettings(settings);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result.PENDING;
|
||||
}
|
||||
|
||||
private Result onSettings(Map<Integer, Integer> settings)
|
||||
{
|
||||
SettingsFrame frame = new SettingsFrame(settings, hasFlag(0x1));
|
||||
reset();
|
||||
return notifySettings(frame) ? Result.ASYNC : Result.COMPLETE;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
PREPARE, SETTING_ID, SETTING_VALUE, SETTING_VALUE_BYTES
|
||||
}
|
||||
}
|
|
@ -33,10 +33,8 @@ public class WindowUpdateBodyParser extends BodyParser
|
|||
super(headerParser, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reset()
|
||||
private void reset()
|
||||
{
|
||||
super.reset();
|
||||
state = State.WINDOW_DELTA;
|
||||
cursor = 0;
|
||||
windowDelta = 0;
|
||||
|
|
|
@ -27,6 +27,7 @@ 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.eclipse.jetty.util.BufferUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -43,6 +44,18 @@ public class DataGenerateParseTest
|
|||
random.nextBytes(largeContent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseNoContentNoPadding()
|
||||
{
|
||||
ByteBuffer content = BufferUtil.EMPTY_BUFFER;
|
||||
List<DataFrame> frames = testGenerateParse(0, content);
|
||||
Assert.assertEquals(1, frames.size());
|
||||
DataFrame frame = frames.get(0);
|
||||
Assert.assertTrue(frame.getStreamId() != 0);
|
||||
Assert.assertTrue(frame.isEnd());
|
||||
Assert.assertEquals(content, frame.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseSmallContentNoPadding()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.http2.generator.Generator;
|
||||
import org.eclipse.jetty.http2.parser.ErrorCode;
|
||||
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 SettingsGenerateParseTest
|
||||
{
|
||||
private final ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||
|
||||
@Test
|
||||
public void testGenerateParseNoSettings() throws Exception
|
||||
{
|
||||
List<SettingsFrame> frames = testGenerateParse(Collections.<Integer, Integer>emptyMap());
|
||||
Assert.assertEquals(1, frames.size());
|
||||
SettingsFrame frame = frames.get(0);
|
||||
Assert.assertEquals(0, frame.getSettings().size());
|
||||
Assert.assertTrue(frame.isReply());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseSettings() throws Exception
|
||||
{
|
||||
Map<Integer, Integer> settings1 = new HashMap<>();
|
||||
int key1 = 13;
|
||||
Integer value1 = 17;
|
||||
settings1.put(key1, value1);
|
||||
int key2 = 19;
|
||||
Integer value2 = 23;
|
||||
settings1.put(key2, value2);
|
||||
List<SettingsFrame> frames = testGenerateParse(settings1);
|
||||
Assert.assertEquals(1, frames.size());
|
||||
SettingsFrame frame = frames.get(0);
|
||||
Map<Integer, Integer> settings2 = frame.getSettings();
|
||||
Assert.assertEquals(2, settings2.size());
|
||||
Assert.assertEquals(value1, settings2.get(key1));
|
||||
Assert.assertEquals(value2, settings2.get(key2));
|
||||
}
|
||||
|
||||
private List<SettingsFrame> testGenerateParse(Map<Integer, Integer> settings)
|
||||
{
|
||||
Generator generator = new Generator(byteBufferPool);
|
||||
|
||||
// Iterate a few times to be sure generator and parser are properly reset.
|
||||
final List<SettingsFrame> frames = new ArrayList<>();
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
Generator.Result result = generator.generateSettings(settings, true);
|
||||
Parser parser = new Parser(new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public boolean onSettings(SettingsFrame frame)
|
||||
{
|
||||
frames.add(frame);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
frames.clear();
|
||||
for (ByteBuffer buffer : result.getByteBuffers())
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
parser.parse(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseInvalidSettings() throws Exception
|
||||
{
|
||||
Generator generator = new Generator(byteBufferPool);
|
||||
Map<Integer, Integer> settings1 = new HashMap<>();
|
||||
settings1.put(13, 17);
|
||||
Generator.Result result = generator.generateSettings(settings1, true);
|
||||
// Modify the length of the frame to make it invalid
|
||||
ByteBuffer bytes = result.getByteBuffers().get(0);
|
||||
bytes.putShort(0, (short)(bytes.getShort(0) - 1));
|
||||
|
||||
final AtomicInteger errorRef = new AtomicInteger();
|
||||
Parser parser = new Parser(new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onConnectionFailure(int error, String reason)
|
||||
{
|
||||
errorRef.set(error);
|
||||
}
|
||||
});
|
||||
|
||||
for (ByteBuffer buffer : result.getByteBuffers())
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertEquals(ErrorCode.PROTOCOL_ERROR, errorRef.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
Generator generator = new Generator(byteBufferPool);
|
||||
|
||||
Map<Integer, Integer> settings1 = new HashMap<>();
|
||||
int key = 13;
|
||||
Integer value = 17;
|
||||
settings1.put(key, value);
|
||||
|
||||
final List<SettingsFrame> frames = new ArrayList<>();
|
||||
Generator.Result result = generator.generateSettings(settings1, true);
|
||||
Parser parser = new Parser(new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public boolean onSettings(SettingsFrame 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());
|
||||
SettingsFrame frame = frames.get(0);
|
||||
Map<Integer, Integer> settings2 = frame.getSettings();
|
||||
Assert.assertEquals(1, settings2.size());
|
||||
Assert.assertEquals(value, settings2.get(key));
|
||||
Assert.assertTrue(frame.isReply());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue