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