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