View Javadoc
1   /*
2    * Copyright 2014 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    *   https://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 io.netty.handler.codec.spdy;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelFuture;
20  import io.netty.channel.ChannelFutureListener;
21  import io.netty.channel.ChannelHandler;
22  import io.netty.channel.ChannelHandlerContext;
23  import io.netty.channel.ChannelOutboundHandler;
24  import io.netty.channel.ChannelPromise;
25  import io.netty.handler.codec.ByteToMessageDecoder;
26  import io.netty.handler.codec.UnsupportedMessageTypeException;
27  
28  import java.net.SocketAddress;
29  import java.util.List;
30  
31  /**
32   * A {@link ChannelHandler} that encodes and decodes SPDY Frames.
33   */
34  public class SpdyFrameCodec extends ByteToMessageDecoder
35          implements SpdyFrameDecoderDelegate, ChannelOutboundHandler {
36  
37      protected static final SpdyProtocolException INVALID_FRAME =
38          new SpdyProtocolException("Received invalid frame");
39  
40      protected final SpdyFrameDecoder spdyFrameDecoder;
41      protected final SpdyFrameEncoder spdyFrameEncoder;
42      private final SpdyHeaderBlockDecoder spdyHeaderBlockDecoder;
43      private final SpdyHeaderBlockEncoder spdyHeaderBlockEncoder;
44  
45      private SpdyHeadersFrame spdyHeadersFrame;
46      private SpdySettingsFrame spdySettingsFrame;
47  
48      private ChannelHandlerContext ctx;
49      private boolean read;
50      private final boolean validateHeaders;
51      private final boolean supportsUnknownFrames;
52  
53      /**
54       * Creates a new instance with the specified {@code version},
55       * {@code validateHeaders (true)}, and
56       * the default decoder and encoder options
57       * ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)},
58       * {@code compressionLevel (6)}, {@code windowBits (15)},
59       * and {@code memLevel (8)}).
60       */
61      public SpdyFrameCodec(SpdyVersion version) {
62          this(version, true);
63      }
64  
65      /**
66       * Creates a new instance with the specified {@code version},
67       * {@code validateHeaders}, and
68       * the default decoder and encoder options
69       * ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)},
70       * {@code compressionLevel (6)}, {@code windowBits (15)},
71       * and {@code memLevel (8)}).
72       */
73      public SpdyFrameCodec(SpdyVersion version, boolean validateHeaders) {
74          this(version, 8192, 16384, 6, 15, 8, validateHeaders);
75      }
76  
77      /**
78       * Creates a new instance with the specified {@code version}, {@code validateHeaders (true)},
79       * decoder and encoder options.
80       */
81      public SpdyFrameCodec(
82              SpdyVersion version, int maxChunkSize, int maxHeaderSize,
83              int compressionLevel, int windowBits, int memLevel) {
84          this(version, maxChunkSize, maxHeaderSize, compressionLevel, windowBits, memLevel, true);
85      }
86  
87      /**
88       * Creates a new instance with the specified {@code version}, {@code validateHeaders},
89       * decoder and encoder options.
90       */
91      public SpdyFrameCodec(
92              SpdyVersion version, int maxChunkSize, int maxHeaderSize,
93              int compressionLevel, int windowBits, int memLevel, boolean validateHeaders) {
94          this(version, maxChunkSize,
95                  SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize),
96                  SpdyHeaderBlockEncoder.newInstance(version, compressionLevel, windowBits, memLevel),
97                  validateHeaders, false);
98      }
99  
100     /**
101      * Creates a new instance with the specified {@code version}, {@code validateHeaders},
102      * decoder and encoder options.
103      */
104     public SpdyFrameCodec(
105             SpdyVersion version, int maxChunkSize, int maxHeaderSize,
106             int compressionLevel, int windowBits, int memLevel, boolean validateHeaders,
107             boolean supportsUnknownFrames) {
108         this(version, maxChunkSize,
109                 SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize),
110                 SpdyHeaderBlockEncoder.newInstance(version, compressionLevel, windowBits, memLevel),
111                 validateHeaders, supportsUnknownFrames);
112     }
113 
114     protected SpdyFrameCodec(SpdyVersion version, int maxChunkSize,
115                              SpdyHeaderBlockDecoder spdyHeaderBlockDecoder,
116                              SpdyHeaderBlockEncoder spdyHeaderBlockEncoder,
117                              boolean validateHeaders) {
118         this(version, maxChunkSize, spdyHeaderBlockDecoder, spdyHeaderBlockEncoder, validateHeaders, false);
119     }
120 
121     protected SpdyFrameCodec(SpdyVersion version, int maxChunkSize,
122             SpdyHeaderBlockDecoder spdyHeaderBlockDecoder, SpdyHeaderBlockEncoder spdyHeaderBlockEncoder,
123             boolean validateHeaders, boolean supportsUnknownFrames) {
124         this.supportsUnknownFrames = supportsUnknownFrames;
125         spdyFrameDecoder = createDecoder(version, supportsUnknownFrames ? new SpdyFrameDecoderExtendedDelegate() {
126             @Override
127             public void readUnknownFrame(int frameType, byte flags, ByteBuf payload) {
128                 SpdyFrameCodec.this.readUnknownFrame(frameType, flags, payload);
129             }
130 
131             @Override
132             public void readDataFrame(int streamId, boolean last, ByteBuf data) {
133                 SpdyFrameCodec.this.readDataFrame(streamId, last, data);
134             }
135 
136             @Override
137             public void readSynStreamFrame(int streamId, int associatedToStreamId, byte priority,
138                                            boolean last, boolean unidirectional) {
139                 SpdyFrameCodec.this.readSynStreamFrame(streamId, associatedToStreamId, priority, last, unidirectional);
140             }
141 
142             @Override
143             public void readSynReplyFrame(int streamId, boolean last) {
144                 SpdyFrameCodec.this.readSynReplyFrame(streamId, last);
145             }
146 
147             @Override
148             public void readRstStreamFrame(int streamId, int statusCode) {
149                 SpdyFrameCodec.this.readRstStreamFrame(streamId, statusCode);
150             }
151 
152             @Override
153             public void readSettingsFrame(boolean clearPersisted) {
154                 SpdyFrameCodec.this.readSettingsFrame(clearPersisted);
155             }
156 
157             @Override
158             public void readSetting(int id, int value, boolean persistValue, boolean persisted) {
159                 SpdyFrameCodec.this.readSetting(id, value, persistValue, persisted);
160             }
161 
162             @Override
163             public void readSettingsEnd() {
164                 SpdyFrameCodec.this.readSettingsEnd();
165             }
166 
167             @Override
168             public void readPingFrame(int id) {
169                 SpdyFrameCodec.this.readPingFrame(id);
170             }
171 
172             @Override
173             public void readGoAwayFrame(int lastGoodStreamId, int statusCode) {
174                 SpdyFrameCodec.this.readGoAwayFrame(lastGoodStreamId, statusCode);
175             }
176 
177             @Override
178             public void readHeadersFrame(int streamId, boolean last) {
179                 SpdyFrameCodec.this.readHeadersFrame(streamId, last);
180             }
181 
182             @Override
183             public void readWindowUpdateFrame(int streamId, int deltaWindowSize) {
184                 SpdyFrameCodec.this.readWindowUpdateFrame(streamId, deltaWindowSize);
185             }
186 
187             @Override
188             public void readHeaderBlock(ByteBuf headerBlock) {
189                 SpdyFrameCodec.this.readHeaderBlock(headerBlock);
190             }
191 
192             @Override
193             public void readHeaderBlockEnd() {
194                 SpdyFrameCodec.this.readHeaderBlockEnd();
195             }
196 
197             @Override
198             public void readFrameError(String message) {
199                 SpdyFrameCodec.this.readFrameError(message);
200             }
201         } : this, maxChunkSize);
202         spdyFrameEncoder = createEncoder(version);
203         this.spdyHeaderBlockDecoder = spdyHeaderBlockDecoder;
204         this.spdyHeaderBlockEncoder = spdyHeaderBlockEncoder;
205         this.validateHeaders = validateHeaders;
206     }
207 
208     protected SpdyFrameDecoder createDecoder(SpdyVersion version, SpdyFrameDecoderDelegate delegate, int maxChunkSize) {
209         return new SpdyFrameDecoder(version, delegate, maxChunkSize) {
210             @Override
211             protected boolean isValidUnknownFrameHeader(int streamId, int type, byte flags, int length) {
212                 if (supportsUnknownFrames) {
213                     return SpdyFrameCodec.this.isValidUnknownFrameHeader(streamId, type, flags, length);
214                 }
215                 return super.isValidUnknownFrameHeader(streamId, type, flags, length);
216             }
217         };
218     }
219 
220     protected SpdyFrameEncoder createEncoder(SpdyVersion version) {
221         return new SpdyFrameEncoder(version);
222     }
223 
224     @Override
225     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
226         super.handlerAdded(ctx);
227         this.ctx = ctx;
228         ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
229             @Override
230             public void operationComplete(ChannelFuture future) throws Exception {
231                 spdyHeaderBlockDecoder.end();
232                 spdyHeaderBlockEncoder.end();
233             }
234         });
235     }
236 
237     @Override
238     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
239         spdyFrameDecoder.decode(in);
240     }
241 
242     @Override
243     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
244         if (!read) {
245             if (!ctx.channel().config().isAutoRead()) {
246                 ctx.read();
247             }
248         }
249         read = false;
250         super.channelReadComplete(ctx);
251     }
252 
253     @Override
254     public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
255         ctx.bind(localAddress, promise);
256     }
257 
258     @Override
259     public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
260                         ChannelPromise promise) throws Exception {
261         ctx.connect(remoteAddress, localAddress, promise);
262     }
263 
264     @Override
265     public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
266         ctx.disconnect(promise);
267     }
268 
269     @Override
270     public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
271         ctx.close(promise);
272     }
273 
274     @Override
275     public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
276         ctx.deregister(promise);
277     }
278 
279     @Override
280     public void read(ChannelHandlerContext ctx) throws Exception {
281         ctx.read();
282     }
283 
284     @Override
285     public void flush(ChannelHandlerContext ctx) throws Exception {
286         ctx.flush();
287     }
288 
289     @Override
290     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
291         ByteBuf frame;
292 
293         if (msg instanceof SpdyDataFrame) {
294 
295             SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
296             try {
297                 frame = spdyFrameEncoder.encodeDataFrame(
298                     ctx.alloc(),
299                     spdyDataFrame.streamId(),
300                     spdyDataFrame.isLast(),
301                     spdyDataFrame.content()
302                 );
303                 ctx.write(frame, promise);
304             } finally {
305                 spdyDataFrame.release();
306             }
307 
308         } else if (msg instanceof SpdySynStreamFrame) {
309 
310             SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
311             ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdySynStreamFrame);
312             try {
313                 frame = spdyFrameEncoder.encodeSynStreamFrame(
314                         ctx.alloc(),
315                         spdySynStreamFrame.streamId(),
316                         spdySynStreamFrame.associatedStreamId(),
317                         spdySynStreamFrame.priority(),
318                         spdySynStreamFrame.isLast(),
319                         spdySynStreamFrame.isUnidirectional(),
320                         headerBlock
321                 );
322             } finally {
323                 headerBlock.release();
324             }
325             ctx.write(frame, promise);
326 
327         } else if (msg instanceof SpdySynReplyFrame) {
328 
329             SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
330             ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdySynReplyFrame);
331             try {
332                 frame = spdyFrameEncoder.encodeSynReplyFrame(
333                         ctx.alloc(),
334                         spdySynReplyFrame.streamId(),
335                         spdySynReplyFrame.isLast(),
336                         headerBlock
337                 );
338             } finally {
339                 headerBlock.release();
340             }
341             ctx.write(frame, promise);
342 
343         } else if (msg instanceof SpdyRstStreamFrame) {
344 
345             SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
346             frame = spdyFrameEncoder.encodeRstStreamFrame(
347                     ctx.alloc(),
348                     spdyRstStreamFrame.streamId(),
349                     spdyRstStreamFrame.status().code()
350             );
351             ctx.write(frame, promise);
352 
353         } else if (msg instanceof SpdySettingsFrame) {
354 
355             SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
356             frame = spdyFrameEncoder.encodeSettingsFrame(
357                     ctx.alloc(),
358                     spdySettingsFrame
359             );
360             ctx.write(frame, promise);
361 
362         } else if (msg instanceof SpdyPingFrame) {
363 
364             SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
365             frame = spdyFrameEncoder.encodePingFrame(
366                     ctx.alloc(),
367                     spdyPingFrame.id()
368             );
369             ctx.write(frame, promise);
370 
371         } else if (msg instanceof SpdyGoAwayFrame) {
372 
373             SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
374             frame = spdyFrameEncoder.encodeGoAwayFrame(
375                     ctx.alloc(),
376                     spdyGoAwayFrame.lastGoodStreamId(),
377                     spdyGoAwayFrame.status().code()
378             );
379             ctx.write(frame, promise);
380 
381         } else if (msg instanceof SpdyHeadersFrame) {
382 
383             SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
384             ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdyHeadersFrame);
385             try {
386                 frame = spdyFrameEncoder.encodeHeadersFrame(
387                         ctx.alloc(),
388                         spdyHeadersFrame.streamId(),
389                         spdyHeadersFrame.isLast(),
390                         headerBlock
391                 );
392             } finally {
393                 headerBlock.release();
394             }
395             ctx.write(frame, promise);
396 
397         } else if (msg instanceof SpdyWindowUpdateFrame) {
398 
399             SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
400             frame = spdyFrameEncoder.encodeWindowUpdateFrame(
401                     ctx.alloc(),
402                     spdyWindowUpdateFrame.streamId(),
403                     spdyWindowUpdateFrame.deltaWindowSize()
404             );
405             ctx.write(frame, promise);
406         } else if (msg instanceof SpdyUnknownFrame) {
407             SpdyUnknownFrame spdyUnknownFrame = (SpdyUnknownFrame) msg;
408             try {
409                 frame = spdyFrameEncoder.encodeUnknownFrame(
410                         ctx.alloc(),
411                         spdyUnknownFrame.frameType(),
412                         spdyUnknownFrame.flags(),
413                         spdyUnknownFrame.content());
414                 ctx.write(frame, promise);
415             } finally {
416                 spdyUnknownFrame.release();
417             }
418         } else {
419             throw new UnsupportedMessageTypeException(msg);
420         }
421     }
422 
423     @Override
424     public void readDataFrame(int streamId, boolean last, ByteBuf data) {
425         read = true;
426 
427         SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId, data);
428         spdyDataFrame.setLast(last);
429         ctx.fireChannelRead(spdyDataFrame);
430     }
431 
432     @Override
433     public void readSynStreamFrame(
434             int streamId, int associatedToStreamId, byte priority, boolean last, boolean unidirectional) {
435         SpdySynStreamFrame spdySynStreamFrame =
436                 new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority, validateHeaders);
437         spdySynStreamFrame.setLast(last);
438         spdySynStreamFrame.setUnidirectional(unidirectional);
439         spdyHeadersFrame = spdySynStreamFrame;
440     }
441 
442     @Override
443     public void readSynReplyFrame(int streamId, boolean last) {
444         SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId, validateHeaders);
445         spdySynReplyFrame.setLast(last);
446         spdyHeadersFrame = spdySynReplyFrame;
447     }
448 
449     @Override
450     public void readRstStreamFrame(int streamId, int statusCode) {
451         read = true;
452 
453         SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, statusCode);
454         ctx.fireChannelRead(spdyRstStreamFrame);
455     }
456 
457     @Override
458     public void readSettingsFrame(boolean clearPersisted) {
459         read = true;
460 
461         spdySettingsFrame = new DefaultSpdySettingsFrame();
462         spdySettingsFrame.setClearPreviouslyPersistedSettings(clearPersisted);
463     }
464 
465     @Override
466     public void readSetting(int id, int value, boolean persistValue, boolean persisted) {
467         spdySettingsFrame.setValue(id, value, persistValue, persisted);
468     }
469 
470     @Override
471     public void readSettingsEnd() {
472         read = true;
473 
474         Object frame = spdySettingsFrame;
475         spdySettingsFrame = null;
476         ctx.fireChannelRead(frame);
477     }
478 
479     @Override
480     public void readPingFrame(int id) {
481         read = true;
482 
483         SpdyPingFrame spdyPingFrame = new DefaultSpdyPingFrame(id);
484         ctx.fireChannelRead(spdyPingFrame);
485     }
486 
487     @Override
488     public void readGoAwayFrame(int lastGoodStreamId, int statusCode) {
489         read = true;
490 
491         SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamId, statusCode);
492         ctx.fireChannelRead(spdyGoAwayFrame);
493     }
494 
495     @Override
496     public void readHeadersFrame(int streamId, boolean last) {
497         spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId, validateHeaders);
498         spdyHeadersFrame.setLast(last);
499     }
500 
501     @Override
502     public void readWindowUpdateFrame(int streamId, int deltaWindowSize) {
503         read = true;
504 
505         SpdyWindowUpdateFrame spdyWindowUpdateFrame = new DefaultSpdyWindowUpdateFrame(streamId, deltaWindowSize);
506         ctx.fireChannelRead(spdyWindowUpdateFrame);
507     }
508 
509     @Override
510     public void readHeaderBlock(ByteBuf headerBlock) {
511         try {
512             spdyHeaderBlockDecoder.decode(ctx.alloc(), headerBlock, spdyHeadersFrame);
513         } catch (Exception e) {
514             ctx.fireExceptionCaught(e);
515         } finally {
516             headerBlock.release();
517         }
518     }
519 
520     @Override
521     public void readHeaderBlockEnd() {
522         Object frame = null;
523         try {
524             spdyHeaderBlockDecoder.endHeaderBlock(spdyHeadersFrame);
525             frame = spdyHeadersFrame;
526             spdyHeadersFrame = null;
527         } catch (Exception e) {
528             ctx.fireExceptionCaught(e);
529         }
530         if (frame != null) {
531             read = true;
532 
533             ctx.fireChannelRead(frame);
534         }
535     }
536 
537     private void readUnknownFrame(int frameType, byte flags, ByteBuf payload) {
538         read = true;
539         ctx.fireChannelRead(newSpdyUnknownFrame(frameType, flags, payload));
540     }
541 
542     /**
543      * Create a SpdyUnknownFrame.
544      * */
545     protected SpdyFrame newSpdyUnknownFrame(int frameType, byte flags, ByteBuf payload) {
546         return new DefaultSpdyUnknownFrame(frameType, flags, payload);
547     }
548 
549     /**
550      * Check whether the unknown frame is valid, if not, the frame will be discarded,
551      * otherwise, the frame will be passed to {@link SpdyFrameDecoder#decodeUnknownFrame(int, byte, int, ByteBuf)}.
552      * <p>
553      * By default this method always returns {@code false}, sub-classes may override this.
554      **/
555     protected boolean isValidUnknownFrameHeader(@SuppressWarnings("unused") int streamId,
556                                                 @SuppressWarnings("unused") int type,
557                                                 @SuppressWarnings("unused") byte flags,
558                                                 @SuppressWarnings("unused") int length) {
559         return false;
560     }
561 
562     @Override
563     public void readFrameError(String message) {
564         ctx.fireExceptionCaught(INVALID_FRAME);
565     }
566 }