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