1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http2;
17
18 import java.io.Closeable;
19
20 import io.netty.buffer.ByteBuf;
21 import io.netty.channel.ChannelFuture;
22 import io.netty.channel.ChannelHandlerContext;
23 import io.netty.channel.ChannelPromise;
24 import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator;
25 import io.netty.handler.codec.http2.Http2FrameWriter.Configuration;
26 import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector;
27 import io.netty.util.LeakPresenceDetector;
28 import io.netty.util.internal.PlatformDependent;
29
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
73
74 public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSizePolicy, Configuration {
75 private static final String STREAM_ID = "Stream ID";
76 private static final String STREAM_DEPENDENCY = "Stream Dependency";
77
78
79
80
81
82 private static final ByteBuf ZERO_BUFFER = LeakPresenceDetector.staticInitializer(() ->
83 unreleasableBuffer(directBuffer(MAX_UNSIGNED_BYTE).writeZero(MAX_UNSIGNED_BYTE)).asReadOnly());
84
85 private final Http2HeadersEncoder headersEncoder;
86 private int maxFrameSize;
87
88 public DefaultHttp2FrameWriter() {
89 this(new DefaultHttp2HeadersEncoder());
90 }
91
92 public DefaultHttp2FrameWriter(SensitivityDetector headersSensitivityDetector) {
93 this(new DefaultHttp2HeadersEncoder(headersSensitivityDetector));
94 }
95
96 public DefaultHttp2FrameWriter(SensitivityDetector headersSensitivityDetector, boolean ignoreMaxHeaderListSize) {
97 this(new DefaultHttp2HeadersEncoder(headersSensitivityDetector, ignoreMaxHeaderListSize));
98 }
99
100
101
102
103 public DefaultHttp2FrameWriter(Http2HeadersEncoder headersEncoder) {
104 this.headersEncoder = headersEncoder;
105 maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
106 }
107
108 @Override
109 public Configuration configuration() {
110 return this;
111 }
112
113 @Override
114 public Http2HeadersEncoder.Configuration headersConfiguration() {
115 return headersEncoder.configuration();
116 }
117
118 @Override
119 public Http2FrameSizePolicy frameSizePolicy() {
120 return this;
121 }
122
123 @Override
124 public void maxFrameSize(int max) throws Http2Exception {
125 if (!isMaxFrameSizeValid(max)) {
126 throw connectionError(FRAME_SIZE_ERROR, "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max);
127 }
128 maxFrameSize = max;
129 }
130
131 @Override
132 public int maxFrameSize() {
133 return maxFrameSize;
134 }
135
136 @Override
137 public void close() {
138 if (headersEncoder instanceof Closeable) {
139 try {
140 ((Closeable) headersEncoder).close();
141 } catch (Exception e) {
142 throw new RuntimeException(e);
143 }
144 }
145 }
146
147 @Override
148 public ChannelFuture writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
149 int padding, boolean endStream, ChannelPromise promise) {
150 final SimpleChannelPromiseAggregator promiseAggregator =
151 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
152 ByteBuf frameHeader = null;
153 try {
154 verifyStreamId(streamId, STREAM_ID);
155 verifyPadding(padding);
156
157 int remainingData = data.readableBytes();
158 Http2Flags flags = new Http2Flags();
159 flags.endOfStream(false);
160 flags.paddingPresent(false);
161
162 if (remainingData > maxFrameSize) {
163 frameHeader = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
164 writeFrameHeaderInternal(frameHeader, maxFrameSize, DATA, flags, streamId);
165 do {
166
167 ctx.write(frameHeader.retainedSlice(), promiseAggregator.newPromise());
168
169
170 ctx.write(data.readRetainedSlice(maxFrameSize), promiseAggregator.newPromise());
171
172 remainingData -= maxFrameSize;
173
174 } while (remainingData > maxFrameSize);
175 }
176
177 if (padding == 0) {
178
179 if (frameHeader != null) {
180 frameHeader.release();
181 frameHeader = null;
182 }
183 ByteBuf frameHeader2 = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
184 flags.endOfStream(endStream);
185 writeFrameHeaderInternal(frameHeader2, remainingData, DATA, flags, streamId);
186 ctx.write(frameHeader2, promiseAggregator.newPromise());
187
188
189 ByteBuf lastFrame = data.readSlice(remainingData);
190 data = null;
191 ctx.write(lastFrame, promiseAggregator.newPromise());
192 } else {
193 if (remainingData != maxFrameSize) {
194 if (frameHeader != null) {
195 frameHeader.release();
196 frameHeader = null;
197 }
198 } else {
199 remainingData -= maxFrameSize;
200
201 ByteBuf lastFrame;
202 if (frameHeader == null) {
203 lastFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
204 writeFrameHeaderInternal(lastFrame, maxFrameSize, DATA, flags, streamId);
205 } else {
206 lastFrame = frameHeader.slice();
207 frameHeader = null;
208 }
209 ctx.write(lastFrame, promiseAggregator.newPromise());
210
211
212 lastFrame = data.readableBytes() != maxFrameSize ? data.readSlice(maxFrameSize) : data;
213 data = null;
214 ctx.write(lastFrame, promiseAggregator.newPromise());
215 }
216
217 do {
218 int frameDataBytes = min(remainingData, maxFrameSize);
219 int framePaddingBytes = min(padding, max(0, maxFrameSize - 1 - frameDataBytes));
220
221
222 padding -= framePaddingBytes;
223 remainingData -= frameDataBytes;
224
225
226 ByteBuf frameHeader2 = ctx.alloc().buffer(DATA_FRAME_HEADER_LENGTH);
227 flags.endOfStream(endStream && remainingData == 0 && padding == 0);
228 flags.paddingPresent(framePaddingBytes > 0);
229 writeFrameHeaderInternal(frameHeader2, framePaddingBytes + frameDataBytes, DATA, flags, streamId);
230 writePaddingLength(frameHeader2, framePaddingBytes);
231 ctx.write(frameHeader2, promiseAggregator.newPromise());
232
233
234 if (data != null) {
235 if (remainingData == 0) {
236 ByteBuf lastFrame = data.readSlice(frameDataBytes);
237 data = null;
238 ctx.write(lastFrame, promiseAggregator.newPromise());
239 } else {
240 ctx.write(data.readRetainedSlice(frameDataBytes), promiseAggregator.newPromise());
241 }
242 }
243
244 if (paddingBytes(framePaddingBytes) > 0) {
245 ctx.write(ZERO_BUFFER.slice(0, paddingBytes(framePaddingBytes)),
246 promiseAggregator.newPromise());
247 }
248 } while (remainingData != 0 || padding != 0);
249 }
250 } catch (Throwable cause) {
251 if (frameHeader != null) {
252 frameHeader.release();
253 }
254
255
256 try {
257 if (data != null) {
258 data.release();
259 }
260 } finally {
261 promiseAggregator.setFailure(cause);
262 promiseAggregator.doneAllocatingPromises();
263 }
264 return promiseAggregator;
265 }
266 return promiseAggregator.doneAllocatingPromises();
267 }
268
269 @Override
270 public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
271 Http2Headers headers, int padding, boolean endStream, ChannelPromise promise) {
272 return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
273 false, 0, (short) 0, false, promise);
274 }
275
276 @Override
277 public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
278 Http2Headers headers, int streamDependency, short weight, boolean exclusive,
279 int padding, boolean endStream, ChannelPromise promise) {
280 return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
281 true, streamDependency, weight, exclusive, promise);
282 }
283
284 @Override
285 public ChannelFuture writePriority(ChannelHandlerContext ctx, int streamId,
286 int streamDependency, short weight, boolean exclusive, ChannelPromise promise) {
287 try {
288 verifyStreamId(streamId, STREAM_ID);
289 verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY);
290 verifyWeight(weight);
291
292 ByteBuf buf = ctx.alloc().buffer(PRIORITY_FRAME_LENGTH);
293 writeFrameHeaderInternal(buf, PRIORITY_ENTRY_LENGTH, PRIORITY, new Http2Flags(), streamId);
294 buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency);
295
296 buf.writeByte(weight - 1);
297 return ctx.write(buf, promise);
298 } catch (Throwable t) {
299 return promise.setFailure(t);
300 }
301 }
302
303 @Override
304 public ChannelFuture writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
305 ChannelPromise promise) {
306 try {
307 verifyStreamId(streamId, STREAM_ID);
308 verifyErrorCode(errorCode);
309
310 ByteBuf buf = ctx.alloc().buffer(RST_STREAM_FRAME_LENGTH);
311 writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(), streamId);
312 buf.writeInt((int) errorCode);
313 return ctx.write(buf, promise);
314 } catch (Throwable t) {
315 return promise.setFailure(t);
316 }
317 }
318
319 @Override
320 public ChannelFuture writeSettings(ChannelHandlerContext ctx, Http2Settings settings,
321 ChannelPromise promise) {
322 try {
323 checkNotNull(settings, "settings");
324 int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
325 ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + payloadLength);
326 writeFrameHeaderInternal(buf, payloadLength, SETTINGS, new Http2Flags(), 0);
327 for (Http2Settings.PrimitiveEntry<Long> entry : settings.entries()) {
328 buf.writeChar(entry.key());
329 buf.writeInt(entry.value().intValue());
330 }
331 return ctx.write(buf, promise);
332 } catch (Throwable t) {
333 return promise.setFailure(t);
334 }
335 }
336
337 @Override
338 public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
339 try {
340 ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
341 writeFrameHeaderInternal(buf, 0, SETTINGS, new Http2Flags().ack(true), 0);
342 return ctx.write(buf, promise);
343 } catch (Throwable t) {
344 return promise.setFailure(t);
345 }
346 }
347
348 @Override
349 public ChannelFuture writePing(ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise) {
350 Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags();
351 ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PING_FRAME_PAYLOAD_LENGTH);
352
353
354 writeFrameHeaderInternal(buf, PING_FRAME_PAYLOAD_LENGTH, PING, flags, 0);
355 buf.writeLong(data);
356 return ctx.write(buf, promise);
357 }
358
359 @Override
360 public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId,
361 int promisedStreamId, Http2Headers headers, int padding, ChannelPromise promise) {
362 ByteBuf headerBlock = null;
363 ByteBuf fragment = 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
372 headerBlock = ctx.alloc().buffer();
373 headersEncoder.encodeHeaders(streamId, headers, headerBlock);
374
375
376 Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
377
378 int nonFragmentLength = INT_FIELD_LENGTH + padding;
379 int maxFragmentLength = maxFrameSize - nonFragmentLength;
380 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
390 buf.writeInt(promisedStreamId);
391 ctx.write(buf, promiseAggregator.newPromise());
392
393
394 ctx.write(fragment, promiseAggregator.newPromise());
395 fragment = null;
396
397
398 if (paddingBytes(padding) > 0) {
399 ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise());
400 }
401
402 if (!flags.endOfHeaders()) {
403 writeContinuationFrames(ctx, streamId, headerBlock, promiseAggregator);
404 }
405 } catch (Http2Exception e) {
406 promiseAggregator.setFailure(e);
407 } catch (Throwable t) {
408 promiseAggregator.setFailure(t);
409 promiseAggregator.doneAllocatingPromises();
410 PlatformDependent.throwException(t);
411 } finally {
412 if (fragment != null) {
413 fragment.release();
414 }
415 if (headerBlock != null) {
416 headerBlock.release();
417 }
418 }
419 return promiseAggregator.doneAllocatingPromises();
420 }
421
422 @Override
423 public ChannelFuture writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
424 ByteBuf debugData, ChannelPromise promise) {
425 SimpleChannelPromiseAggregator promiseAggregator =
426 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
427 try {
428 verifyStreamOrConnectionId(lastStreamId, "Last Stream ID");
429 verifyErrorCode(errorCode);
430
431 int payloadLength = 8 + debugData.readableBytes();
432 ByteBuf buf = ctx.alloc().buffer(GO_AWAY_FRAME_HEADER_LENGTH);
433
434
435 writeFrameHeaderInternal(buf, payloadLength, GO_AWAY, new Http2Flags(), 0);
436 buf.writeInt(lastStreamId);
437 buf.writeInt((int) errorCode);
438 ctx.write(buf, promiseAggregator.newPromise());
439 } catch (Throwable t) {
440 try {
441 debugData.release();
442 } finally {
443 promiseAggregator.setFailure(t);
444 promiseAggregator.doneAllocatingPromises();
445 }
446 return promiseAggregator;
447 }
448
449 try {
450 ctx.write(debugData, promiseAggregator.newPromise());
451 } catch (Throwable t) {
452 promiseAggregator.setFailure(t);
453 }
454 return promiseAggregator.doneAllocatingPromises();
455 }
456
457 @Override
458 public ChannelFuture writeWindowUpdate(ChannelHandlerContext ctx, int streamId,
459 int windowSizeIncrement, ChannelPromise promise) {
460 try {
461 verifyStreamOrConnectionId(streamId, STREAM_ID);
462 verifyWindowSizeIncrement(windowSizeIncrement);
463
464 ByteBuf buf = ctx.alloc().buffer(WINDOW_UPDATE_FRAME_LENGTH);
465 writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, WINDOW_UPDATE, new Http2Flags(), streamId);
466 buf.writeInt(windowSizeIncrement);
467 return ctx.write(buf, promise);
468 } catch (Throwable t) {
469 return promise.setFailure(t);
470 }
471 }
472
473 @Override
474 public ChannelFuture writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
475 Http2Flags flags, ByteBuf payload, ChannelPromise promise) {
476 SimpleChannelPromiseAggregator promiseAggregator =
477 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
478 try {
479 verifyStreamOrConnectionId(streamId, STREAM_ID);
480 ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
481
482
483 writeFrameHeaderInternal(buf, payload.readableBytes(), frameType, flags, streamId);
484 ctx.write(buf, promiseAggregator.newPromise());
485 } catch (Throwable t) {
486 try {
487 payload.release();
488 } finally {
489 promiseAggregator.setFailure(t);
490 promiseAggregator.doneAllocatingPromises();
491 }
492 return promiseAggregator;
493 }
494 try {
495 ctx.write(payload, promiseAggregator.newPromise());
496 } catch (Throwable t) {
497 promiseAggregator.setFailure(t);
498 }
499 return promiseAggregator.doneAllocatingPromises();
500 }
501
502 private ChannelFuture writeHeadersInternal(ChannelHandlerContext ctx,
503 int streamId, Http2Headers headers, int padding, boolean endStream,
504 boolean hasPriority, int streamDependency, short weight, boolean exclusive, ChannelPromise promise) {
505 ByteBuf headerBlock = null;
506 ByteBuf fragment = null;
507 SimpleChannelPromiseAggregator promiseAggregator =
508 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
509 try {
510 verifyStreamId(streamId, STREAM_ID);
511 if (hasPriority) {
512 verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY);
513 verifyPadding(padding);
514 verifyWeight(weight);
515 }
516
517
518 headerBlock = ctx.alloc().buffer();
519 headersEncoder.encodeHeaders(streamId, headers, headerBlock);
520
521 Http2Flags flags =
522 new Http2Flags().endOfStream(endStream).priorityPresent(hasPriority).paddingPresent(padding > 0);
523
524
525 int nonFragmentBytes = padding + flags.getNumPriorityBytes();
526 int maxFragmentLength = maxFrameSize - nonFragmentBytes;
527 fragment = headerBlock.readRetainedSlice(min(headerBlock.readableBytes(), maxFragmentLength));
528
529
530 flags.endOfHeaders(!headerBlock.isReadable());
531
532 int payloadLength = fragment.readableBytes() + nonFragmentBytes;
533 ByteBuf buf = ctx.alloc().buffer(HEADERS_FRAME_HEADER_LENGTH);
534 writeFrameHeaderInternal(buf, payloadLength, HEADERS, flags, streamId);
535 writePaddingLength(buf, padding);
536
537 if (hasPriority) {
538 buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency);
539
540
541 buf.writeByte(weight - 1);
542 }
543 ctx.write(buf, promiseAggregator.newPromise());
544
545
546 ctx.write(fragment, promiseAggregator.newPromise());
547 fragment = null;
548
549
550 if (paddingBytes(padding) > 0) {
551 ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise());
552 }
553
554 if (!flags.endOfHeaders()) {
555 writeContinuationFrames(ctx, streamId, headerBlock, promiseAggregator);
556 }
557 } catch (Http2Exception e) {
558 promiseAggregator.setFailure(e);
559 } catch (Throwable t) {
560 promiseAggregator.setFailure(t);
561 promiseAggregator.doneAllocatingPromises();
562 PlatformDependent.throwException(t);
563 } finally {
564 if (fragment != null) {
565 fragment.release();
566 }
567 if (headerBlock != null) {
568 headerBlock.release();
569 }
570 }
571 return promiseAggregator.doneAllocatingPromises();
572 }
573
574
575
576
577 private ChannelFuture writeContinuationFrames(ChannelHandlerContext ctx, int streamId,
578 ByteBuf headerBlock, SimpleChannelPromiseAggregator promiseAggregator) {
579 Http2Flags flags = new Http2Flags();
580
581 if (headerBlock.isReadable()) {
582 int fragmentReadableBytes;
583 ByteBuf buf = null;
584 try {
585 do {
586 fragmentReadableBytes = min(headerBlock.readableBytes(), maxFrameSize);
587 ByteBuf fragment = headerBlock.readRetainedSlice(fragmentReadableBytes);
588 boolean fragmentWritten = false;
589 try {
590 if (headerBlock.isReadable()) {
591 if (buf == null) {
592 buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH);
593 writeFrameHeaderInternal(buf, fragmentReadableBytes, CONTINUATION, flags, streamId);
594 }
595 ctx.write(buf.retainedSlice(), promiseAggregator.newPromise());
596 } else {
597
598
599 if (buf != null) {
600 buf.release();
601 buf = null;
602 }
603 flags = flags.endOfHeaders(true);
604 buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH);
605 writeFrameHeaderInternal(buf, fragmentReadableBytes, CONTINUATION, flags, streamId);
606 ctx.write(buf, promiseAggregator.newPromise());
607 buf = null;
608 }
609 ctx.write(fragment, promiseAggregator.newPromise());
610 fragmentWritten = true;
611 } finally {
612 if (!fragmentWritten) {
613 fragment.release();
614 }
615 }
616 } while (headerBlock.isReadable());
617 } finally {
618 if (buf != null) {
619 buf.release();
620 }
621 }
622 }
623 return promiseAggregator;
624 }
625
626
627
628
629 private static int paddingBytes(int padding) {
630
631
632 return padding - 1;
633 }
634
635 private static void writePaddingLength(ByteBuf buf, int padding) {
636 if (padding > 0) {
637
638
639 buf.writeByte(padding - 1);
640 }
641 }
642
643 private static void verifyStreamId(int streamId, String argumentName) {
644 checkPositive(streamId, argumentName);
645 }
646
647 private static void verifyStreamOrConnectionId(int streamId, String argumentName) {
648 checkPositiveOrZero(streamId, argumentName);
649 }
650
651 private static void verifyWeight(short weight) {
652 if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) {
653 throw new IllegalArgumentException("Invalid weight: " + weight);
654 }
655 }
656
657 private static void verifyErrorCode(long errorCode) {
658 if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
659 throw new IllegalArgumentException("Invalid errorCode: " + errorCode);
660 }
661 }
662
663 private static void verifyWindowSizeIncrement(int windowSizeIncrement) {
664 checkPositiveOrZero(windowSizeIncrement, "windowSizeIncrement");
665 }
666 }