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