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