blob: dd7b1f035605ebab0ffe0984d93475836fd10fcc [file] [log] [blame]
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.incubator.http.internal.frame;
import jdk.incubator.http.internal.common.ByteBufferReference;
import jdk.incubator.http.internal.common.Utils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Frames Encoder
*
* Encode framed into ByteBuffers.
* The class is stateless.
*/
public class FramesEncoder {
public FramesEncoder() {
}
public ByteBufferReference[] encodeFrames(List<HeaderFrame> frames) {
List<ByteBufferReference> refs = new ArrayList<>(frames.size() * 2);
for (HeaderFrame f : frames) {
refs.addAll(Arrays.asList(encodeFrame(f)));
}
return refs.toArray(new ByteBufferReference[0]);
}
public ByteBufferReference encodeConnectionPreface(byte[] preface, SettingsFrame frame) {
final int length = frame.length();
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length + preface.length);
ByteBuffer buf = ref.get();
buf.put(preface);
putSettingsFrame(buf, frame, length);
buf.flip();
return ref;
}
public ByteBufferReference[] encodeFrame(Http2Frame frame) {
switch (frame.type()) {
case DataFrame.TYPE:
return encodeDataFrame((DataFrame) frame);
case HeadersFrame.TYPE:
return encodeHeadersFrame((HeadersFrame) frame);
case PriorityFrame.TYPE:
return encodePriorityFrame((PriorityFrame) frame);
case ResetFrame.TYPE:
return encodeResetFrame((ResetFrame) frame);
case SettingsFrame.TYPE:
return encodeSettingsFrame((SettingsFrame) frame);
case PushPromiseFrame.TYPE:
return encodePushPromiseFrame((PushPromiseFrame) frame);
case PingFrame.TYPE:
return encodePingFrame((PingFrame) frame);
case GoAwayFrame.TYPE:
return encodeGoAwayFrame((GoAwayFrame) frame);
case WindowUpdateFrame.TYPE:
return encodeWindowUpdateFrame((WindowUpdateFrame) frame);
case ContinuationFrame.TYPE:
return encodeContinuationFrame((ContinuationFrame) frame);
default:
throw new UnsupportedOperationException("Not supported frame "+frame.type()+" ("+frame.getClass().getName()+")");
}
}
private static final int NO_FLAGS = 0;
private static final int ZERO_STREAM = 0;
private ByteBufferReference[] encodeDataFrame(DataFrame frame) {
// non-zero stream
assert frame.streamid() != 0;
ByteBufferReference ref = encodeDataFrameStart(frame);
if (frame.getFlag(DataFrame.PADDED)) {
return joinWithPadding(ref, frame.getData(), frame.getPadLength());
} else {
return join(ref, frame.getData());
}
}
private ByteBufferReference encodeDataFrameStart(DataFrame frame) {
boolean isPadded = frame.getFlag(DataFrame.PADDED);
final int length = frame.getDataLength() + (isPadded ? (frame.getPadLength() + 1) : 0);
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0));
ByteBuffer buf = ref.get();
putHeader(buf, length, DataFrame.TYPE, frame.getFlags(), frame.streamid());
if (isPadded) {
buf.put((byte) frame.getPadLength());
}
buf.flip();
return ref;
}
private ByteBufferReference[] encodeHeadersFrame(HeadersFrame frame) {
// non-zero stream
assert frame.streamid() != 0;
ByteBufferReference ref = encodeHeadersFrameStart(frame);
if (frame.getFlag(HeadersFrame.PADDED)) {
return joinWithPadding(ref, frame.getHeaderBlock(), frame.getPadLength());
} else {
return join(ref, frame.getHeaderBlock());
}
}
private ByteBufferReference encodeHeadersFrameStart(HeadersFrame frame) {
boolean isPadded = frame.getFlag(HeadersFrame.PADDED);
boolean hasPriority = frame.getFlag(HeadersFrame.PRIORITY);
final int length = frame.getHeaderLength() + (isPadded ? (frame.getPadLength() + 1) : 0) + (hasPriority ? 5 : 0);
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0) + (hasPriority ? 5 : 0));
ByteBuffer buf = ref.get();
putHeader(buf, length, HeadersFrame.TYPE, frame.getFlags(), frame.streamid());
if (isPadded) {
buf.put((byte) frame.getPadLength());
}
if (hasPriority) {
putPriority(buf, frame.getExclusive(), frame.getStreamDependency(), frame.getWeight());
}
buf.flip();
return ref;
}
private ByteBufferReference[] encodePriorityFrame(PriorityFrame frame) {
// non-zero stream; no flags
assert frame.streamid() != 0;
final int length = 5;
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
ByteBuffer buf = ref.get();
putHeader(buf, length, PriorityFrame.TYPE, NO_FLAGS, frame.streamid());
putPriority(buf, frame.exclusive(), frame.streamDependency(), frame.weight());
buf.flip();
return new ByteBufferReference[]{ref};
}
private ByteBufferReference[] encodeResetFrame(ResetFrame frame) {
// non-zero stream; no flags
assert frame.streamid() != 0;
final int length = 4;
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
ByteBuffer buf = ref.get();
putHeader(buf, length, ResetFrame.TYPE, NO_FLAGS, frame.streamid());
buf.putInt(frame.getErrorCode());
buf.flip();
return new ByteBufferReference[]{ref};
}
private ByteBufferReference[] encodeSettingsFrame(SettingsFrame frame) {
// only zero stream
assert frame.streamid() == 0;
final int length = frame.length();
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
ByteBuffer buf = ref.get();
putSettingsFrame(buf, frame, length);
buf.flip();
return new ByteBufferReference[]{ref};
}
private ByteBufferReference[] encodePushPromiseFrame(PushPromiseFrame frame) {
// non-zero stream
assert frame.streamid() != 0;
boolean isPadded = frame.getFlag(PushPromiseFrame.PADDED);
final int length = frame.getHeaderLength() + (isPadded ? 5 : 4);
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 5 : 4));
ByteBuffer buf = ref.get();
putHeader(buf, length, PushPromiseFrame.TYPE, frame.getFlags(), frame.streamid());
if (isPadded) {
buf.put((byte) frame.getPadLength());
}
buf.putInt(frame.getPromisedStream());
buf.flip();
if (frame.getFlag(PushPromiseFrame.PADDED)) {
return joinWithPadding(ref, frame.getHeaderBlock(), frame.getPadLength());
} else {
return join(ref, frame.getHeaderBlock());
}
}
private ByteBufferReference[] encodePingFrame(PingFrame frame) {
// only zero stream
assert frame.streamid() == 0;
final int length = 8;
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
ByteBuffer buf = ref.get();
putHeader(buf, length, PingFrame.TYPE, frame.getFlags(), ZERO_STREAM);
buf.put(frame.getData());
buf.flip();
return new ByteBufferReference[]{ref};
}
private ByteBufferReference[] encodeGoAwayFrame(GoAwayFrame frame) {
// only zero stream; no flags
assert frame.streamid() == 0;
byte[] debugData = frame.getDebugData();
final int length = 8 + debugData.length;
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
ByteBuffer buf = ref.get();
putHeader(buf, length, GoAwayFrame.TYPE, NO_FLAGS, ZERO_STREAM);
buf.putInt(frame.getLastStream());
buf.putInt(frame.getErrorCode());
if (debugData.length > 0) {
buf.put(debugData);
}
buf.flip();
return new ByteBufferReference[]{ref};
}
private ByteBufferReference[] encodeWindowUpdateFrame(WindowUpdateFrame frame) {
// any stream; no flags
final int length = 4;
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
ByteBuffer buf = ref.get();
putHeader(buf, length, WindowUpdateFrame.TYPE, NO_FLAGS, frame.streamid);
buf.putInt(frame.getUpdate());
buf.flip();
return new ByteBufferReference[]{ref};
}
private ByteBufferReference[] encodeContinuationFrame(ContinuationFrame frame) {
// non-zero stream;
assert frame.streamid() != 0;
final int length = frame.getHeaderLength();
ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE);
ByteBuffer buf = ref.get();
putHeader(buf, length, ContinuationFrame.TYPE, frame.getFlags(), frame.streamid());
buf.flip();
return join(ref, frame.getHeaderBlock());
}
private ByteBufferReference[] joinWithPadding(ByteBufferReference ref, ByteBufferReference[] data, int padLength) {
ByteBufferReference[] references = new ByteBufferReference[2 + data.length];
references[0] = ref;
System.arraycopy(data, 0, references, 1, data.length);
assert references[references.length - 1] == null;
references[references.length - 1] = getPadding(padLength);
return references;
}
private ByteBufferReference[] join(ByteBufferReference ref, ByteBufferReference[] data) {
ByteBufferReference[] references = new ByteBufferReference[1 + data.length];
references[0] = ref;
System.arraycopy(data, 0, references, 1, data.length);
return references;
}
private void putSettingsFrame(ByteBuffer buf, SettingsFrame frame, int length) {
// only zero stream;
assert frame.streamid() == 0;
putHeader(buf, length, SettingsFrame.TYPE, frame.getFlags(), ZERO_STREAM);
frame.toByteBuffer(buf);
}
private void putHeader(ByteBuffer buf, int length, int type, int flags, int streamId) {
int x = (length << 8) + type;
buf.putInt(x);
buf.put((byte) flags);
buf.putInt(streamId);
}
private void putPriority(ByteBuffer buf, boolean exclusive, int streamDependency, int weight) {
buf.putInt(exclusive ? (1 << 31) + streamDependency : streamDependency);
buf.put((byte) weight);
}
private ByteBufferReference getBuffer(int capacity) {
return ByteBufferReference.of(ByteBuffer.allocate(capacity));
}
public ByteBufferReference getPadding(int length) {
if (length > 255) {
throw new IllegalArgumentException("Padding too big");
}
return ByteBufferReference.of(ByteBuffer.allocate(length)); // zeroed!
}
}