1 /*
   2  * Copyright (c) 2015, 2016, 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 jdk.incubator.http.internal.common.ByteBufferReference;
  29 import jdk.incubator.http.internal.common.Utils;
  30 
  31 import java.nio.ByteBuffer;
  32 import java.util.ArrayList;
  33 import java.util.Arrays;
  34 import java.util.List;
  35 
  36 /**
  37  * Frames Encoder
  38  *
  39  * Encode framed into ByteBuffers.
  40  * The class is stateless.
  41  */
  42 public class FramesEncoder {
  43 
  44 
  45     public FramesEncoder() {
  46     }
  47 
  48     public ByteBufferReference[] encodeFrames(List<HeaderFrame> frames) {
  49         List<ByteBufferReference> refs = new ArrayList<>(frames.size() * 2);
  50         for (HeaderFrame f : frames) {
  51             refs.addAll(Arrays.asList(encodeFrame(f)));
  52         }
  53         return refs.toArray(new ByteBufferReference[0]);
  54     }
  55 
  56     public ByteBufferReference encodeConnectionPreface(byte[] preface, SettingsFrame frame) {
  57         final int length = frame.length();
  58         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length + preface.length);
  59         ByteBuffer buf = ref.get();
  60         buf.put(preface);
  61         putSettingsFrame(buf, frame, length);
  62         buf.flip();
  63         return ref;
  64     }
  65 
  66     public ByteBufferReference[] encodeFrame(Http2Frame frame) {
  67         switch (frame.type()) {
  68             case DataFrame.TYPE:
  69                 return encodeDataFrame((DataFrame) frame);
  70             case HeadersFrame.TYPE:
  71                 return encodeHeadersFrame((HeadersFrame) frame);
  72             case PriorityFrame.TYPE:
  73                 return encodePriorityFrame((PriorityFrame) frame);
  74             case ResetFrame.TYPE:
  75                 return encodeResetFrame((ResetFrame) frame);
  76             case SettingsFrame.TYPE:
  77                 return encodeSettingsFrame((SettingsFrame) frame);
  78             case PushPromiseFrame.TYPE:
  79                 return encodePushPromiseFrame((PushPromiseFrame) frame);
  80             case PingFrame.TYPE:
  81                 return encodePingFrame((PingFrame) frame);
  82             case GoAwayFrame.TYPE:
  83                 return encodeGoAwayFrame((GoAwayFrame) frame);
  84             case WindowUpdateFrame.TYPE:
  85                 return encodeWindowUpdateFrame((WindowUpdateFrame) frame);
  86             case ContinuationFrame.TYPE:
  87                 return encodeContinuationFrame((ContinuationFrame) frame);
  88             default:
  89                 throw new UnsupportedOperationException("Not supported frame "+frame.type()+" ("+frame.getClass().getName()+")");
  90         }
  91     }
  92 
  93     private static final int NO_FLAGS = 0;
  94     private static final int ZERO_STREAM = 0;
  95 
  96     private ByteBufferReference[] encodeDataFrame(DataFrame frame) {
  97         // non-zero stream
  98         assert frame.streamid() != 0;
  99         ByteBufferReference ref = encodeDataFrameStart(frame);
 100         if (frame.getFlag(DataFrame.PADDED)) {
 101             return joinWithPadding(ref, frame.getData(), frame.getPadLength());
 102         } else {
 103             return join(ref, frame.getData());
 104         }
 105     }
 106 
 107     private ByteBufferReference encodeDataFrameStart(DataFrame frame) {
 108         boolean isPadded = frame.getFlag(DataFrame.PADDED);
 109         final int length = frame.getDataLength() + (isPadded ? (frame.getPadLength() + 1) : 0);
 110         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0));
 111         ByteBuffer buf = ref.get();
 112         putHeader(buf, length, DataFrame.TYPE, frame.getFlags(), frame.streamid());
 113         if (isPadded) {
 114             buf.put((byte) frame.getPadLength());
 115         }
 116         buf.flip();
 117         return ref;
 118     }
 119 
 120     private ByteBufferReference[] encodeHeadersFrame(HeadersFrame frame) {
 121         // non-zero stream
 122         assert frame.streamid() != 0;
 123         ByteBufferReference ref = encodeHeadersFrameStart(frame);
 124         if (frame.getFlag(HeadersFrame.PADDED)) {
 125             return joinWithPadding(ref, frame.getHeaderBlock(), frame.getPadLength());
 126         } else {
 127             return join(ref, frame.getHeaderBlock());
 128         }
 129     }
 130 
 131     private ByteBufferReference encodeHeadersFrameStart(HeadersFrame frame) {
 132         boolean isPadded = frame.getFlag(HeadersFrame.PADDED);
 133         boolean hasPriority = frame.getFlag(HeadersFrame.PRIORITY);
 134         final int length = frame.getHeaderLength() + (isPadded ? (frame.getPadLength() + 1) : 0) + (hasPriority ? 5 : 0);
 135         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0) + (hasPriority ? 5 : 0));
 136         ByteBuffer buf = ref.get();
 137         putHeader(buf, length, HeadersFrame.TYPE, frame.getFlags(), frame.streamid());
 138         if (isPadded) {
 139             buf.put((byte) frame.getPadLength());
 140         }
 141         if (hasPriority) {
 142             putPriority(buf, frame.getExclusive(), frame.getStreamDependency(), frame.getWeight());
 143         }
 144         buf.flip();
 145         return ref;
 146     }
 147 
 148     private ByteBufferReference[] encodePriorityFrame(PriorityFrame frame) {
 149         // non-zero stream; no flags
 150         assert frame.streamid() != 0;
 151         final int length = 5;
 152         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 153         ByteBuffer buf = ref.get();
 154         putHeader(buf, length, PriorityFrame.TYPE, NO_FLAGS, frame.streamid());
 155         putPriority(buf, frame.exclusive(), frame.streamDependency(), frame.weight());
 156         buf.flip();
 157         return new ByteBufferReference[]{ref};
 158     }
 159 
 160     private ByteBufferReference[] encodeResetFrame(ResetFrame frame) {
 161         // non-zero stream; no flags
 162         assert frame.streamid() != 0;
 163         final int length = 4;
 164         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 165         ByteBuffer buf = ref.get();
 166         putHeader(buf, length, ResetFrame.TYPE, NO_FLAGS, frame.streamid());
 167         buf.putInt(frame.getErrorCode());
 168         buf.flip();
 169         return new ByteBufferReference[]{ref};
 170     }
 171 
 172     private ByteBufferReference[] encodeSettingsFrame(SettingsFrame frame) {
 173         // only zero stream
 174         assert frame.streamid() == 0;
 175         final int length = frame.length();
 176         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 177         ByteBuffer buf = ref.get();
 178         putSettingsFrame(buf, frame, length);
 179         buf.flip();
 180         return new ByteBufferReference[]{ref};
 181     }
 182 
 183     private ByteBufferReference[] encodePushPromiseFrame(PushPromiseFrame frame) {
 184         // non-zero stream
 185         assert frame.streamid() != 0;
 186         boolean isPadded = frame.getFlag(PushPromiseFrame.PADDED);
 187         final int length = frame.getHeaderLength() + (isPadded ? 5 : 4);
 188         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 5 : 4));
 189         ByteBuffer buf = ref.get();
 190         putHeader(buf, length, PushPromiseFrame.TYPE, frame.getFlags(), frame.streamid());
 191         if (isPadded) {
 192             buf.put((byte) frame.getPadLength());
 193         }
 194         buf.putInt(frame.getPromisedStream());
 195         buf.flip();
 196 
 197         if (frame.getFlag(PushPromiseFrame.PADDED)) {
 198             return joinWithPadding(ref, frame.getHeaderBlock(), frame.getPadLength());
 199         } else {
 200             return join(ref, frame.getHeaderBlock());
 201         }
 202     }
 203 
 204     private ByteBufferReference[] encodePingFrame(PingFrame frame) {
 205         // only zero stream
 206         assert frame.streamid() == 0;
 207         final int length = 8;
 208         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 209         ByteBuffer buf = ref.get();
 210         putHeader(buf, length, PingFrame.TYPE, frame.getFlags(), ZERO_STREAM);
 211         buf.put(frame.getData());
 212         buf.flip();
 213         return new ByteBufferReference[]{ref};
 214     }
 215 
 216     private ByteBufferReference[] encodeGoAwayFrame(GoAwayFrame frame) {
 217         // only zero stream; no flags
 218         assert frame.streamid() == 0;
 219         byte[] debugData = frame.getDebugData();
 220         final int length = 8 + debugData.length;
 221         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 222         ByteBuffer buf = ref.get();
 223         putHeader(buf, length, GoAwayFrame.TYPE, NO_FLAGS, ZERO_STREAM);
 224         buf.putInt(frame.getLastStream());
 225         buf.putInt(frame.getErrorCode());
 226         if (debugData.length > 0) {
 227             buf.put(debugData);
 228         }
 229         buf.flip();
 230         return new ByteBufferReference[]{ref};
 231     }
 232 
 233     private ByteBufferReference[] encodeWindowUpdateFrame(WindowUpdateFrame frame) {
 234         // any stream; no flags
 235         final int length = 4;
 236         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
 237         ByteBuffer buf = ref.get();
 238         putHeader(buf, length, WindowUpdateFrame.TYPE, NO_FLAGS, frame.streamid);
 239         buf.putInt(frame.getUpdate());
 240         buf.flip();
 241         return new ByteBufferReference[]{ref};
 242     }
 243 
 244     private ByteBufferReference[] encodeContinuationFrame(ContinuationFrame frame) {
 245         // non-zero stream;
 246         assert frame.streamid() != 0;
 247         final int length = frame.getHeaderLength();
 248         ByteBufferReference ref = getBuffer(Http2Frame.FRAME_HEADER_SIZE);
 249         ByteBuffer buf = ref.get();
 250         putHeader(buf, length, ContinuationFrame.TYPE, frame.getFlags(), frame.streamid());
 251         buf.flip();
 252         return join(ref, frame.getHeaderBlock());
 253     }
 254 
 255     private ByteBufferReference[] joinWithPadding(ByteBufferReference ref, ByteBufferReference[] data, int padLength) {
 256         ByteBufferReference[] references = new ByteBufferReference[2 + data.length];
 257         references[0] = ref;
 258         System.arraycopy(data, 0, references, 1, data.length);
 259         assert references[references.length - 1] == null;
 260         references[references.length - 1] = getPadding(padLength);
 261         return references;
 262     }
 263 
 264     private ByteBufferReference[] join(ByteBufferReference ref, ByteBufferReference[] data) {
 265         ByteBufferReference[] references = new ByteBufferReference[1 + data.length];
 266         references[0] = ref;
 267         System.arraycopy(data, 0, references, 1, data.length);
 268         return references;
 269     }
 270 
 271     private void putSettingsFrame(ByteBuffer buf, SettingsFrame frame, int length) {
 272         // only zero stream;
 273         assert frame.streamid() == 0;
 274         putHeader(buf, length, SettingsFrame.TYPE, frame.getFlags(), ZERO_STREAM);
 275         frame.toByteBuffer(buf);
 276     }
 277 
 278     private void putHeader(ByteBuffer buf, int length, int type, int flags, int streamId) {
 279         int x = (length << 8) + type;
 280         buf.putInt(x);
 281         buf.put((byte) flags);
 282         buf.putInt(streamId);
 283     }
 284 
 285     private void putPriority(ByteBuffer buf, boolean exclusive, int streamDependency, int weight) {
 286         buf.putInt(exclusive ? (1 << 31) + streamDependency : streamDependency);
 287         buf.put((byte) weight);
 288     }
 289 
 290     private ByteBufferReference getBuffer(int capacity) {
 291         return ByteBufferReference.of(ByteBuffer.allocate(capacity));
 292     }
 293 
 294     public ByteBufferReference getPadding(int length) {
 295         if (length > 255) {
 296             throw new IllegalArgumentException("Padding too big");
 297         }
 298         return ByteBufferReference.of(ByteBuffer.allocate(length)); // zeroed!
 299     }
 300 
 301 }