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