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 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         int numBytes;
548         boolean done;
549         do {
550             numBytes = headerBlockDecompressor.decode(decompressed);
551             done = decompressed.readableBytes() >= bytes;
552         } while (!done && numBytes > 0);
553         return done;
554     }
555 
556     private int readLengthField() {
557         if (version < 3) {
558             return decompressed.readUnsignedShort();
559         } else {
560             return decompressed.readInt();
561         }
562     }
563 
564     private void decodeHeaderBlock(ChannelBuffer buffer) throws Exception {
565         if (decompressed == null) {
566             // First time we start to decode a header block
567             // Initialize header block decoding fields
568             headerSize = 0;
569             numHeaders = -1;
570             decompressed = ChannelBuffers.dynamicBuffer(8192);
571         }
572 
573         // Accumulate decompressed data
574         headerBlockDecompressor.setInput(buffer);
575         headerBlockDecompressor.decode(decompressed);
576 
577         if (spdyHeaderBlock == null) {
578             // Only decompressing data to keep decompression context in sync
579             decompressed = null;
580             return;
581         }
582 
583         int lengthFieldSize = version < 3 ? 2 : 4;
584 
585         if (numHeaders == -1) {
586             // Read number of Name/Value pairs
587             if (decompressed.readableBytes() < lengthFieldSize) {
588                 return;
589             }
590             numHeaders = readLengthField();
591             if (numHeaders < 0) {
592                 spdyHeaderBlock.setInvalid();
593                 return;
594             }
595         }
596 
597         while (numHeaders > 0) {
598             int headerSize = this.headerSize;
599             decompressed.markReaderIndex();
600 
601             // Try to read length of name
602             if (!ensureBytes(lengthFieldSize)) {
603                 decompressed.resetReaderIndex();
604                 decompressed.discardReadBytes();
605                 return;
606             }
607             int nameLength = readLengthField();
608 
609             // Recipients of a zero-length name must issue a stream error
610             if (nameLength <= 0) {
611                 spdyHeaderBlock.setInvalid();
612                 return;
613             }
614             headerSize += nameLength;
615             if (headerSize > maxHeaderSize) {
616                 throw new TooLongFrameException(
617                         "Header block exceeds " + maxHeaderSize);
618             }
619 
620             // Try to read name
621             if (!ensureBytes(nameLength)) {
622                 decompressed.resetReaderIndex();
623                 decompressed.discardReadBytes();
624                 return;
625             }
626             byte[] nameBytes = new byte[nameLength];
627             decompressed.readBytes(nameBytes);
628             String name = new String(nameBytes, "UTF-8");
629 
630             // Check for identically named headers
631             if (spdyHeaderBlock.containsHeader(name)) {
632                 spdyHeaderBlock.setInvalid();
633                 return;
634             }
635 
636             // Try to read length of value
637             if (!ensureBytes(lengthFieldSize)) {
638                 decompressed.resetReaderIndex();
639                 decompressed.discardReadBytes();
640                 return;
641             }
642             int valueLength = readLengthField();
643 
644             // Recipients of illegal value fields must issue a stream error
645             if (valueLength < 0) {
646                 spdyHeaderBlock.setInvalid();
647                 return;
648             }
649 
650             // SPDY/3 allows zero-length (empty) header values
651             if (valueLength == 0) {
652                 if (version < 3) {
653                     spdyHeaderBlock.setInvalid();
654                     return;
655                 } else {
656                     spdyHeaderBlock.addHeader(name, "");
657                     numHeaders --;
658                     this.headerSize = headerSize;
659                     continue;
660                 }
661             }
662 
663             headerSize += valueLength;
664             if (headerSize > maxHeaderSize) {
665                 throw new TooLongFrameException(
666                         "Header block exceeds " + maxHeaderSize);
667             }
668 
669             // Try to read value
670             if (!ensureBytes(valueLength)) {
671                 decompressed.resetReaderIndex();
672                 decompressed.discardReadBytes();
673                 return;
674             }
675             byte[] valueBytes = new byte[valueLength];
676             decompressed.readBytes(valueBytes);
677 
678             // Add Name/Value pair to headers
679             int index = 0;
680             int offset = 0;
681             while (index < valueLength) {
682                 while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
683                     index ++;
684                 }
685                 if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
686                     // Received multiple, in-sequence NULL characters
687                     // Recipients of illegal value fields must issue a stream error
688                     spdyHeaderBlock.setInvalid();
689                     return;
690                 }
691                 String value = new String(valueBytes, offset, index - offset, "UTF-8");
692 
693                 try {
694                     spdyHeaderBlock.addHeader(name, value);
695                 } catch (IllegalArgumentException e) {
696                     // Name contains NULL or non-ascii characters
697                     spdyHeaderBlock.setInvalid();
698                     return;
699                 }
700                 index ++;
701                 offset = index;
702             }
703             numHeaders --;
704             this.headerSize = headerSize;
705         }
706         decompressed = null;
707     }
708 
709     private boolean isValidControlFrameHeader() {
710         switch (type) {
711         case SPDY_SYN_STREAM_FRAME:
712             return version < 3 ? length >= 12 : length >= 10;
713 
714         case SPDY_SYN_REPLY_FRAME:
715             return version < 3 ? length >= 8 : length >= 4;
716 
717         case SPDY_RST_STREAM_FRAME:
718             return flags == 0 && length == 8;
719 
720         case SPDY_SETTINGS_FRAME:
721             return length >= 4;
722 
723         case SPDY_NOOP_FRAME:
724             return length == 0;
725 
726         case SPDY_PING_FRAME:
727             return length == 4;
728 
729         case SPDY_GOAWAY_FRAME:
730             return version < 3 ? length == 4 : length == 8;
731 
732         case SPDY_HEADERS_FRAME:
733             if (version < 3) {
734                 return length == 4 || length >= 8;
735             } else {
736                 return length >= 4;
737             }
738 
739         case SPDY_WINDOW_UPDATE_FRAME:
740             return length == 8;
741 
742         case SPDY_CREDENTIAL_FRAME:
743         default:
744             return true;
745         }
746     }
747 
748     private boolean willGenerateControlFrame() {
749         switch (type) {
750         case SPDY_SYN_STREAM_FRAME:
751         case SPDY_SYN_REPLY_FRAME:
752         case SPDY_RST_STREAM_FRAME:
753         case SPDY_SETTINGS_FRAME:
754         case SPDY_PING_FRAME:
755         case SPDY_GOAWAY_FRAME:
756         case SPDY_HEADERS_FRAME:
757         case SPDY_WINDOW_UPDATE_FRAME:
758             return true;
759 
760         case SPDY_NOOP_FRAME:
761         case SPDY_CREDENTIAL_FRAME:
762         default:
763             return false;
764         }
765     }
766 
767     private void fireInvalidControlFrameException(ChannelHandlerContext ctx) {
768         String message = "Received invalid control frame";
769         switch (type) {
770         case SPDY_SYN_STREAM_FRAME:
771             message = "Received invalid SYN_STREAM control frame";
772             break;
773 
774         case SPDY_SYN_REPLY_FRAME:
775             message = "Received invalid SYN_REPLY control frame";
776             break;
777 
778         case SPDY_RST_STREAM_FRAME:
779             message = "Received invalid RST_STREAM control frame";
780             break;
781 
782         case SPDY_SETTINGS_FRAME:
783             message = "Received invalid SETTINGS control frame";
784             break;
785 
786         case SPDY_NOOP_FRAME:
787             message = "Received invalid NOOP control frame";
788             break;
789 
790         case SPDY_PING_FRAME:
791             message = "Received invalid PING control frame";
792             break;
793 
794         case SPDY_GOAWAY_FRAME:
795             message = "Received invalid GOAWAY control frame";
796             break;
797 
798         case SPDY_HEADERS_FRAME:
799             message = "Received invalid HEADERS control frame";
800             break;
801 
802         case SPDY_WINDOW_UPDATE_FRAME:
803             message = "Received invalid WINDOW_UPDATE control frame";
804             break;
805 
806         case SPDY_CREDENTIAL_FRAME:
807             message = "Received invalid CREDENTIAL control frame";
808             break;
809         }
810         fireProtocolException(ctx, message);
811     }
812 
813     private static void fireProtocolException(ChannelHandlerContext ctx, String message) {
814         Channels.fireExceptionCaught(ctx, new SpdyProtocolException(message));
815     }
816 }