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, ByteBuf data, ChannelPromise promise) {
336         final SimpleChannelPromiseAggregator promiseAggregator =
337                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
338         try {
339             verifyPingPayload(data);
340             Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags();
341             int payloadLength = data.readableBytes();
342             ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
343             // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership
344             // in the catch block.
345             writeFrameHeaderInternal(buf, payloadLength, PING, flags, 0);
346             ctx.write(buf, promiseAggregator.newPromise());
347         } catch (Throwable t) {
348             try {
349                 data.release();
350             } finally {
351                 promiseAggregator.setFailure(t);
352                 promiseAggregator.doneAllocatingPromises();
353             }
354             return promiseAggregator;
355         }
356 
357         try {
358             // Write the debug data.
359             ctx.write(data, promiseAggregator.newPromise());
360         } catch (Throwable t) {
361             promiseAggregator.setFailure(t);
362         }
363 
364         return promiseAggregator.doneAllocatingPromises();
365     }
366 
367     @Override
368     public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId,
369             int promisedStreamId, Http2Headers headers, int padding, ChannelPromise promise) {
370         ByteBuf headerBlock = null;
371         SimpleChannelPromiseAggregator promiseAggregator =
372                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
373         try {
374             verifyStreamId(streamId, STREAM_ID);
375             verifyStreamId(promisedStreamId, "Promised Stream ID");
376             verifyPadding(padding);
377 
378             // Encode the entire header block into an intermediate buffer.
379             headerBlock = ctx.alloc().buffer();
380             headersEncoder.encodeHeaders(streamId, headers, headerBlock);
381 
382             // Read the first fragment (possibly everything).
383             Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
384             // INT_FIELD_LENGTH is for the length of the promisedStreamId
385             int nonFragmentLength = INT_FIELD_LENGTH + padding;
386             int maxFragmentLength = maxFrameSize - nonFragmentLength;
387             ByteBuf fragment = headerBlock.readRetainedSlice(min(headerBlock.readableBytes(), maxFragmentLength));
388 
389             flags.endOfHeaders(!headerBlock.isReadable());
390 
391             int payloadLength = fragment.readableBytes() + nonFragmentLength;
392             ByteBuf buf = ctx.alloc().buffer(PUSH_PROMISE_FRAME_HEADER_LENGTH);
393             writeFrameHeaderInternal(buf, payloadLength, PUSH_PROMISE, flags, streamId);
394             writePaddingLength(buf, padding);
395 
396             // Write out the promised stream ID.
397             buf.writeInt(promisedStreamId);
398             ctx.write(buf, promiseAggregator.newPromise());
399 
400             // Write the first fragment.
401             ctx.write(fragment, promiseAggregator.newPromise());
402 
403             // Write out the padding, if any.
404             if (paddingBytes(padding) > 0) {
405                 ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise());
406             }
407 
408             if (!flags.endOfHeaders()) {
409                 writeContinuationFrames(ctx, streamId, headerBlock, padding, promiseAggregator);
410             }
411         } catch (Http2Exception e) {
412             promiseAggregator.setFailure(e);
413         } catch (Throwable t) {
414             promiseAggregator.setFailure(t);
415             promiseAggregator.doneAllocatingPromises();
416             PlatformDependent.throwException(t);
417         } finally {
418             if (headerBlock != null) {
419                 headerBlock.release();
420             }
421         }
422         return promiseAggregator.doneAllocatingPromises();
423     }
424 
425     @Override
426     public ChannelFuture writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
427             ByteBuf debugData, ChannelPromise promise) {
428         SimpleChannelPromiseAggregator promiseAggregator =
429                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
430         try {
431             verifyStreamOrConnectionId(lastStreamId, "Last Stream ID");
432             verifyErrorCode(errorCode);
433 
434             int payloadLength = 8 + debugData.readableBytes();
435             ByteBuf buf = ctx.alloc().buffer(GO_AWAY_FRAME_HEADER_LENGTH);
436             // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership
437             // in the catch block.
438             writeFrameHeaderInternal(buf, payloadLength, GO_AWAY, new Http2Flags(), 0);
439             buf.writeInt(lastStreamId);
440             buf.writeInt((int) errorCode);
441             ctx.write(buf, promiseAggregator.newPromise());
442         } catch (Throwable t) {
443             try {
444                 debugData.release();
445             } finally {
446                 promiseAggregator.setFailure(t);
447                 promiseAggregator.doneAllocatingPromises();
448             }
449             return promiseAggregator;
450         }
451 
452         try {
453             ctx.write(debugData, promiseAggregator.newPromise());
454         } catch (Throwable t) {
455             promiseAggregator.setFailure(t);
456         }
457         return promiseAggregator.doneAllocatingPromises();
458     }
459 
460     @Override
461     public ChannelFuture writeWindowUpdate(ChannelHandlerContext ctx, int streamId,
462             int windowSizeIncrement, ChannelPromise promise) {
463         try {
464             verifyStreamOrConnectionId(streamId, STREAM_ID);
465             verifyWindowSizeIncrement(windowSizeIncrement);
466 
467             ByteBuf buf = ctx.alloc().buffer(WINDOW_UPDATE_FRAME_LENGTH);
468             writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, WINDOW_UPDATE, new Http2Flags(), streamId);
469             buf.writeInt(windowSizeIncrement);
470             return ctx.write(buf, promise);
471         } catch (Throwable t) {
472             return promise.setFailure(t);
473         }
474     }
475 
476     @Override
477     public ChannelFuture writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
478             Http2Flags flags, ByteBuf payload, ChannelPromise promise) {
479         SimpleChannelPromiseAggregator promiseAggregator =
480                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
481         try {
482             verifyStreamOrConnectionId(streamId, STREAM_ID);
483             ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
484             // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership
485             // in the catch block.
486             writeFrameHeaderInternal(buf, payload.readableBytes(), frameType, flags, streamId);
487             ctx.write(buf, promiseAggregator.newPromise());
488         } catch (Throwable t) {
489             try {
490                 payload.release();
491             } finally {
492                 promiseAggregator.setFailure(t);
493                 promiseAggregator.doneAllocatingPromises();
494             }
495             return promiseAggregator;
496         }
497         try {
498             ctx.write(payload, promiseAggregator.newPromise());
499         } catch (Throwable t) {
500             promiseAggregator.setFailure(t);
501         }
502         return promiseAggregator.doneAllocatingPromises();
503     }
504 
505     private ChannelFuture writeHeadersInternal(ChannelHandlerContext ctx,
506             int streamId, Http2Headers headers, int padding, boolean endStream,
507             boolean hasPriority, int streamDependency, short weight, boolean exclusive, ChannelPromise promise) {
508         ByteBuf headerBlock = null;
509         SimpleChannelPromiseAggregator promiseAggregator =
510                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
511         try {
512             verifyStreamId(streamId, STREAM_ID);
513             if (hasPriority) {
514                 verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY);
515                 verifyPadding(padding);
516                 verifyWeight(weight);
517             }
518 
519             // Encode the entire header block.
520             headerBlock = ctx.alloc().buffer();
521             headersEncoder.encodeHeaders(streamId, headers, headerBlock);
522 
523             Http2Flags flags =
524                     new Http2Flags().endOfStream(endStream).priorityPresent(hasPriority).paddingPresent(padding > 0);
525 
526             // Read the first fragment (possibly everything).
527             int nonFragmentBytes = padding + flags.getNumPriorityBytes();
528             int maxFragmentLength = maxFrameSize - nonFragmentBytes;
529             ByteBuf fragment = headerBlock.readRetainedSlice(min(headerBlock.readableBytes(), maxFragmentLength));
530 
531             // Set the end of headers flag for the first frame.
532             flags.endOfHeaders(!headerBlock.isReadable());
533 
534             int payloadLength = fragment.readableBytes() + nonFragmentBytes;
535             ByteBuf buf = ctx.alloc().buffer(HEADERS_FRAME_HEADER_LENGTH);
536             writeFrameHeaderInternal(buf, payloadLength, HEADERS, flags, streamId);
537             writePaddingLength(buf, padding);
538 
539             if (hasPriority) {
540                 buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency);
541 
542                 // Adjust the weight so that it fits into a single byte on the wire.
543                 buf.writeByte(weight - 1);
544             }
545             ctx.write(buf, promiseAggregator.newPromise());
546 
547             // Write the first fragment.
548             ctx.write(fragment, promiseAggregator.newPromise());
549 
550             // Write out the padding, if any.
551             if (paddingBytes(padding) > 0) {
552                 ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise());
553             }
554 
555             if (!flags.endOfHeaders()) {
556                 writeContinuationFrames(ctx, streamId, headerBlock, padding, promiseAggregator);
557             }
558         } catch (Http2Exception e) {
559             promiseAggregator.setFailure(e);
560         } catch (Throwable t) {
561             promiseAggregator.setFailure(t);
562             promiseAggregator.doneAllocatingPromises();
563             PlatformDependent.throwException(t);
564         } finally {
565             if (headerBlock != null) {
566                 headerBlock.release();
567             }
568         }
569         return promiseAggregator.doneAllocatingPromises();
570     }
571 
572     /**
573      * Writes as many continuation frames as needed until {@code padding} and {@code headerBlock} are consumed.
574      */
575     private ChannelFuture writeContinuationFrames(ChannelHandlerContext ctx, int streamId,
576             ByteBuf headerBlock, int padding, SimpleChannelPromiseAggregator promiseAggregator) {
577         Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
578         int maxFragmentLength = maxFrameSize - padding;
579         // TODO: same padding is applied to all frames, is this desired?
580         if (maxFragmentLength <= 0) {
581             return promiseAggregator.setFailure(new IllegalArgumentException(
582                     "Padding [" + padding + "] is too large for max frame size [" + maxFrameSize + "]"));
583         }
584 
585         if (headerBlock.isReadable()) {
586             // The frame header (and padding) only changes on the last frame, so allocate it once and re-use
587             int fragmentReadableBytes = min(headerBlock.readableBytes(), maxFragmentLength);
588             int payloadLength = fragmentReadableBytes + padding;
589             ByteBuf buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH);
590             writeFrameHeaderInternal(buf, payloadLength, CONTINUATION, flags, streamId);
591             writePaddingLength(buf, padding);
592 
593             do {
594                 fragmentReadableBytes = min(headerBlock.readableBytes(), maxFragmentLength);
595                 ByteBuf fragment = headerBlock.readRetainedSlice(fragmentReadableBytes);
596 
597                 payloadLength = fragmentReadableBytes + padding;
598                 if (headerBlock.isReadable()) {
599                     ctx.write(buf.retain(), promiseAggregator.newPromise());
600                 } else {
601                     // The frame header is different for the last frame, so re-allocate and release the old buffer
602                     flags = flags.endOfHeaders(true);
603                     buf.release();
604                     buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH);
605                     writeFrameHeaderInternal(buf, payloadLength, CONTINUATION, flags, streamId);
606                     writePaddingLength(buf, padding);
607                     ctx.write(buf, promiseAggregator.newPromise());
608                 }
609 
610                 ctx.write(fragment, promiseAggregator.newPromise());
611 
612                 // Write out the padding, if any.
613                 if (paddingBytes(padding) > 0) {
614                     ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise());
615                 }
616             } while(headerBlock.isReadable());
617         }
618         return promiseAggregator;
619     }
620 
621     /**
622      * Returns the number of padding bytes that should be appended to the end of a frame.
623      */
624     private static int paddingBytes(int padding) {
625         // The padding parameter contains the 1 byte pad length field as well as the trailing padding bytes.
626         // Subtract 1, so to only get the number of padding bytes that need to be appended to the end of a frame.
627         return padding - 1;
628     }
629 
630     private static void writePaddingLength(ByteBuf buf, int padding) {
631         if (padding > 0) {
632             // It is assumed that the padding length has been bounds checked before this
633             // Minus 1, as the pad length field is included in the padding parameter and is 1 byte wide.
634             buf.writeByte(padding - 1);
635         }
636     }
637 
638     private static void verifyStreamId(int streamId, String argumentName) {
639         if (streamId <= 0) {
640             throw new IllegalArgumentException(argumentName + " must be > 0");
641         }
642     }
643 
644     private static void verifyStreamOrConnectionId(int streamId, String argumentName) {
645         if (streamId < 0) {
646             throw new IllegalArgumentException(argumentName + " must be >= 0");
647         }
648     }
649 
650     private static void verifyWeight(short weight) {
651         if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) {
652             throw new IllegalArgumentException("Invalid weight: " + weight);
653         }
654     }
655 
656     private static void verifyErrorCode(long errorCode) {
657         if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
658             throw new IllegalArgumentException("Invalid errorCode: " + errorCode);
659         }
660     }
661 
662     private static void verifyWindowSizeIncrement(int windowSizeIncrement) {
663         if (windowSizeIncrement < 0) {
664             throw new IllegalArgumentException("WindowSizeIncrement must be >= 0");
665         }
666     }
667 
668     private static void verifyPingPayload(ByteBuf data) {
669         if (data == null || data.readableBytes() != PING_FRAME_PAYLOAD_LENGTH) {
670             throw new IllegalArgumentException("Opaque data must be " + PING_FRAME_PAYLOAD_LENGTH + " bytes");
671         }
672     }
673 }