FramesEncoder.java revision 16234:3b25414eb6af
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
26package jdk.incubator.http.internal.frame;
27
28import jdk.incubator.http.internal.common.ByteBufferReference;
29import jdk.incubator.http.internal.common.Utils;
30
31import java.nio.ByteBuffer;
32import java.util.ArrayList;
33import java.util.Arrays;
34import java.util.List;
35
36/**
37 * Frames Encoder
38 *
39 * Encode framed into ByteBuffers.
40 * The class is stateless.
41 */
42public 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}
302