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    * http://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 static io.netty.handler.codec.http2.Http2CodecUtil.CONTINUATION_FRAME_HEADER_LENGTH;
19  import static io.netty.handler.codec.http2.Http2CodecUtil.DATA_FRAME_HEADER_LENGTH;
20  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE;
21  import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
22  import static io.netty.handler.codec.http2.Http2CodecUtil.GO_AWAY_FRAME_HEADER_LENGTH;
23  import static io.netty.handler.codec.http2.Http2CodecUtil.HEADERS_FRAME_HEADER_LENGTH;
24  import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
25  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE;
26  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
27  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT;
28  import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT;
29  import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
30  import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_FRAME_LENGTH;
31  import static io.netty.handler.codec.http2.Http2CodecUtil.PUSH_PROMISE_FRAME_HEADER_LENGTH;
32  import static io.netty.handler.codec.http2.Http2CodecUtil.RST_STREAM_FRAME_LENGTH;
33  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
34  import static io.netty.handler.codec.http2.Http2CodecUtil.WINDOW_UPDATE_FRAME_LENGTH;
35  import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid;
36  import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeaderInternal;
37  import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedInt;
38  import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedShort;
39  import static io.netty.handler.codec.http2.Http2Error.FRAME_SIZE_ERROR;
40  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
41  import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
42  import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
43  import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
44  import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS;
45  import static io.netty.handler.codec.http2.Http2FrameTypes.PING;
46  import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY;
47  import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
48  import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM;
49  import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
50  import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
51  import static io.netty.util.internal.ObjectUtil.checkNotNull;
52  import io.netty.buffer.ByteBuf;
53  import io.netty.buffer.Unpooled;
54  import io.netty.channel.ChannelFuture;
55  import io.netty.channel.ChannelHandlerContext;
56  import io.netty.channel.ChannelPromise;
57  import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator;
58  import io.netty.handler.codec.http2.Http2FrameWriter.Configuration;
59  import io.netty.util.collection.IntObjectMap;
60  
61  /**
62   * A {@link Http2FrameWriter} that supports all frame types defined by the HTTP/2 specification.
63   */
64  public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSizePolicy, Configuration {
65      private static final String STREAM_ID = "Stream ID";
66      private static final String STREAM_DEPENDENCY = "Stream Dependency";
67      /**
68       * This buffer is allocated to the maximum padding size needed, and filled with padding.
69       * When padding is needed it can be taken as a slice of this buffer. Users should call {@link ByteBuf#retain()}
70       * before using their slice.
71       */
72      private static final ByteBuf ZERO_BUFFER = Unpooled.buffer(MAX_UNSIGNED_BYTE).writeZero(MAX_UNSIGNED_BYTE);
73  
74      private final Http2HeadersEncoder headersEncoder;
75      private int maxFrameSize;
76  
77      public DefaultHttp2FrameWriter() {
78          this(new DefaultHttp2HeadersEncoder());
79      }
80  
81      public DefaultHttp2FrameWriter(Http2HeadersEncoder headersEncoder) {
82          this.headersEncoder = headersEncoder;
83          maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
84      }
85  
86      @Override
87      public Configuration configuration() {
88          return this;
89      }
90  
91      @Override
92      public Http2HeaderTable headerTable() {
93          return headersEncoder.configuration().headerTable();
94      }
95  
96      @Override
97      public Http2FrameSizePolicy frameSizePolicy() {
98          return this;
99      }
100 
101     @Override
102     public void maxFrameSize(int max) throws Http2Exception {
103         if (!isMaxFrameSizeValid(max)) {
104             throw connectionError(FRAME_SIZE_ERROR, "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max);
105         }
106         maxFrameSize = max;
107     }
108 
109     @Override
110     public int maxFrameSize() {
111         return maxFrameSize;
112     }
113 
114     @Override
115     public void close() { }
116 
117     @Override
118     public ChannelFuture writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
119             int padding, boolean endStream, ChannelPromise promise) {
120         boolean releaseData = true;
121         ByteBuf buf = null;
122         SimpleChannelPromiseAggregator promiseAggregator =
123                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
124         try {
125             verifyStreamId(streamId, STREAM_ID);
126             verifyPadding(padding);
127 
128             Http2Flags flags = new Http2Flags().paddingPresent(padding > 0).endOfStream(endStream);
129 
130             int payloadLength = data.readableBytes() + padding + flags.getPaddingPresenceFieldLength();
131             verifyPayloadLength(payloadLength);
132 
133             buf = ctx.alloc().buffer(DATA_FRAME_HEADER_LENGTH);
134             writeFrameHeaderInternal(buf, payloadLength, DATA, flags, streamId);
135             writePaddingLength(buf, padding);
136             ctx.write(buf, promiseAggregator.newPromise());
137 
138             // Write the data.
139             releaseData = false;
140             ctx.write(data, promiseAggregator.newPromise());
141 
142             if (padding > 0) { // Write the required padding.
143                 ctx.write(ZERO_BUFFER.slice(0, padding).retain(), promiseAggregator.newPromise());
144             }
145             return promiseAggregator.doneAllocatingPromises();
146         } catch (Throwable t) {
147             if (releaseData) {
148                 data.release();
149             }
150             return promiseAggregator.setFailure(t);
151         }
152     }
153 
154     @Override
155     public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
156             Http2Headers headers, int padding, boolean endStream, ChannelPromise promise) {
157         return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
158                 false, 0, (short) 0, false, promise);
159     }
160 
161     @Override
162     public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
163             Http2Headers headers, int streamDependency, short weight, boolean exclusive,
164             int padding, boolean endStream, ChannelPromise promise) {
165         return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
166                 true, streamDependency, weight, exclusive, promise);
167     }
168 
169     @Override
170     public ChannelFuture writePriority(ChannelHandlerContext ctx, int streamId,
171             int streamDependency, short weight, boolean exclusive, ChannelPromise promise) {
172         try {
173             verifyStreamId(streamId, STREAM_ID);
174             verifyStreamId(streamDependency, STREAM_DEPENDENCY);
175             verifyWeight(weight);
176 
177             ByteBuf buf = ctx.alloc().buffer(PRIORITY_FRAME_LENGTH);
178             writeFrameHeaderInternal(buf, PRIORITY_ENTRY_LENGTH, PRIORITY, new Http2Flags(), streamId);
179             long word1 = exclusive ? 0x80000000L | streamDependency : streamDependency;
180             writeUnsignedInt(word1, buf);
181             // Adjust the weight so that it fits into a single byte on the wire.
182             buf.writeByte(weight - 1);
183             return ctx.write(buf, promise);
184         } catch (Throwable t) {
185             return promise.setFailure(t);
186         }
187     }
188 
189     @Override
190     public ChannelFuture writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
191             ChannelPromise promise) {
192         try {
193             verifyStreamId(streamId, STREAM_ID);
194             verifyErrorCode(errorCode);
195 
196             ByteBuf buf = ctx.alloc().buffer(RST_STREAM_FRAME_LENGTH);
197             writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(), streamId);
198             writeUnsignedInt(errorCode, buf);
199             return ctx.write(buf, promise);
200         } catch (Throwable t) {
201             return promise.setFailure(t);
202         }
203     }
204 
205     @Override
206     public ChannelFuture writeSettings(ChannelHandlerContext ctx, Http2Settings settings,
207             ChannelPromise promise) {
208         try {
209             checkNotNull(settings, "settings");
210             int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
211             ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + settings.size() * SETTING_ENTRY_LENGTH);
212             writeFrameHeaderInternal(buf, payloadLength, SETTINGS, new Http2Flags(), 0);
213             for (IntObjectMap.Entry<Long> entry : settings.entries()) {
214                 writeUnsignedShort(entry.key(), buf);
215                 writeUnsignedInt(entry.value(), buf);
216             }
217             return ctx.write(buf, promise);
218         } catch (Throwable t) {
219             return promise.setFailure(t);
220         }
221     }
222 
223     @Override
224     public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
225         try {
226             ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
227             writeFrameHeaderInternal(buf, 0, SETTINGS, new Http2Flags().ack(true), 0);
228             return ctx.write(buf, promise);
229         } catch (Throwable t) {
230             return promise.setFailure(t);
231         }
232     }
233 
234     @Override
235     public ChannelFuture writePing(ChannelHandlerContext ctx, boolean ack, ByteBuf data,
236             ChannelPromise promise) {
237         boolean releaseData = true;
238         SimpleChannelPromiseAggregator promiseAggregator =
239                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
240         try {
241             Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags();
242             ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
243             writeFrameHeaderInternal(buf, data.readableBytes(), PING, flags, 0);
244             ctx.write(buf, promiseAggregator.newPromise());
245 
246             // Write the debug data.
247             releaseData = false;
248             ctx.write(data, promiseAggregator.newPromise());
249 
250             return promiseAggregator.doneAllocatingPromises();
251         } catch (Throwable t) {
252             if (releaseData) {
253                 data.release();
254             }
255             return promiseAggregator.setFailure(t);
256         }
257     }
258 
259     @Override
260     public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId,
261             int promisedStreamId, Http2Headers headers, int padding, ChannelPromise promise) {
262         ByteBuf headerBlock = null;
263         SimpleChannelPromiseAggregator promiseAggregator =
264                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
265         try {
266             verifyStreamId(streamId, STREAM_ID);
267             verifyStreamId(promisedStreamId, "Promised Stream ID");
268             verifyPadding(padding);
269 
270             // Encode the entire header block into an intermediate buffer.
271             headerBlock = ctx.alloc().buffer();
272             headersEncoder.encodeHeaders(headers, headerBlock);
273 
274             // Read the first fragment (possibly everything).
275             Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
276             // INT_FIELD_LENGTH is for the length of the promisedStreamId
277             int nonFragmentLength = INT_FIELD_LENGTH + padding + flags.getPaddingPresenceFieldLength();
278             int maxFragmentLength = maxFrameSize - nonFragmentLength;
279             ByteBuf fragment =
280                     headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)).retain();
281 
282             flags.endOfHeaders(!headerBlock.isReadable());
283 
284             int payloadLength = fragment.readableBytes() + nonFragmentLength;
285             ByteBuf buf = ctx.alloc().buffer(PUSH_PROMISE_FRAME_HEADER_LENGTH);
286             writeFrameHeaderInternal(buf, payloadLength, PUSH_PROMISE, flags, streamId);
287             writePaddingLength(buf, padding);
288 
289             // Write out the promised stream ID.
290             buf.writeInt(promisedStreamId);
291             ctx.write(buf, promiseAggregator.newPromise());
292 
293             // Write the first fragment.
294             ctx.write(fragment, promiseAggregator.newPromise());
295 
296             if (padding > 0) { // Write out the padding, if any.
297                 ctx.write(ZERO_BUFFER.slice(0, padding).retain(), promiseAggregator.newPromise());
298             }
299 
300             if (!flags.endOfHeaders()) {
301                 writeContinuationFrames(ctx, streamId, headerBlock, padding, promiseAggregator);
302             }
303 
304             return promiseAggregator.doneAllocatingPromises();
305         } catch (Throwable t) {
306             return promiseAggregator.setFailure(t);
307         } finally {
308             if (headerBlock != null) {
309                 headerBlock.release();
310             }
311         }
312     }
313 
314     @Override
315     public ChannelFuture writeGoAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
316             ByteBuf debugData, ChannelPromise promise) {
317         boolean releaseData = true;
318         SimpleChannelPromiseAggregator promiseAggregator =
319                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
320         try {
321             verifyStreamOrConnectionId(lastStreamId, "Last Stream ID");
322             verifyErrorCode(errorCode);
323 
324             int payloadLength = 8 + debugData.readableBytes();
325             ByteBuf buf = ctx.alloc().buffer(GO_AWAY_FRAME_HEADER_LENGTH);
326             writeFrameHeaderInternal(buf, payloadLength, GO_AWAY, new Http2Flags(), 0);
327             buf.writeInt(lastStreamId);
328             writeUnsignedInt(errorCode, buf);
329             ctx.write(buf, promiseAggregator.newPromise());
330 
331             releaseData = false;
332             ctx.write(debugData, promiseAggregator.newPromise());
333             return promiseAggregator.doneAllocatingPromises();
334         } catch (Throwable t) {
335             if (releaseData) {
336                 debugData.release();
337             }
338             return promiseAggregator.setFailure(t);
339         }
340     }
341 
342     @Override
343     public ChannelFuture writeWindowUpdate(ChannelHandlerContext ctx, int streamId,
344             int windowSizeIncrement, ChannelPromise promise) {
345         try {
346             verifyStreamOrConnectionId(streamId, STREAM_ID);
347             verifyWindowSizeIncrement(windowSizeIncrement);
348 
349             ByteBuf buf = ctx.alloc().buffer(WINDOW_UPDATE_FRAME_LENGTH);
350             writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, WINDOW_UPDATE, new Http2Flags(), streamId);
351             buf.writeInt(windowSizeIncrement);
352             return ctx.write(buf, promise);
353         } catch (Throwable t) {
354             return promise.setFailure(t);
355         }
356     }
357 
358     @Override
359     public ChannelFuture writeFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
360             Http2Flags flags, ByteBuf payload, ChannelPromise promise) {
361         boolean releaseData = true;
362         SimpleChannelPromiseAggregator promiseAggregator =
363                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
364         try {
365             verifyStreamOrConnectionId(streamId, STREAM_ID);
366             ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH);
367             writeFrameHeaderInternal(buf, payload.readableBytes(), frameType, flags, streamId);
368             ctx.write(buf, promiseAggregator.newPromise());
369 
370             releaseData = false;
371             ctx.write(payload, promiseAggregator.newPromise());
372             return promiseAggregator.doneAllocatingPromises();
373         } catch (Throwable t) {
374             if (releaseData) {
375                 payload.release();
376             }
377             return promiseAggregator.setFailure(t);
378         }
379     }
380 
381     private ChannelFuture writeHeadersInternal(ChannelHandlerContext ctx,
382             int streamId, Http2Headers headers, int padding, boolean endStream,
383             boolean hasPriority, int streamDependency, short weight, boolean exclusive, ChannelPromise promise) {
384         ByteBuf headerBlock = null;
385         SimpleChannelPromiseAggregator promiseAggregator =
386                 new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
387         try {
388             verifyStreamId(streamId, STREAM_ID);
389             if (hasPriority) {
390                 verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY);
391                 verifyPadding(padding);
392                 verifyWeight(weight);
393             }
394 
395             // Encode the entire header block.
396             headerBlock = ctx.alloc().buffer();
397             headersEncoder.encodeHeaders(headers, headerBlock);
398 
399             Http2Flags flags =
400                     new Http2Flags().endOfStream(endStream).priorityPresent(hasPriority).paddingPresent(padding > 0);
401 
402             // Read the first fragment (possibly everything).
403             int nonFragmentBytes = padding + flags.getNumPriorityBytes() + flags.getPaddingPresenceFieldLength();
404             int maxFragmentLength = maxFrameSize - nonFragmentBytes;
405             ByteBuf fragment =
406                     headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength)).retain();
407 
408             // Set the end of headers flag for the first frame.
409             flags.endOfHeaders(!headerBlock.isReadable());
410 
411             int payloadLength = fragment.readableBytes() + nonFragmentBytes;
412             ByteBuf buf = ctx.alloc().buffer(HEADERS_FRAME_HEADER_LENGTH);
413             writeFrameHeaderInternal(buf, payloadLength, HEADERS, flags, streamId);
414             writePaddingLength(buf, padding);
415 
416             if (hasPriority) {
417                 long word1 = exclusive ? 0x80000000L | streamDependency : streamDependency;
418                 writeUnsignedInt(word1, buf);
419 
420                 // Adjust the weight so that it fits into a single byte on the wire.
421                 buf.writeByte(weight - 1);
422             }
423             ctx.write(buf, promiseAggregator.newPromise());
424 
425             // Write the first fragment.
426             ctx.write(fragment, promiseAggregator.newPromise());
427 
428             if (padding > 0) { // Write out the padding, if any.
429                 ctx.write(ZERO_BUFFER.slice(0, padding).retain(), promiseAggregator.newPromise());
430             }
431 
432             if (!flags.endOfHeaders()) {
433                 writeContinuationFrames(ctx, streamId, headerBlock, padding, promiseAggregator);
434             }
435 
436             return promiseAggregator.doneAllocatingPromises();
437         } catch (Throwable t) {
438             return promiseAggregator.setFailure(t);
439         } finally {
440             if (headerBlock != null) {
441                 headerBlock.release();
442             }
443         }
444     }
445 
446     /**
447      * Writes as many continuation frames as needed until {@code padding} and {@code headerBlock} are consumed.
448      */
449     private ChannelFuture writeContinuationFrames(ChannelHandlerContext ctx, int streamId,
450             ByteBuf headerBlock, int padding, SimpleChannelPromiseAggregator promiseAggregator) {
451         Http2Flags flags = new Http2Flags().paddingPresent(padding > 0);
452         int nonFragmentLength = padding + flags.getPaddingPresenceFieldLength();
453         int maxFragmentLength = maxFrameSize - nonFragmentLength;
454         // TODO: same padding is applied to all frames, is this desired?
455         if (maxFragmentLength <= 0) {
456             return promiseAggregator.setFailure(new IllegalArgumentException(
457                     "Padding [" + padding + "] is too large for max frame size [" + maxFrameSize + "]"));
458         }
459 
460         if (headerBlock.isReadable()) {
461             // The frame header (and padding) only changes on the last frame, so allocate it once and re-use
462             final ByteBuf paddingBuf = padding > 0 ? ZERO_BUFFER.slice(0, padding) : null;
463             int fragmentReadableBytes = Math.min(headerBlock.readableBytes(), maxFragmentLength);
464             int payloadLength = fragmentReadableBytes + nonFragmentLength;
465             ByteBuf buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH);
466             writeFrameHeaderInternal(buf, payloadLength, CONTINUATION, flags, streamId);
467             writePaddingLength(buf, padding);
468 
469             do {
470                 fragmentReadableBytes = Math.min(headerBlock.readableBytes(), maxFragmentLength);
471                 ByteBuf fragment = headerBlock.readSlice(fragmentReadableBytes).retain();
472 
473                 payloadLength = fragmentReadableBytes + nonFragmentLength;
474                 if (headerBlock.isReadable()) {
475                     ctx.write(buf.retain(), promiseAggregator.newPromise());
476                 } else {
477                     // The frame header is different for the last frame, so re-allocate and release the old buffer
478                     flags = flags.endOfHeaders(true);
479                     buf.release();
480                     buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH);
481                     writeFrameHeaderInternal(buf, payloadLength, CONTINUATION, flags, streamId);
482                     writePaddingLength(buf, padding);
483                     ctx.write(buf, promiseAggregator.newPromise());
484                 }
485 
486                 ctx.write(fragment, promiseAggregator.newPromise());
487 
488                 // Write out the padding, if any.
489                 if (paddingBuf != null) {
490                     ctx.write(paddingBuf.retain(), promiseAggregator.newPromise());
491                 }
492             } while(headerBlock.isReadable());
493         }
494         return promiseAggregator;
495     }
496 
497     private static void writePaddingLength(ByteBuf buf, int paddingLength) {
498         if (paddingLength > 0) {
499             // It is assumed that the padding length has been bounds checked before this
500             buf.writeByte(paddingLength);
501         }
502     }
503 
504     private static void verifyStreamId(int streamId, String argumentName) {
505         if (streamId <= 0) {
506             throw new IllegalArgumentException(argumentName + " must be > 0");
507         }
508     }
509 
510     private static void verifyStreamOrConnectionId(int streamId, String argumentName) {
511         if (streamId < 0) {
512             throw new IllegalArgumentException(argumentName + " must be >= 0");
513         }
514     }
515 
516     private static void verifyPadding(int padding) {
517         if (padding < 0 || padding > MAX_UNSIGNED_BYTE) {
518             throw new IllegalArgumentException("Invalid padding value: " + padding);
519         }
520     }
521 
522     private void verifyPayloadLength(int payloadLength) {
523         if (payloadLength > maxFrameSize) {
524             throw new IllegalArgumentException("Total payload length " + payloadLength
525                     + " exceeds max frame length.");
526         }
527     }
528 
529     private static void verifyWeight(short weight) {
530         if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) {
531             throw new IllegalArgumentException("Invalid weight: " + weight);
532         }
533     }
534 
535     private static void verifyErrorCode(long errorCode) {
536         if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
537             throw new IllegalArgumentException("Invalid errorCode: " + errorCode);
538         }
539     }
540 
541     private static void verifyWindowSizeIncrement(int windowSizeIncrement) {
542         if (windowSizeIncrement < 0) {
543             throw new IllegalArgumentException("WindowSizeIncrement must be >= 0");
544         }
545     }
546 }