/* * Copyright (c) 2015, 2017, 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 java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; /** * Frames Encoder * * Encode framed into ByteBuffers. * The class is stateless. */ public class FramesEncoder { public FramesEncoder() { } public List encodeFrames(List frames) { List bufs = new ArrayList<>(frames.size() * 2); for (HeaderFrame f : frames) { bufs.addAll(encodeFrame(f)); } return bufs; } public ByteBuffer encodeConnectionPreface(byte[] preface, SettingsFrame frame) { final int length = frame.length(); ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length + preface.length); buf.put(preface); putSettingsFrame(buf, frame, length); buf.flip(); return buf; } public List 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 List encodeDataFrame(DataFrame frame) { // non-zero stream assert frame.streamid() != 0; ByteBuffer buf = encodeDataFrameStart(frame); if (frame.getFlag(DataFrame.PADDED)) { return joinWithPadding(buf, frame.getData(), frame.getPadLength()); } else { return join(buf, frame.getData()); } } private ByteBuffer encodeDataFrameStart(DataFrame frame) { boolean isPadded = frame.getFlag(DataFrame.PADDED); final int length = frame.getDataLength() + (isPadded ? (frame.getPadLength() + 1) : 0); ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0)); putHeader(buf, length, DataFrame.TYPE, frame.getFlags(), frame.streamid()); if (isPadded) { buf.put((byte) frame.getPadLength()); } buf.flip(); return buf; } private List encodeHeadersFrame(HeadersFrame frame) { // non-zero stream assert frame.streamid() != 0; ByteBuffer buf = encodeHeadersFrameStart(frame); if (frame.getFlag(HeadersFrame.PADDED)) { return joinWithPadding(buf, frame.getHeaderBlock(), frame.getPadLength()); } else { return join(buf, frame.getHeaderBlock()); } } private ByteBuffer 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); ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0) + (hasPriority ? 5 : 0)); 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 buf; } private List encodePriorityFrame(PriorityFrame frame) { // non-zero stream; no flags assert frame.streamid() != 0; final int length = 5; ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length); putHeader(buf, length, PriorityFrame.TYPE, NO_FLAGS, frame.streamid()); putPriority(buf, frame.exclusive(), frame.streamDependency(), frame.weight()); buf.flip(); return List.of(buf); } private List encodeResetFrame(ResetFrame frame) { // non-zero stream; no flags assert frame.streamid() != 0; final int length = 4; ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length); putHeader(buf, length, ResetFrame.TYPE, NO_FLAGS, frame.streamid()); buf.putInt(frame.getErrorCode()); buf.flip(); return List.of(buf); } private List encodeSettingsFrame(SettingsFrame frame) { // only zero stream assert frame.streamid() == 0; final int length = frame.length(); ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length); putSettingsFrame(buf, frame, length); buf.flip(); return List.of(buf); } private List encodePushPromiseFrame(PushPromiseFrame frame) { // non-zero stream assert frame.streamid() != 0; boolean isPadded = frame.getFlag(PushPromiseFrame.PADDED); final int length = frame.getHeaderLength() + (isPadded ? 5 : 4); ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 5 : 4)); 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(buf, frame.getHeaderBlock(), frame.getPadLength()); } else { return join(buf, frame.getHeaderBlock()); } } private List encodePingFrame(PingFrame frame) { // only zero stream assert frame.streamid() == 0; final int length = 8; ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length); putHeader(buf, length, PingFrame.TYPE, frame.getFlags(), ZERO_STREAM); buf.put(frame.getData()); buf.flip(); return List.of(buf); } private List encodeGoAwayFrame(GoAwayFrame frame) { // only zero stream; no flags assert frame.streamid() == 0; byte[] debugData = frame.getDebugData(); final int length = 8 + debugData.length; ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length); 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 List.of(buf); } private List encodeWindowUpdateFrame(WindowUpdateFrame frame) { // any stream; no flags final int length = 4; ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length); putHeader(buf, length, WindowUpdateFrame.TYPE, NO_FLAGS, frame.streamid); buf.putInt(frame.getUpdate()); buf.flip(); return List.of(buf); } private List encodeContinuationFrame(ContinuationFrame frame) { // non-zero stream; assert frame.streamid() != 0; final int length = frame.getHeaderLength(); ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE); putHeader(buf, length, ContinuationFrame.TYPE, frame.getFlags(), frame.streamid()); buf.flip(); return join(buf, frame.getHeaderBlock()); } private List joinWithPadding(ByteBuffer buf, List data, int padLength) { int len = data.size(); if (len == 0) return List.of(buf, getPadding(padLength)); else if (len == 1) return List.of(buf, data.get(0), getPadding(padLength)); else if (len == 2) return List.of(buf, data.get(0), data.get(1), getPadding(padLength)); List res = new ArrayList<>(len+2); res.add(buf); res.addAll(data); res.add(getPadding(padLength)); return res; } private List join(ByteBuffer buf, List data) { int len = data.size(); if (len == 0) return List.of(buf); else if (len == 1) return List.of(buf, data.get(0)); else if (len == 2) return List.of(buf, data.get(0), data.get(1)); List joined = new ArrayList<>(len + 1); joined.add(buf); joined.addAll(data); return joined; } 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 ByteBuffer getBuffer(int capacity) { return ByteBuffer.allocate(capacity); } public ByteBuffer getPadding(int length) { if (length > 255) { throw new IllegalArgumentException("Padding too big"); } return ByteBuffer.allocate(length); // zeroed! } }