View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at:
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  
16  package io.netty.handler.codec.http2;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelFuture;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.ChannelPromise;
22  import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator;
23  import io.netty.handler.codec.http2.Http2FrameWriter.Configuration;
24  import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector;
25  import io.netty.util.internal.PlatformDependent;
26  import io.netty.util.internal.UnstableApi;
27  
28  import static io.netty.buffer.Unpooled.directBuffer;
29  import static io.netty.buffer.Unpooled.unreleasableBuffer;
30  import static io.netty.handler.codec.http2.Http2CodecUtil.CONTINUATION_FRAME_HEADER_LENGTH;
31  import static io.netty.handler.codec.http2.Http2CodecUtil.DATA_FRAME_HEADER_LENGTH;
32  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE;
33  import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
34  import static io.netty.handler.codec.http2.Http2CodecUtil.GO_AWAY_FRAME_HEADER_LENGTH;
35  import static io.netty.handler.codec.http2.Http2CodecUtil.HEADERS_FRAME_HEADER_LENGTH;
36  import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
37  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE;
38  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
39  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT;
40  import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT;
41  import static io.netty.handler.codec.http2.Http2CodecUtil.PING_FRAME_PAYLOAD_LENGTH;
42  import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
43  import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_FRAME_LENGTH;
44  import static io.netty.handler.codec.http2.Http2CodecUtil.PUSH_PROMISE_FRAME_HEADER_LENGTH;
45  import static io.netty.handler.codec.http2.Http2CodecUtil.RST_STREAM_FRAME_LENGTH;
46  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
47  import static io.netty.handler.codec.http2.Http2CodecUtil.WINDOW_UPDATE_FRAME_LENGTH;
48  import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid;
49  import static io.netty.handler.codec.http2.Http2CodecUtil.verifyPadding;
50  import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeaderInternal;
51  import static io.netty.handler.codec.http2.Http2Error.FRAME_SIZE_ERROR;
52  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
53  import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
54  import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
55  import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
56  import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS;
57  import static io.netty.handler.codec.http2.Http2FrameTypes.PING;
58  import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY;
59  import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
60  import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM;
61  import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
62  import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
63  import static io.netty.util.internal.ObjectUtil.checkNotNull;
64  import static java.lang.Math.max;
65  import static java.lang.Math.min;
66  
67  /**
68   * A {@link Http2FrameWriter} that supports all frame types defined by the HTTP/2 specification.
69   */
70  @UnstableApi
71  public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSizePolicy, Configuration {
72      private static final String STREAM_ID = "Stream ID";
73      private static final String STREAM_DEPENDENCY = "Stream Dependency";
74      /**
75       * This buffer is allocated to the maximum size of the padding field, and filled with zeros.
76       * When padding is needed it can be taken as a slice of this buffer. Users should call {@link ByteBuf#retain()}
77       * before using their slice.
78       */
79      private static final ByteBuf ZERO_BUFFER =
80              unreleasableBuffer(directBuffer(MAX_UNSIGNED_BYTE).writeZero(MAX_UNSIGNED_BYTE)).asReadOnly();
81  
82      private final Http2HeadersEncoder headersEncoder;
83      private int maxFrameSize;
84  
85      public DefaultHttp2FrameWriter() {
86          this(new DefaultHttp2HeadersEncoder());
87      }
88  
89      public DefaultHttp2FrameWriter(SensitivityDetector headersSensitivityDetector) {
90          this(new DefaultHttp2HeadersEncoder(headersSensitivityDetector));
91      }
92  
93      public DefaultHttp2FrameWriter(SensitivityDetector headersSensitivityDetector, boolean ignoreMaxHeaderListSize) {
94          this(new DefaultHttp2HeadersEncoder(headersSensitivityDetector, ignoreMaxHeaderListSize));
95      }
96  
97      public DefaultHttp2FrameWriter(Http2HeadersEncoder headersEncoder) {
98          this.headersEncoder = headersEncoder;
99          maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
100     }
101 
102     @Override
103     public Configuration configuration() {
104         return this;
105     }
106 
107     @Override
108     public Http2HeadersEncoder.Configuration headersConfiguration() {
109         return headersEncoder.configuration();
110     }
111 
112     @Override
113     public Http2FrameSizePolicy frameSizePolicy() {
114         return this;
115     }
116 
117     @Override
118     public void maxFrameSize(int max) throws Http2Exception {
119         if (!isMaxFrameSizeValid(max)) {
120             throw connectionError(FRAME_SIZE_ERROR, "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max);
121         }
122         maxFrameSize = max;
123     }
124 
125     @Override
126     public int maxFrameSize() {
127         return maxFrameSize;
128     }
129 
130     @Override
131     public void close() { }
132 
133     @Override
134     public ChannelFuture writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
135             int padding, boolean endStream, ChannelPromise promise) {
136         final SimpleChannelPromiseAggregator promiseAggregator =
137                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
138         ByteBuf frameHeader = null;
139         try {
140             verifyStreamId(streamId, STREAM_ID);
141             verifyPadding(padding);
142 
143             int remainingData = data.readableBytes();
144             Http2Flags flags = new Http2Flags();
145             flags.endOfStream(false);
146             flags.paddingPresent(false);
147             // Fast path to write frames of payload size maxFrameSize first.
148             if (remainingData > maxFrameSize) {
149                 frameHeader = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
150                 writeFrameHeaderInternal(frameHeader, maxFrameSize, DATA, flags, streamId);
151                 do {
152                     // Write the header.
153                     ctx.write(frameHeader.retainedSlice(), promiseAggregator.newPromise());
154 
155                     // Write the payload.
156                     ctx.write(data.readRetainedSlice(maxFrameSize), promiseAggregator.newPromise());
157 
158                     remainingData -= maxFrameSize;
159                     // Stop iterating if remainingData == maxFrameSize so we can take care of reference counts below.
160                 } while (remainingData > maxFrameSize);
161             }
162 
163             if (padding == 0) {
164                 // Write the header.
165                 if (frameHeader != null) {
166                     frameHeader.release();
167                     frameHeader = null;
168                 }
169                 ByteBuf frameHeader2 = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
170                 flags.endOfStream(endStream);
171                 writeFrameHeaderInternal(frameHeader2, remainingData, DATA, flags, streamId);
172                 ctx.write(frameHeader2, promiseAggregator.newPromise());
173 
174                 // Write the payload.
175                 ByteBuf lastFrame = data.readSlice(remainingData);
176                 data = null;
177                 ctx.write(lastFrame, promiseAggregator.newPromise());
178             } else {
179                 if (remainingData != maxFrameSize) {
180                     if (frameHeader != null) {
181                         frameHeader.release();
182                         frameHeader = null;
183                     }
184                 } else {
185                     remainingData -= maxFrameSize;
186                     // Write the header.
187                     ByteBuf lastFrame;
188                     if (frameHeader == null) {
189                         lastFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
190                         writeFrameHeaderInternal(lastFrame, maxFrameSize, DATA, flags, streamId);
191                     } else {
192                         lastFrame = frameHeader.slice();
193                         frameHeader = null;
194                     }
195                     ctx.write(lastFrame, promiseAggregator.newPromise());
196 
197                     // Write the payload.
198                     lastFrame = data.readSlice(maxFrameSize);
199                     data = null;
200                     ctx.write(lastFrame, promiseAggregator.newPromise());
201                 }
202 
203                 do {
204                     int frameDataBytes = min(remainingData, maxFrameSize);
205                     int framePaddingBytes = min(padding, max(0, (maxFrameSize - 1) - frameDataBytes));
206 
207                     // Decrement the remaining counters.
208                     padding -= framePaddingBytes;
209                     remainingData -= frameDataBytes;
210 
211                     // Write the header.
212                     ByteBuf frameHeader2 = ctx.alloc().buffer(DATA_FRAME_HEADER_LENGTH);
213                     flags.endOfStream(endStream && remainingData == 0 && padding == 0);
214                     flags.paddingPresent(framePaddingBytes > 0);
215                     writeFrameHeaderInternal(frameHeader2, framePaddingBytes + frameDataBytes, DATA, flags, streamId);
216                     writePaddingLength(frameHeader2, framePaddingBytes);
217                     ctx.write(frameHeader2, promiseAggregator.newPromise());
218 
219                     // Write the payload.
220                     if (frameDataBytes != 0) {
221                         if (remainingData == 0) {
222                             ByteBuf lastFrame = data.readSlice(frameDataBytes);
223                             data = null;
224                             ctx.write(lastFrame, promiseAggregator.newPromise());
225                         } else {
226                             ctx.write(data.readRetainedSlice(frameDataBytes), promiseAggregator.newPromise());
227                         }
228                     }
229                     // Write the frame padding.
230                     if (paddingBytes(framePaddingBytes) > 0) {
231                         ctx.write(ZERO_BUFFER.slice(0, paddingBytes(framePaddingBytes)),
232                                   promiseAggregator.newPromise());
233                     }
234                 } while (remainingData != 0 || padding != 0);
235             }
236         } catch (Throwable cause) {
237             if (frameHeader != null) {
238                 frameHeader.release();
239             }
240             // Use a try/finally here in case the data has been released before calling this method. This is not
241             // necessary above because we internally allocate frameHeader.
242             try {
243                 if (data != null) {
244                     data.release();
245                 }
246             } finally {
247                 promiseAggregator.setFailure(cause);
248                 promiseAggregator.doneAllocatingPromises();
249             }
250             return promiseAggregator;
251         }
252         return promiseAggregator.doneAllocatingPromises();
253     }
254 
255     @Override
256     public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
257             Http2Headers headers, int padding, boolean endStream, ChannelPromise promise) {
258         return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
259                 false, 0, (short) 0, false, promise);
260     }
261 
262     @Override
263     public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
264             Http2Headers headers, int streamDependency, short weight, boolean exclusive,
265             int padding, boolean endStream, ChannelPromise promise) {
266         return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
267                 true, streamDependency, weight, exclusive, promise);
268     }
269 
270     @Override
271     public ChannelFuture writePriority(ChannelHandlerContext ctx, int streamId,
272             int streamDependency, short weight, boolean exclusive, ChannelPromise promise) {
273         try {
274             verifyStreamId(streamId, STREAM_ID);
275             verifyStreamId(streamDependency, STREAM_DEPENDENCY);
276             verifyWeight(weight);
277 
278             ByteBuf buf = ctx.alloc().buffer(PRIORITY_FRAME_LENGTH);
279             writeFrameHeaderInternal(buf, PRIORITY_ENTRY_LENGTH, PRIORITY, new Http2Flags(), streamId);
280             buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency);
281             // Adjust the weight so that it fits into a single byte on the wire.
282             buf.writeByte(weight - 1);
283             return ctx.write(buf, promise);
284         } catch (Throwable t) {
285             return promise.setFailure(t);
286         }
287     }
288 
289     @Override
290     public ChannelFuture writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
291             ChannelPromise promise) {
292         try {
293             verifyStreamId(streamId, STREAM_ID);
294             verifyErrorCode(errorCode);
295 
296             ByteBuf buf = ctx.alloc().buffer(RST_STREAM_FRAME_LENGTH);
297             writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(), streamId);
298             buf.writeInt((int) errorCode);
299             return ctx.write(buf, promise);
300         } catch (Throwable t) {
301             return promise.setFailure(t);
302         }
303     }
304 
305     @Override
306     public ChannelFuture writeSettings(ChannelHandlerContext ctx, Http2Settings settings,
307             ChannelPromise promise) {
308         try {
309             checkNotNull(settings, "settings");
310             int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
311             ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + settings.size() * SETTING_ENTRY_LENGTH);
312             writeFrameHeaderInternal(buf, payloadLength, SETTINGS, new Http2Flags(), 0);
313             for (Http2Settings.PrimitiveEntry<Long> entry : settings.entries()) {
314                 buf.writeChar(entry.key());
315                 buf.writeInt(entry.value().intValue());
316             }
317             return ctx.write(buf, promise);
318         } catch (Throwable t) {
319             return promise.setFailure(t);
320         }
321     }
322 
323     @Override
324     public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
325         try {
326             ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
327             writeFrameHeaderInternal(buf, 0, SETTINGS, new Http2Flags().ack(true), 0);
328             return ctx.write(buf, promise);
329         } catch (Throwable t) {
330             return promise.setFailure(t);
331         }
332     }
333 
334     @Override
335     public ChannelFuture writePing(ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise) {
336         Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags();
337         ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PING_FRAME_PAYLOAD_LENGTH);
338         // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership
339         // in the catch block.
340         writeFrameHeaderInternal(buf, PING_FRAME_PAYLOAD_LENGTH, PING, flags, 0);
341         buf.writeLong(data);
342         return ctx.write(buf, promise);
343     }
344 
345     @Override
346     public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId,
347             int promisedStreamId, Http2Headers headers, int padding, ChannelPromise promise) {
348         ByteBuf headerBlock = null;
349         SimpleChannelPromiseAggregator promiseAggregator =
350                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
351         try {
352             verifyStreamId(streamId, STREAM_ID);
353             verifyStreamId(promisedStreamId, "Promised Stream ID");
354             verifyPadding(padding);
355 
356             // Encode the entire header block into an intermediate buffer.
357             headerBlock = ctx.alloc().buffer();
358             headersEncoder.encodeHeaders(streamId, headers, headerBlock);
359 
360             // Read the first fragment (possibly everything).
361             Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
362             // INT_FIELD_LENGTH is for the length of the promisedStreamId
363             int nonFragmentLength = INT_FIELD_LENGTH + padding;
364             int maxFragmentLength = maxFrameSize - nonFragmentLength;
365             ByteBuf fragment = headerBlock.readRetainedSlice(min(headerBlock.readableBytes(), maxFragmentLength));
366 
367             flags.endOfHeaders(!headerBlock.isReadable());
368 
369             int payloadLength = fragment.readableBytes() + nonFragmentLength;
370             ByteBuf buf = ctx.alloc().buffer(PUSH_PROMISE_FRAME_HEADER_LENGTH);
371             writeFrameHeaderInternal(buf, payloadLength, PUSH_PROMISE, flags, streamId);
372             writePaddingLength(buf, padding);
373 
374             // Write out the promised stream ID.
375             buf.writeInt(promisedStreamId);
376             ctx.write(buf, promiseAggregator.newPromise());
377 
378             // Write the first fragment.
379             ctx.write(fragment, promiseAggregator.newPromise());
380 
381             // Write out the padding, if any.
382             if (paddingBytes(padding) > 0) {
383                 ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise());
384             }
385 
386             if (!flags.endOfHeaders()) {
387                 writeContinuationFrames(ctx, streamId, headerBlock, padding, promiseAggregator);
388             }
389         } catch (Http2Exception e) {
390             promiseAggregator.setFailure(e);
391         } catch (Throwable t) {
392             promiseAggregator.setFailure(t);
393             promiseAggregator.doneAllocatingPromises();
394             PlatformDependent.throwException(t);
395         } finally {
396             if (headerBlock != null) {
397                 headerBlock.release();
398             }
399         }
400         return promiseAggregator.doneAllocatingPromises();
401     }
402 
403     @Override
404     public ChannelFuture writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
405             ByteBuf debugData, ChannelPromise promise) {
406         SimpleChannelPromiseAggregator promiseAggregator =
407                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
408         try {
409             verifyStreamOrConnectionId(lastStreamId, "Last Stream ID");
410             verifyErrorCode(errorCode);
411 
412             int payloadLength = 8 + debugData.readableBytes();
413             ByteBuf buf = ctx.alloc().buffer(GO_AWAY_FRAME_HEADER_LENGTH);
414             // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership
415             // in the catch block.
416             writeFrameHeaderInternal(buf, payloadLength, GO_AWAY, new Http2Flags(), 0);
417             buf.writeInt(lastStreamId);
418             buf.writeInt((int) errorCode);
419             ctx.write(buf, promiseAggregator.newPromise());
420         } catch (Throwable t) {
421             try {
422                 debugData.release();
423             } finally {
424                 promiseAggregator.setFailure(t);
425                 promiseAggregator.doneAllocatingPromises();
426             }
427             return promiseAggregator;
428         }
429 
430         try {
431             ctx.write(debugData, promiseAggregator.newPromise());
432         } catch (Throwable t) {
433             promiseAggregator.setFailure(t);
434         }
435         return promiseAggregator.doneAllocatingPromises();
436     }
437 
438     @Override
439     public ChannelFuture writeWindowUpdate(ChannelHandlerContext ctx, int streamId,
440             int windowSizeIncrement, ChannelPromise promise) {
441         try {
442             verifyStreamOrConnectionId(streamId, STREAM_ID);
443             verifyWindowSizeIncrement(windowSizeIncrement);
444 
445             ByteBuf buf = ctx.alloc().buffer(WINDOW_UPDATE_FRAME_LENGTH);
446             writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, WINDOW_UPDATE, new Http2Flags(), streamId);
447             buf.writeInt(windowSizeIncrement);
448             return ctx.write(buf, promise);
449         } catch (Throwable t) {
450             return promise.setFailure(t);
451         }
452     }
453 
454     @Override
455     public ChannelFuture writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
456             Http2Flags flags, ByteBuf payload, ChannelPromise promise) {
457         SimpleChannelPromiseAggregator promiseAggregator =
458                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
459         try {
460             verifyStreamOrConnectionId(streamId, STREAM_ID);
461             ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
462             // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership
463             // in the catch block.
464             writeFrameHeaderInternal(buf, payload.readableBytes(), frameType, flags, streamId);
465             ctx.write(buf, promiseAggregator.newPromise());
466         } catch (Throwable t) {
467             try {
468                 payload.release();
469             } finally {
470                 promiseAggregator.setFailure(t);
471                 promiseAggregator.doneAllocatingPromises();
472             }
473             return promiseAggregator;
474         }
475         try {
476             ctx.write(payload, promiseAggregator.newPromise());
477         } catch (Throwable t) {
478             promiseAggregator.setFailure(t);
479         }
480         return promiseAggregator.doneAllocatingPromises();
481     }
482 
483     private ChannelFuture writeHeadersInternal(ChannelHandlerContext ctx,
484             int streamId, Http2Headers headers, int padding, boolean endStream,
485             boolean hasPriority, int streamDependency, short weight, boolean exclusive, ChannelPromise promise) {
486         ByteBuf headerBlock = null;
487         SimpleChannelPromiseAggregator promiseAggregator =
488                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
489         try {
490             verifyStreamId(streamId, STREAM_ID);
491             if (hasPriority) {
492                 verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY);
493                 verifyPadding(padding);
494                 verifyWeight(weight);
495             }
496 
497             // Encode the entire header block.
498             headerBlock = ctx.alloc().buffer();
499             headersEncoder.encodeHeaders(streamId, headers, headerBlock);
500 
501             Http2Flags flags =
502                     new Http2Flags().endOfStream(endStream).priorityPresent(hasPriority).paddingPresent(padding > 0);
503 
504             // Read the first fragment (possibly everything).
505             int nonFragmentBytes = padding + flags.getNumPriorityBytes();
506             int maxFragmentLength = maxFrameSize - nonFragmentBytes;
507             ByteBuf fragment = headerBlock.readRetainedSlice(min(headerBlock.readableBytes(), maxFragmentLength));
508 
509             // Set the end of headers flag for the first frame.
510             flags.endOfHeaders(!headerBlock.isReadable());
511 
512             int payloadLength = fragment.readableBytes() + nonFragmentBytes;
513             ByteBuf buf = ctx.alloc().buffer(HEADERS_FRAME_HEADER_LENGTH);
514             writeFrameHeaderInternal(buf, payloadLength, HEADERS, flags, streamId);
515             writePaddingLength(buf, padding);
516 
517             if (hasPriority) {
518                 buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency);
519 
520                 // Adjust the weight so that it fits into a single byte on the wire.
521                 buf.writeByte(weight - 1);
522             }
523             ctx.write(buf, promiseAggregator.newPromise());
524 
525             // Write the first fragment.
526             ctx.write(fragment, promiseAggregator.newPromise());
527 
528             // Write out the padding, if any.
529             if (paddingBytes(padding) > 0) {
530                 ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise());
531             }
532 
533             if (!flags.endOfHeaders()) {
534                 writeContinuationFrames(ctx, streamId, headerBlock, padding, promiseAggregator);
535             }
536         } catch (Http2Exception e) {
537             promiseAggregator.setFailure(e);
538         } catch (Throwable t) {
539             promiseAggregator.setFailure(t);
540             promiseAggregator.doneAllocatingPromises();
541             PlatformDependent.throwException(t);
542         } finally {
543             if (headerBlock != null) {
544                 headerBlock.release();
545             }
546         }
547         return promiseAggregator.doneAllocatingPromises();
548     }
549 
550     /**
551      * Writes as many continuation frames as needed until {@code padding} and {@code headerBlock} are consumed.
552      */
553     private ChannelFuture writeContinuationFrames(ChannelHandlerContext ctx, int streamId,
554             ByteBuf headerBlock, int padding, SimpleChannelPromiseAggregator promiseAggregator) {
555         Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
556         int maxFragmentLength = maxFrameSize - padding;
557         // TODO: same padding is applied to all frames, is this desired?
558         if (maxFragmentLength <= 0) {
559             return promiseAggregator.setFailure(new IllegalArgumentException(
560                     "Padding [" + padding + "] is too large for max frame size [" + maxFrameSize + "]"));
561         }
562 
563         if (headerBlock.isReadable()) {
564             // The frame header (and padding) only changes on the last frame, so allocate it once and re-use
565             int fragmentReadableBytes = min(headerBlock.readableBytes(), maxFragmentLength);
566             int payloadLength = fragmentReadableBytes + padding;
567             ByteBuf buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH);
568             writeFrameHeaderInternal(buf, payloadLength, CONTINUATION, flags, streamId);
569             writePaddingLength(buf, padding);
570 
571             do {
572                 fragmentReadableBytes = min(headerBlock.readableBytes(), maxFragmentLength);
573                 ByteBuf fragment = headerBlock.readRetainedSlice(fragmentReadableBytes);
574 
575                 payloadLength = fragmentReadableBytes + padding;
576                 if (headerBlock.isReadable()) {
577                     ctx.write(buf.retain(), promiseAggregator.newPromise());
578                 } else {
579                     // The frame header is different for the last frame, so re-allocate and release the old buffer
580                     flags = flags.endOfHeaders(true);
581                     buf.release();
582                     buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH);
583                     writeFrameHeaderInternal(buf, payloadLength, CONTINUATION, flags, streamId);
584                     writePaddingLength(buf, padding);
585                     ctx.write(buf, promiseAggregator.newPromise());
586                 }
587 
588                 ctx.write(fragment, promiseAggregator.newPromise());
589 
590                 // Write out the padding, if any.
591                 if (paddingBytes(padding) > 0) {
592                     ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise());
593                 }
594             } while(headerBlock.isReadable());
595         }
596         return promiseAggregator;
597     }
598 
599     /**
600      * Returns the number of padding bytes that should be appended to the end of a frame.
601      */
602     private static int paddingBytes(int padding) {
603         // The padding parameter contains the 1 byte pad length field as well as the trailing padding bytes.
604         // Subtract 1, so to only get the number of padding bytes that need to be appended to the end of a frame.
605         return padding - 1;
606     }
607 
608     private static void writePaddingLength(ByteBuf buf, int padding) {
609         if (padding > 0) {
610             // It is assumed that the padding length has been bounds checked before this
611             // Minus 1, as the pad length field is included in the padding parameter and is 1 byte wide.
612             buf.writeByte(padding - 1);
613         }
614     }
615 
616     private static void verifyStreamId(int streamId, String argumentName) {
617         if (streamId <= 0) {
618             throw new IllegalArgumentException(argumentName + " must be > 0");
619         }
620     }
621 
622     private static void verifyStreamOrConnectionId(int streamId, String argumentName) {
623         if (streamId < 0) {
624             throw new IllegalArgumentException(argumentName + " must be >= 0");
625         }
626     }
627 
628     private static void verifyWeight(short weight) {
629         if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) {
630             throw new IllegalArgumentException("Invalid weight: " + weight);
631         }
632     }
633 
634     private static void verifyErrorCode(long errorCode) {
635         if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
636             throw new IllegalArgumentException("Invalid errorCode: " + errorCode);
637         }
638     }
639 
640     private static void verifyWindowSizeIncrement(int windowSizeIncrement) {
641         if (windowSizeIncrement < 0) {
642             throw new IllegalArgumentException("WindowSizeIncrement must be >= 0");
643         }
644     }
645 
646     private static void verifyPingPayload(ByteBuf data) {
647         if (data == null || data.readableBytes() != PING_FRAME_PAYLOAD_LENGTH) {
648             throw new IllegalArgumentException("Opaque data must be " + PING_FRAME_PAYLOAD_LENGTH + " bytes");
649         }
650     }
651 }