View Javadoc

1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a 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
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package org.jboss.netty.handler.codec.spdy;
17  
18  import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;
19  
20  import org.jboss.netty.buffer.ChannelBuffer;
21  import org.jboss.netty.buffer.ChannelBuffers;
22  import org.jboss.netty.channel.Channel;
23  import org.jboss.netty.channel.ChannelHandlerContext;
24  import org.jboss.netty.channel.Channels;
25  import org.jboss.netty.handler.codec.frame.FrameDecoder;
26  import org.jboss.netty.handler.codec.frame.TooLongFrameException;
27  
28  /**
29   * Decodes {@link ChannelBuffer}s into SPDY Data and Control Frames.
30   */
31  public class SpdyFrameDecoder extends FrameDecoder {
32  
33      private final int spdyVersion;
34      private final int maxChunkSize;
35      private final int maxHeaderSize;
36  
37      private final SpdyHeaderBlockDecompressor headerBlockDecompressor;
38  
39      private State state;
40      private SpdySettingsFrame spdySettingsFrame;
41      private SpdyHeaderBlock spdyHeaderBlock;
42  
43      // SPDY common header fields
44      private byte flags;
45      private int length;
46      private int version;
47      private int type;
48      private int streamID;
49  
50      // Header block decoding fields
51      private int headerSize;
52      private int numHeaders;
53      private ChannelBuffer decompressed;
54  
55      private static enum State {
56          READ_COMMON_HEADER,
57          READ_CONTROL_FRAME,
58          READ_SETTINGS_FRAME,
59          READ_HEADER_BLOCK_FRAME,
60          READ_HEADER_BLOCK,
61          READ_DATA_FRAME,
62          DISCARD_FRAME,
63          FRAME_ERROR
64      }
65  
66      /**
67       * Creates a new instance with the default {@code version (2)},
68       * {@code maxChunkSize (8192)}, and {@code maxHeaderSize (16384)}.
69       */
70      @Deprecated
71      public SpdyFrameDecoder() {
72          this(2);
73      }
74  
75      /**
76       * Creates a new instance with the specified {@code version} and the default
77       * {@code maxChunkSize (8192)} and {@code maxHeaderSize (16384)}.
78       */
79      public SpdyFrameDecoder(int version) {
80          this(version, 8192, 16384);
81      }
82  
83      /**
84       * Creates a new instance with the specified parameters.
85       */
86      public SpdyFrameDecoder(int version, int maxChunkSize, int maxHeaderSize) {
87          super(false);
88          if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
89              throw new IllegalArgumentException(
90                      "unsupported version: " + version);
91          }
92          if (maxChunkSize <= 0) {
93              throw new IllegalArgumentException(
94                      "maxChunkSize must be a positive integer: " + maxChunkSize);
95          }
96          if (maxHeaderSize <= 0) {
97              throw new IllegalArgumentException(
98                      "maxHeaderSize must be a positive integer: " + maxHeaderSize);
99          }
100         spdyVersion = version;
101         this.maxChunkSize = maxChunkSize;
102         this.maxHeaderSize = maxHeaderSize;
103         headerBlockDecompressor = SpdyHeaderBlockDecompressor.newInstance(version);
104         state = State.READ_COMMON_HEADER;
105     }
106 
107     @Override
108     protected Object decodeLast(
109             ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)
110             throws Exception {
111         try {
112             return decode(ctx, channel, buffer);
113         } finally {
114             headerBlockDecompressor.end();
115         }
116     }
117 
118     @Override
119     protected Object decode(
120             ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)
121             throws Exception {
122         switch(state) {
123         case READ_COMMON_HEADER:
124             state = readCommonHeader(buffer);
125             if (state == State.FRAME_ERROR) {
126                 if (version != spdyVersion) {
127                     fireProtocolException(ctx, "Unsupported version: " + version);
128                 } else {
129                     fireInvalidControlFrameException(ctx);
130                 }
131             }
132 
133             // FrameDecoders must consume data when producing frames
134             // All length 0 frames must be generated now
135             if (length == 0) {
136                 if (state == State.READ_DATA_FRAME) {
137                     if (streamID == 0) {
138                         state = State.FRAME_ERROR;
139                         fireProtocolException(ctx, "Received invalid data frame");
140                         return null;
141                     }
142 
143                     SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
144                     spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
145                     state = State.READ_COMMON_HEADER;
146                     return spdyDataFrame;
147                 }
148                 // There are no length 0 control frames
149                 state = State.READ_COMMON_HEADER;
150             }
151 
152             return null;
153 
154         case READ_CONTROL_FRAME:
155             try {
156                 Object frame = readControlFrame(buffer);
157                 if (frame != null) {
158                     state = State.READ_COMMON_HEADER;
159                 }
160                 return frame;
161             } catch (IllegalArgumentException e) {
162                 state = State.FRAME_ERROR;
163                 fireInvalidControlFrameException(ctx);
164             }
165             return null;
166 
167         case READ_SETTINGS_FRAME:
168             if (spdySettingsFrame == null) {
169                 // Validate frame length against number of entries
170                 if (buffer.readableBytes() < 4) {
171                     return null;
172                 }
173                 int numEntries = getUnsignedInt(buffer, buffer.readerIndex());
174                 buffer.skipBytes(4);
175                 length -= 4;
176 
177                 // Each ID/Value entry is 8 bytes
178                 if ((length & 0x07) != 0 || length >> 3 != numEntries) {
179                     state = State.FRAME_ERROR;
180                     fireInvalidControlFrameException(ctx);
181                     return null;
182                 }
183 
184                 spdySettingsFrame = new DefaultSpdySettingsFrame();
185 
186                 boolean clear = (flags & SPDY_SETTINGS_CLEAR) != 0;
187                 spdySettingsFrame.setClearPreviouslyPersistedSettings(clear);
188             }
189 
190             int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3);
191             for (int i = 0; i < readableEntries; i ++) {
192                 int ID;
193                 byte ID_flags;
194                 if (version < 3) {
195                     // Chromium Issue 79156
196                     // SPDY setting ids are not written in network byte order
197                     // Read id assuming the architecture is little endian
198                     ID = buffer.readByte() & 0xFF |
199                         (buffer.readByte() & 0xFF) << 8 |
200                         (buffer.readByte() & 0xFF) << 16;
201                     ID_flags = buffer.readByte();
202                 } else {
203                     ID_flags = buffer.readByte();
204                     ID = getUnsignedMedium(buffer, buffer.readerIndex());
205                     buffer.skipBytes(3);
206                 }
207                 int value = getSignedInt(buffer, buffer.readerIndex());
208                 buffer.skipBytes(4);
209 
210                 // Check for invalid ID -- avoid IllegalArgumentException in setValue
211                 if (ID == 0) {
212                     state = State.FRAME_ERROR;
213                     spdySettingsFrame = null;
214                     fireInvalidControlFrameException(ctx);
215                     return null;
216                 }
217 
218                 if (!spdySettingsFrame.isSet(ID)) {
219                     boolean persistVal = (ID_flags & SPDY_SETTINGS_PERSIST_VALUE) != 0;
220                     boolean persisted  = (ID_flags & SPDY_SETTINGS_PERSISTED) != 0;
221                     spdySettingsFrame.setValue(ID, value, persistVal, persisted);
222                 }
223             }
224 
225             length -= 8 * readableEntries;
226             if (length == 0) {
227                 state = State.READ_COMMON_HEADER;
228                 Object frame = spdySettingsFrame;
229                 spdySettingsFrame = null;
230                 return frame;
231             }
232             return null;
233 
234         case READ_HEADER_BLOCK_FRAME:
235             try {
236                 spdyHeaderBlock = readHeaderBlockFrame(buffer);
237                 if (spdyHeaderBlock != null) {
238                     if (length == 0) {
239                         state = State.READ_COMMON_HEADER;
240                         Object frame = spdyHeaderBlock;
241                         spdyHeaderBlock = null;
242                         return frame;
243                     }
244                     state = State.READ_HEADER_BLOCK;
245                 }
246                 return null;
247             } catch (IllegalArgumentException e) {
248                 state = State.FRAME_ERROR;
249                 fireInvalidControlFrameException(ctx);
250                 return null;
251             }
252 
253         case READ_HEADER_BLOCK:
254             int compressedBytes = Math.min(buffer.readableBytes(), length);
255             length -= compressedBytes;
256 
257             try {
258                 decodeHeaderBlock(buffer.readSlice(compressedBytes));
259             } catch (Exception e) {
260                 state = State.FRAME_ERROR;
261                 spdyHeaderBlock = null;
262                 decompressed = null;
263                 Channels.fireExceptionCaught(ctx, e);
264                 return null;
265             }
266 
267             if (spdyHeaderBlock != null && spdyHeaderBlock.isInvalid()) {
268                 Object frame = spdyHeaderBlock;
269                 spdyHeaderBlock = null;
270                 decompressed = null;
271                 if (length == 0) {
272                     state = State.READ_COMMON_HEADER;
273                 }
274                 return frame;
275             }
276 
277             if (length == 0) {
278                 Object frame = spdyHeaderBlock;
279                 spdyHeaderBlock = null;
280                 state = State.READ_COMMON_HEADER;
281                 return frame;
282             }
283             return null;
284 
285         case READ_DATA_FRAME:
286             if (streamID == 0) {
287                 state = State.FRAME_ERROR;
288                 fireProtocolException(ctx, "Received invalid data frame");
289                 return null;
290             }
291 
292             // Generate data frames that do not exceed maxChunkSize
293             int dataLength = Math.min(maxChunkSize, length);
294 
295             // Wait until entire frame is readable
296             if (buffer.readableBytes() < dataLength) {
297                 return null;
298             }
299 
300             SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
301             spdyDataFrame.setData(buffer.readBytes(dataLength));
302             length -= dataLength;
303 
304             if (length == 0) {
305                 spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
306                 state = State.READ_COMMON_HEADER;
307             }
308             return spdyDataFrame;
309 
310         case DISCARD_FRAME:
311             int numBytes = Math.min(buffer.readableBytes(), length);
312             buffer.skipBytes(numBytes);
313             length -= numBytes;
314             if (length == 0) {
315                 state = State.READ_COMMON_HEADER;
316             }
317             return null;
318 
319         case FRAME_ERROR:
320             buffer.skipBytes(buffer.readableBytes());
321             return null;
322 
323         default:
324             throw new Error("Shouldn't reach here.");
325         }
326     }
327 
328     private State readCommonHeader(ChannelBuffer buffer) {
329         // Wait until entire header is readable
330         if (buffer.readableBytes() < SPDY_HEADER_SIZE) {
331             return State.READ_COMMON_HEADER;
332         }
333 
334         int frameOffset  = buffer.readerIndex();
335         int flagsOffset  = frameOffset + SPDY_HEADER_FLAGS_OFFSET;
336         int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET;
337         buffer.skipBytes(SPDY_HEADER_SIZE);
338 
339         // Read common header fields
340         boolean control = (buffer.getByte(frameOffset) & 0x80) != 0;
341         flags  = buffer.getByte(flagsOffset);
342         length = getUnsignedMedium(buffer, lengthOffset);
343 
344         if (control) {
345             // Decode control frame common header
346             version = getUnsignedShort(buffer, frameOffset) & 0x7FFF;
347 
348             int typeOffset = frameOffset + SPDY_HEADER_TYPE_OFFSET;
349             type = getUnsignedShort(buffer, typeOffset);
350 
351             // Check version first then validity
352             if (version != spdyVersion || !isValidControlFrameHeader()) {
353                 return State.FRAME_ERROR;
354             }
355 
356             // Make sure decoder will produce a frame or consume input
357             State nextState;
358             if (willGenerateControlFrame()) {
359                 switch (type) {
360                 case SPDY_SYN_STREAM_FRAME:
361                 case SPDY_SYN_REPLY_FRAME:
362                 case SPDY_HEADERS_FRAME:
363                     nextState = State.READ_HEADER_BLOCK_FRAME;
364                     break;
365 
366                 case SPDY_SETTINGS_FRAME:
367                     nextState = State.READ_SETTINGS_FRAME;
368                     break;
369 
370                 default:
371                     nextState = State.READ_CONTROL_FRAME;
372                 }
373             } else if (length != 0) {
374                 nextState = State.DISCARD_FRAME;
375             } else {
376                 nextState = State.READ_COMMON_HEADER;
377             }
378             return nextState;
379         } else {
380             // Decode data frame common header
381             streamID = getUnsignedInt(buffer, frameOffset);
382 
383             return State.READ_DATA_FRAME;
384         }
385     }
386 
387     private Object readControlFrame(ChannelBuffer buffer) {
388         int streamID;
389         int statusCode;
390         switch (type) {
391         case SPDY_RST_STREAM_FRAME:
392             if (buffer.readableBytes() < 8) {
393                 return null;
394             }
395 
396             streamID = getUnsignedInt(buffer, buffer.readerIndex());
397             statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
398             buffer.skipBytes(8);
399 
400             return new DefaultSpdyRstStreamFrame(streamID, statusCode);
401 
402         case SPDY_PING_FRAME:
403             if (buffer.readableBytes() < 4) {
404                 return null;
405             }
406 
407             int ID = getSignedInt(buffer, buffer.readerIndex());
408             buffer.skipBytes(4);
409 
410             return new DefaultSpdyPingFrame(ID);
411 
412         case SPDY_GOAWAY_FRAME:
413             int minLength = version < 3 ? 4 : 8;
414             if (buffer.readableBytes() < minLength) {
415                 return null;
416             }
417 
418             int lastGoodStreamID = getUnsignedInt(buffer, buffer.readerIndex());
419             buffer.skipBytes(4);
420 
421             if (version < 3) {
422                 return new DefaultSpdyGoAwayFrame(lastGoodStreamID);
423             }
424 
425             statusCode = getSignedInt(buffer, buffer.readerIndex());
426             buffer.skipBytes(4);
427 
428             return new DefaultSpdyGoAwayFrame(lastGoodStreamID, statusCode);
429 
430         case SPDY_WINDOW_UPDATE_FRAME:
431             if (buffer.readableBytes() < 8) {
432                 return null;
433             }
434 
435             streamID = getUnsignedInt(buffer, buffer.readerIndex());
436             int deltaWindowSize = getUnsignedInt(buffer, buffer.readerIndex() + 4);
437             buffer.skipBytes(8);
438 
439             return new DefaultSpdyWindowUpdateFrame(streamID, deltaWindowSize);
440 
441         default:
442             throw new Error("Shouldn't reach here.");
443         }
444     }
445 
446     private SpdyHeaderBlock readHeaderBlockFrame(ChannelBuffer buffer) {
447         int minLength;
448         int streamID;
449         switch (type) {
450         case SPDY_SYN_STREAM_FRAME:
451             minLength = version < 3 ? 12 : 10;
452             if (buffer.readableBytes() < minLength) {
453                 return null;
454             }
455 
456             int offset = buffer.readerIndex();
457             streamID = getUnsignedInt(buffer, offset);
458             int associatedToStreamID = getUnsignedInt(buffer, offset + 4);
459             byte priority = (byte) (buffer.getByte(offset + 8) >> 5 & 0x07);
460             if (version < 3) {
461                 priority >>= 1;
462             }
463             buffer.skipBytes(10);
464             length -= 10;
465 
466             // SPDY/2 requires 16-bits of padding for empty header blocks
467             if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
468                 buffer.skipBytes(2);
469                 length = 0;
470             }
471 
472             SpdySynStreamFrame spdySynStreamFrame =
473                     new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority);
474             spdySynStreamFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
475             spdySynStreamFrame.setUnidirectional((flags & SPDY_FLAG_UNIDIRECTIONAL) != 0);
476 
477             return spdySynStreamFrame;
478 
479         case SPDY_SYN_REPLY_FRAME:
480             minLength = version < 3 ? 8 : 4;
481             if (buffer.readableBytes() < minLength) {
482                 return null;
483             }
484 
485             streamID = getUnsignedInt(buffer, buffer.readerIndex());
486             buffer.skipBytes(4);
487             length -= 4;
488 
489             // SPDY/2 has 16-bits of unused space
490             if (version < 3) {
491                 buffer.skipBytes(2);
492                 length -= 2;
493             }
494 
495             // SPDY/2 requires 16-bits of padding for empty header blocks
496             if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
497                 buffer.skipBytes(2);
498                 length = 0;
499             }
500 
501             SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
502             spdySynReplyFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
503 
504             return spdySynReplyFrame;
505 
506         case SPDY_HEADERS_FRAME:
507             if (buffer.readableBytes() < 4) {
508                 return null;
509             }
510 
511             // SPDY/2 allows length 4 frame when there are no name/value pairs
512             if (version < 3 && length > 4 && buffer.readableBytes() < 8) {
513                 return null;
514             }
515 
516             streamID = getUnsignedInt(buffer, buffer.readerIndex());
517             buffer.skipBytes(4);
518             length -= 4;
519 
520             // SPDY/2 has 16-bits of unused space
521             if (version < 3 && length != 0) {
522                 buffer.skipBytes(2);
523                 length -= 2;
524             }
525 
526             // SPDY/2 requires 16-bits of padding for empty header blocks
527             if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
528                 buffer.skipBytes(2);
529                 length = 0;
530             }
531 
532             SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID);
533             spdyHeadersFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
534 
535             return spdyHeadersFrame;
536 
537         default:
538             throw new Error("Shouldn't reach here.");
539         }
540     }
541 
542     private boolean ensureBytes(int bytes) throws Exception {
543         if (decompressed.readableBytes() >= bytes) {
544             return true;
545         }
546         // Perhaps last call to decode filled output buffer
547         headerBlockDecompressor.decode(decompressed);
548         return decompressed.readableBytes() >= bytes;
549     }
550 
551     private int readLengthField() {
552         if (version < 3) {
553             return decompressed.readUnsignedShort();
554         } else {
555             return decompressed.readInt();
556         }
557     }
558 
559     private void decodeHeaderBlock(ChannelBuffer buffer) throws Exception {
560         if (decompressed == null) {
561             // First time we start to decode a header block
562             // Initialize header block decoding fields
563             headerSize = 0;
564             numHeaders = -1;
565             decompressed = ChannelBuffers.dynamicBuffer(8192);
566         }
567 
568         // Accumulate decompressed data
569         headerBlockDecompressor.setInput(buffer);
570         headerBlockDecompressor.decode(decompressed);
571 
572         if (spdyHeaderBlock == null) {
573             // Only decompressing data to keep decompression context in sync
574             decompressed = null;
575             return;
576         }
577 
578         int lengthFieldSize = version < 3 ? 2 : 4;
579 
580         if (numHeaders == -1) {
581             // Read number of Name/Value pairs
582             if (decompressed.readableBytes() < lengthFieldSize) {
583                 return;
584             }
585             numHeaders = readLengthField();
586             if (numHeaders < 0) {
587                 spdyHeaderBlock.setInvalid();
588                 return;
589             }
590         }
591 
592         while (numHeaders > 0) {
593             int headerSize = this.headerSize;
594             decompressed.markReaderIndex();
595 
596             // Try to read length of name
597             if (!ensureBytes(lengthFieldSize)) {
598                 decompressed.resetReaderIndex();
599                 decompressed.discardReadBytes();
600                 return;
601             }
602             int nameLength = readLengthField();
603 
604             // Recipients of a zero-length name must issue a stream error
605             if (nameLength <= 0) {
606                 spdyHeaderBlock.setInvalid();
607                 return;
608             }
609             headerSize += nameLength;
610             if (headerSize > maxHeaderSize) {
611                 throw new TooLongFrameException(
612                         "Header block exceeds " + maxHeaderSize);
613             }
614 
615             // Try to read name
616             if (!ensureBytes(nameLength)) {
617                 decompressed.resetReaderIndex();
618                 decompressed.discardReadBytes();
619                 return;
620             }
621             byte[] nameBytes = new byte[nameLength];
622             decompressed.readBytes(nameBytes);
623             String name = new String(nameBytes, "UTF-8");
624 
625             // Check for identically named headers
626             if (spdyHeaderBlock.containsHeader(name)) {
627                 spdyHeaderBlock.setInvalid();
628                 return;
629             }
630 
631             // Try to read length of value
632             if (!ensureBytes(lengthFieldSize)) {
633                 decompressed.resetReaderIndex();
634                 decompressed.discardReadBytes();
635                 return;
636             }
637             int valueLength = readLengthField();
638 
639             // Recipients of illegal value fields must issue a stream error
640             if (valueLength <= 0) {
641                 spdyHeaderBlock.setInvalid();
642                 return;
643             }
644             headerSize += valueLength;
645             if (headerSize > maxHeaderSize) {
646                 throw new TooLongFrameException(
647                         "Header block exceeds " + maxHeaderSize);
648             }
649 
650             // Try to read value
651             if (!ensureBytes(valueLength)) {
652                 decompressed.resetReaderIndex();
653                 decompressed.discardReadBytes();
654                 return;
655             }
656             byte[] valueBytes = new byte[valueLength];
657             decompressed.readBytes(valueBytes);
658 
659             // Add Name/Value pair to headers
660             int index = 0;
661             int offset = 0;
662             while (index < valueLength) {
663                 while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
664                     index ++;
665                 }
666                 if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
667                     // Received multiple, in-sequence NULL characters
668                     // Recipients of illegal value fields must issue a stream error
669                     spdyHeaderBlock.setInvalid();
670                     return;
671                 }
672                 String value = new String(valueBytes, offset, index - offset, "UTF-8");
673 
674                 try {
675                     spdyHeaderBlock.addHeader(name, value);
676                 } catch (IllegalArgumentException e) {
677                     // Name contains NULL or non-ascii characters
678                     spdyHeaderBlock.setInvalid();
679                     return;
680                 }
681                 index ++;
682                 offset = index;
683             }
684             numHeaders --;
685             this.headerSize = headerSize;
686         }
687         decompressed = null;
688     }
689 
690     private boolean isValidControlFrameHeader() {
691         switch (type) {
692         case SPDY_SYN_STREAM_FRAME:
693             return version < 3 ? length >= 12 : length >= 10;
694 
695         case SPDY_SYN_REPLY_FRAME:
696             return version < 3 ? length >= 8 : length >= 4;
697 
698         case SPDY_RST_STREAM_FRAME:
699             return flags == 0 && length == 8;
700 
701         case SPDY_SETTINGS_FRAME:
702             return length >= 4;
703 
704         case SPDY_NOOP_FRAME:
705             return length == 0;
706 
707         case SPDY_PING_FRAME:
708             return length == 4;
709 
710         case SPDY_GOAWAY_FRAME:
711             return version < 3 ? length == 4 : length == 8;
712 
713         case SPDY_HEADERS_FRAME:
714             if (version < 3) {
715                 return length == 4 || length >= 8;
716             } else {
717                 return length >= 4;
718             }
719 
720         case SPDY_WINDOW_UPDATE_FRAME:
721             return length == 8;
722 
723         case SPDY_CREDENTIAL_FRAME:
724         default:
725             return true;
726         }
727     }
728 
729     private boolean willGenerateControlFrame() {
730         switch (type) {
731         case SPDY_SYN_STREAM_FRAME:
732         case SPDY_SYN_REPLY_FRAME:
733         case SPDY_RST_STREAM_FRAME:
734         case SPDY_SETTINGS_FRAME:
735         case SPDY_PING_FRAME:
736         case SPDY_GOAWAY_FRAME:
737         case SPDY_HEADERS_FRAME:
738         case SPDY_WINDOW_UPDATE_FRAME:
739             return true;
740 
741         case SPDY_NOOP_FRAME:
742         case SPDY_CREDENTIAL_FRAME:
743         default:
744             return false;
745         }
746     }
747 
748     private void fireInvalidControlFrameException(ChannelHandlerContext ctx) {
749         String message = "Received invalid control frame";
750         switch (type) {
751         case SPDY_SYN_STREAM_FRAME:
752             message = "Received invalid SYN_STREAM control frame";
753             break;
754 
755         case SPDY_SYN_REPLY_FRAME:
756             message = "Received invalid SYN_REPLY control frame";
757             break;
758 
759         case SPDY_RST_STREAM_FRAME:
760             message = "Received invalid RST_STREAM control frame";
761             break;
762 
763         case SPDY_SETTINGS_FRAME:
764             message = "Received invalid SETTINGS control frame";
765             break;
766 
767         case SPDY_NOOP_FRAME:
768             message = "Received invalid NOOP control frame";
769             break;
770 
771         case SPDY_PING_FRAME:
772             message = "Received invalid PING control frame";
773             break;
774 
775         case SPDY_GOAWAY_FRAME:
776             message = "Received invalid GOAWAY control frame";
777             break;
778 
779         case SPDY_HEADERS_FRAME:
780             message = "Received invalid HEADERS control frame";
781             break;
782 
783         case SPDY_WINDOW_UPDATE_FRAME:
784             message = "Received invalid WINDOW_UPDATE control frame";
785             break;
786 
787         case SPDY_CREDENTIAL_FRAME:
788             message = "Received invalid CREDENTIAL control frame";
789             break;
790         }
791         fireProtocolException(ctx, message);
792     }
793 
794     private static void fireProtocolException(ChannelHandlerContext ctx, String message) {
795         Channels.fireExceptionCaught(ctx, new SpdyProtocolException(message));
796     }
797 }