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