1 /*
   2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.incubator.http.internal.frame;
  27 
  28 import java.nio.ByteBuffer;
  29 import java.util.ArrayList;
  30 import java.util.List;
  31 
  32 /**
  33  * Frames Encoder
  34  *
  35  * Encode framed into ByteBuffers.
  36  * The class is stateless.
  37  */
  38 public class FramesEncoder {
  39 
  40 
  41     public FramesEncoder() {
  42     }
  43 
  44     public List<ByteBuffer> encodeFrames(List<HeaderFrame> frames) {
  45         List<ByteBuffer> bufs = new ArrayList<>(frames.size() * 2);
  46         for (HeaderFrame f : frames) {
  47             bufs.addAll(encodeFrame(f));
  48         }
  49         return bufs;
  50     }
  51 
  52     public ByteBuffer encodeConnectionPreface(byte[] preface, SettingsFrame frame) {
  53         final int length = frame.length();
  54         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length + preface.length);
  55         buf.put(preface);
  56         putSettingsFrame(buf, frame, length);
  57         buf.flip();
  58         return buf;
  59     }
  60 
  61     public List<ByteBuffer> encodeFrame(Http2Frame frame) {
  62         switch (frame.type()) {
  63             case DataFrame.TYPE:
  64                 return encodeDataFrame((DataFrame) frame);
  65             case HeadersFrame.TYPE:
  66                 return encodeHeadersFrame((HeadersFrame) frame);
  67             case PriorityFrame.TYPE:
  68                 return encodePriorityFrame((PriorityFrame) frame);
  69             case ResetFrame.TYPE:
  70                 return encodeResetFrame((ResetFrame) frame);
  71             case SettingsFrame.TYPE:
  72                 return encodeSettingsFrame((SettingsFrame) frame);
  73             case PushPromiseFrame.TYPE:
  74                 return encodePushPromiseFrame((PushPromiseFrame) frame);
  75             case PingFrame.TYPE:
  76                 return encodePingFrame((PingFrame) frame);
  77             case GoAwayFrame.TYPE:
  78                 return encodeGoAwayFrame((GoAwayFrame) frame);
  79             case WindowUpdateFrame.TYPE:
  80                 return encodeWindowUpdateFrame((WindowUpdateFrame) frame);
  81             case ContinuationFrame.TYPE:
  82                 return encodeContinuationFrame((ContinuationFrame) frame);
  83             default:
  84                 throw new UnsupportedOperationException("Not supported frame "+frame.type()+" ("+frame.getClass().getName()+")");
  85         }
  86     }
  87 
  88     private static final int NO_FLAGS = 0;
  89     private static final int ZERO_STREAM = 0;
  90 
  91     private List<ByteBuffer> encodeDataFrame(DataFrame frame) {
  92         // non-zero stream
  93         assert frame.streamid() != 0;
  94         ByteBuffer buf = encodeDataFrameStart(frame);
  95         if (frame.getFlag(DataFrame.PADDED)) {
  96             return joinWithPadding(buf, frame.getData(), frame.getPadLength());
  97         } else {
  98             return join(buf, frame.getData());
  99         }
 100     }
 101 
 102     private ByteBuffer encodeDataFrameStart(DataFrame frame) {
 103         boolean isPadded = frame.getFlag(DataFrame.PADDED);
 104         final int length = frame.getDataLength() + (isPadded ? (frame.getPadLength() + 1) : 0);
 105         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0));
 106         putHeader(buf, length, DataFrame.TYPE, frame.getFlags(), frame.streamid());
 107         if (isPadded) {
 108             buf.put((byte) frame.getPadLength());
 109         }
 110         buf.flip();
 111         return buf;
 112     }
 113 
 114     private List<ByteBuffer> encodeHeadersFrame(HeadersFrame frame) {
 115         // non-zero stream
 116         assert frame.streamid() != 0;
 117         ByteBuffer buf = encodeHeadersFrameStart(frame);
 118         if (frame.getFlag(HeadersFrame.PADDED)) {
 119             return joinWithPadding(buf, frame.getHeaderBlock(), frame.getPadLength());
 120         } else {
 121             return join(buf, frame.getHeaderBlock());
 122         }
 123     }
 124 
 125     private ByteBuffer encodeHeadersFrameStart(HeadersFrame frame) {
 126         boolean isPadded = frame.getFlag(HeadersFrame.PADDED);
 127         boolean hasPriority = frame.getFlag(HeadersFrame.PRIORITY);
 128         final int length = frame.getHeaderLength() + (isPadded ? (frame.getPadLength() + 1) : 0) + (hasPriority ? 5 : 0);
 129         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0) + (hasPriority ? 5 : 0));
 130         putHeader(buf, length, HeadersFrame.TYPE, frame.getFlags(), frame.streamid());
 131         if (isPadded) {
 132             buf.put((byte) frame.getPadLength());
 133         }
 134         if (hasPriority) {
 135             putPriority(buf, frame.getExclusive(), frame.getStreamDependency(), frame.getWeight());
 136         }
 137         buf.flip();
 138         return buf;
 139     }
 140 
 141     private List<ByteBuffer> encodePriorityFrame(PriorityFrame frame) {
 142         // non-zero stream; no flags
 143         assert frame.streamid() != 0;
 144         final int length = 5;
 145         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 146         putHeader(buf, length, PriorityFrame.TYPE, NO_FLAGS, frame.streamid());
 147         putPriority(buf, frame.exclusive(), frame.streamDependency(), frame.weight());
 148         buf.flip();
 149         return List.of(buf);
 150     }
 151 
 152     private List<ByteBuffer> encodeResetFrame(ResetFrame frame) {
 153         // non-zero stream; no flags
 154         assert frame.streamid() != 0;
 155         final int length = 4;
 156         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 157         putHeader(buf, length, ResetFrame.TYPE, NO_FLAGS, frame.streamid());
 158         buf.putInt(frame.getErrorCode());
 159         buf.flip();
 160         return List.of(buf);
 161     }
 162 
 163     private List<ByteBuffer> encodeSettingsFrame(SettingsFrame frame) {
 164         // only zero stream
 165         assert frame.streamid() == 0;
 166         final int length = frame.length();
 167         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 168         putSettingsFrame(buf, frame, length);
 169         buf.flip();
 170         return List.of(buf);
 171     }
 172 
 173     private List<ByteBuffer> encodePushPromiseFrame(PushPromiseFrame frame) {
 174         // non-zero stream
 175         assert frame.streamid() != 0;
 176         boolean isPadded = frame.getFlag(PushPromiseFrame.PADDED);
 177         final int length = frame.getHeaderLength() + (isPadded ? 5 : 4);
 178         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 5 : 4));
 179         putHeader(buf, length, PushPromiseFrame.TYPE, frame.getFlags(), frame.streamid());
 180         if (isPadded) {
 181             buf.put((byte) frame.getPadLength());
 182         }
 183         buf.putInt(frame.getPromisedStream());
 184         buf.flip();
 185 
 186         if (frame.getFlag(PushPromiseFrame.PADDED)) {
 187             return joinWithPadding(buf, frame.getHeaderBlock(), frame.getPadLength());
 188         } else {
 189             return join(buf, frame.getHeaderBlock());
 190         }
 191     }
 192 
 193     private List<ByteBuffer> encodePingFrame(PingFrame frame) {
 194         // only zero stream
 195         assert frame.streamid() == 0;
 196         final int length = 8;
 197         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 198         putHeader(buf, length, PingFrame.TYPE, frame.getFlags(), ZERO_STREAM);
 199         buf.put(frame.getData());
 200         buf.flip();
 201         return List.of(buf);
 202     }
 203 
 204     private List<ByteBuffer> encodeGoAwayFrame(GoAwayFrame frame) {
 205         // only zero stream; no flags
 206         assert frame.streamid() == 0;
 207         byte[] debugData = frame.getDebugData();
 208         final int length = 8 + debugData.length;
 209         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 210         putHeader(buf, length, GoAwayFrame.TYPE, NO_FLAGS, ZERO_STREAM);
 211         buf.putInt(frame.getLastStream());
 212         buf.putInt(frame.getErrorCode());
 213         if (debugData.length > 0) {
 214             buf.put(debugData);
 215         }
 216         buf.flip();
 217         return List.of(buf);
 218     }
 219 
 220     private List<ByteBuffer> encodeWindowUpdateFrame(WindowUpdateFrame frame) {
 221         // any stream; no flags
 222         final int length = 4;
 223         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 224         putHeader(buf, length, WindowUpdateFrame.TYPE, NO_FLAGS, frame.streamid);
 225         buf.putInt(frame.getUpdate());
 226         buf.flip();
 227         return List.of(buf);
 228     }
 229 
 230     private List<ByteBuffer> encodeContinuationFrame(ContinuationFrame frame) {
 231         // non-zero stream;
 232         assert frame.streamid() != 0;
 233         final int length = frame.getHeaderLength();
 234         ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE);
 235         putHeader(buf, length, ContinuationFrame.TYPE, frame.getFlags(), frame.streamid());
 236         buf.flip();
 237         return join(buf, frame.getHeaderBlock());
 238     }
 239 
 240     private List<ByteBuffer> joinWithPadding(ByteBuffer buf, List<ByteBuffer> data, int padLength) {
 241         int len = data.size();
 242         if (len == 0) return List.of(buf, getPadding(padLength));
 243         else if (len == 1) return List.of(buf, data.get(0), getPadding(padLength));
 244         else if (len == 2) return List.of(buf, data.get(0), data.get(1), getPadding(padLength));
 245         List<ByteBuffer> res = new ArrayList<>(len+2);
 246         res.add(buf);
 247         res.addAll(data);
 248         res.add(getPadding(padLength));
 249         return res;
 250     }
 251 
 252     private List<ByteBuffer> join(ByteBuffer buf, List<ByteBuffer> data) {
 253         int len = data.size();
 254         if (len == 0) return List.of(buf);
 255         else if (len == 1) return List.of(buf, data.get(0));
 256         else if (len == 2) return List.of(buf, data.get(0), data.get(1));
 257         List<ByteBuffer> joined = new ArrayList<>(len + 1);
 258         joined.add(buf);
 259         joined.addAll(data);
 260         return joined;
 261     }
 262 
 263     private void putSettingsFrame(ByteBuffer buf, SettingsFrame frame, int length) {
 264         // only zero stream;
 265         assert frame.streamid() == 0;
 266         putHeader(buf, length, SettingsFrame.TYPE, frame.getFlags(), ZERO_STREAM);
 267         frame.toByteBuffer(buf);
 268     }
 269 
 270     private void putHeader(ByteBuffer buf, int length, int type, int flags, int streamId) {
 271         int x = (length << 8) + type;
 272         buf.putInt(x);
 273         buf.put((byte) flags);
 274         buf.putInt(streamId);
 275     }
 276 
 277     private void putPriority(ByteBuffer buf, boolean exclusive, int streamDependency, int weight) {
 278         buf.putInt(exclusive ? (1 << 31) + streamDependency : streamDependency);
 279         buf.put((byte) weight);
 280     }
 281 
 282     private ByteBuffer getBuffer(int capacity) {
 283         return ByteBuffer.allocate(capacity);
 284     }
 285 
 286     public ByteBuffer getPadding(int length) {
 287         if (length > 255) {
 288             throw new IllegalArgumentException("Padding too big");
 289         }
 290         return ByteBuffer.allocate(length); // zeroed!
 291     }
 292 
 293 }