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, this, maxChunkSize);
126         spdyFrameEncoder = createEncoder(version);
127         this.spdyHeaderBlockDecoder = spdyHeaderBlockDecoder;
128         this.spdyHeaderBlockEncoder = spdyHeaderBlockEncoder;
129         this.validateHeaders = validateHeaders;
130     }
131 
132     protected SpdyFrameDecoder createDecoder(SpdyVersion version, SpdyFrameDecoderDelegate delegate, int maxChunkSize) {
133         return new SpdyFrameDecoder(version, delegate, maxChunkSize) {
134             @Override
135             protected boolean isValidUnknownFrameHeader(int streamId, int type, byte flags, int length) {
136                 if (supportsUnknownFrames) {
137                     return SpdyFrameCodec.this.isValidUnknownFrameHeader(streamId, type, flags, length);
138                 }
139                 return super.isValidUnknownFrameHeader(streamId, type, flags, length);
140             }
141         };
142     }
143 
144     protected SpdyFrameEncoder createEncoder(SpdyVersion version) {
145         return new SpdyFrameEncoder(version);
146     }
147 
148     @Override
149     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
150         super.handlerAdded(ctx);
151         this.ctx = ctx;
152         ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
153             @Override
154             public void operationComplete(ChannelFuture future) throws Exception {
155                 spdyHeaderBlockDecoder.end();
156                 spdyHeaderBlockEncoder.end();
157             }
158         });
159     }
160 
161     @Override
162     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
163         spdyFrameDecoder.decode(in);
164     }
165 
166     @Override
167     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
168         if (!read) {
169             if (!ctx.channel().config().isAutoRead()) {
170                 ctx.read();
171             }
172         }
173         read = false;
174         super.channelReadComplete(ctx);
175     }
176 
177     @Override
178     public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
179         ctx.bind(localAddress, promise);
180     }
181 
182     @Override
183     public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
184                         ChannelPromise promise) throws Exception {
185         ctx.connect(remoteAddress, localAddress, promise);
186     }
187 
188     @Override
189     public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
190         ctx.disconnect(promise);
191     }
192 
193     @Override
194     public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
195         ctx.close(promise);
196     }
197 
198     @Override
199     public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
200         ctx.deregister(promise);
201     }
202 
203     @Override
204     public void read(ChannelHandlerContext ctx) throws Exception {
205         ctx.read();
206     }
207 
208     @Override
209     public void flush(ChannelHandlerContext ctx) throws Exception {
210         ctx.flush();
211     }
212 
213     @Override
214     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
215         ByteBuf frame;
216 
217         if (msg instanceof SpdyDataFrame) {
218 
219             SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
220             try {
221                 frame = spdyFrameEncoder.encodeDataFrame(
222                     ctx.alloc(),
223                     spdyDataFrame.streamId(),
224                     spdyDataFrame.isLast(),
225                     spdyDataFrame.content()
226                 );
227                 ctx.write(frame, promise);
228             } finally {
229                 spdyDataFrame.release();
230             }
231 
232         } else if (msg instanceof SpdySynStreamFrame) {
233 
234             SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
235             ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdySynStreamFrame);
236             try {
237                 frame = spdyFrameEncoder.encodeSynStreamFrame(
238                         ctx.alloc(),
239                         spdySynStreamFrame.streamId(),
240                         spdySynStreamFrame.associatedStreamId(),
241                         spdySynStreamFrame.priority(),
242                         spdySynStreamFrame.isLast(),
243                         spdySynStreamFrame.isUnidirectional(),
244                         headerBlock
245                 );
246             } finally {
247                 headerBlock.release();
248             }
249             ctx.write(frame, promise);
250 
251         } else if (msg instanceof SpdySynReplyFrame) {
252 
253             SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
254             ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdySynReplyFrame);
255             try {
256                 frame = spdyFrameEncoder.encodeSynReplyFrame(
257                         ctx.alloc(),
258                         spdySynReplyFrame.streamId(),
259                         spdySynReplyFrame.isLast(),
260                         headerBlock
261                 );
262             } finally {
263                 headerBlock.release();
264             }
265             ctx.write(frame, promise);
266 
267         } else if (msg instanceof SpdyRstStreamFrame) {
268 
269             SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
270             frame = spdyFrameEncoder.encodeRstStreamFrame(
271                     ctx.alloc(),
272                     spdyRstStreamFrame.streamId(),
273                     spdyRstStreamFrame.status().code()
274             );
275             ctx.write(frame, promise);
276 
277         } else if (msg instanceof SpdySettingsFrame) {
278 
279             SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
280             frame = spdyFrameEncoder.encodeSettingsFrame(
281                     ctx.alloc(),
282                     spdySettingsFrame
283             );
284             ctx.write(frame, promise);
285 
286         } else if (msg instanceof SpdyPingFrame) {
287 
288             SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
289             frame = spdyFrameEncoder.encodePingFrame(
290                     ctx.alloc(),
291                     spdyPingFrame.id()
292             );
293             ctx.write(frame, promise);
294 
295         } else if (msg instanceof SpdyGoAwayFrame) {
296 
297             SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
298             frame = spdyFrameEncoder.encodeGoAwayFrame(
299                     ctx.alloc(),
300                     spdyGoAwayFrame.lastGoodStreamId(),
301                     spdyGoAwayFrame.status().code()
302             );
303             ctx.write(frame, promise);
304 
305         } else if (msg instanceof SpdyHeadersFrame) {
306 
307             SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
308             ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdyHeadersFrame);
309             try {
310                 frame = spdyFrameEncoder.encodeHeadersFrame(
311                         ctx.alloc(),
312                         spdyHeadersFrame.streamId(),
313                         spdyHeadersFrame.isLast(),
314                         headerBlock
315                 );
316             } finally {
317                 headerBlock.release();
318             }
319             ctx.write(frame, promise);
320 
321         } else if (msg instanceof SpdyWindowUpdateFrame) {
322 
323             SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
324             frame = spdyFrameEncoder.encodeWindowUpdateFrame(
325                     ctx.alloc(),
326                     spdyWindowUpdateFrame.streamId(),
327                     spdyWindowUpdateFrame.deltaWindowSize()
328             );
329             ctx.write(frame, promise);
330         } else if (msg instanceof SpdyUnknownFrame) {
331             SpdyUnknownFrame spdyUnknownFrame = (SpdyUnknownFrame) msg;
332             try {
333                 frame = spdyFrameEncoder.encodeUnknownFrame(
334                         ctx.alloc(),
335                         spdyUnknownFrame.frameType(),
336                         spdyUnknownFrame.flags(),
337                         spdyUnknownFrame.content());
338                 ctx.write(frame, promise);
339             } finally {
340                 spdyUnknownFrame.release();
341             }
342         } else {
343             throw new UnsupportedMessageTypeException(msg);
344         }
345     }
346 
347     @Override
348     public void readDataFrame(int streamId, boolean last, ByteBuf data) {
349         read = true;
350 
351         SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId, data);
352         spdyDataFrame.setLast(last);
353         ctx.fireChannelRead(spdyDataFrame);
354     }
355 
356     @Override
357     public void readSynStreamFrame(
358             int streamId, int associatedToStreamId, byte priority, boolean last, boolean unidirectional) {
359         SpdySynStreamFrame spdySynStreamFrame =
360                 new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority, validateHeaders);
361         spdySynStreamFrame.setLast(last);
362         spdySynStreamFrame.setUnidirectional(unidirectional);
363         spdyHeadersFrame = spdySynStreamFrame;
364     }
365 
366     @Override
367     public void readSynReplyFrame(int streamId, boolean last) {
368         SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId, validateHeaders);
369         spdySynReplyFrame.setLast(last);
370         spdyHeadersFrame = spdySynReplyFrame;
371     }
372 
373     @Override
374     public void readRstStreamFrame(int streamId, int statusCode) {
375         read = true;
376 
377         SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, statusCode);
378         ctx.fireChannelRead(spdyRstStreamFrame);
379     }
380 
381     @Override
382     public void readSettingsFrame(boolean clearPersisted) {
383         read = true;
384 
385         spdySettingsFrame = new DefaultSpdySettingsFrame();
386         spdySettingsFrame.setClearPreviouslyPersistedSettings(clearPersisted);
387     }
388 
389     @Override
390     public void readSetting(int id, int value, boolean persistValue, boolean persisted) {
391         spdySettingsFrame.setValue(id, value, persistValue, persisted);
392     }
393 
394     @Override
395     public void readSettingsEnd() {
396         read = true;
397 
398         Object frame = spdySettingsFrame;
399         spdySettingsFrame = null;
400         ctx.fireChannelRead(frame);
401     }
402 
403     @Override
404     public void readPingFrame(int id) {
405         read = true;
406 
407         SpdyPingFrame spdyPingFrame = new DefaultSpdyPingFrame(id);
408         ctx.fireChannelRead(spdyPingFrame);
409     }
410 
411     @Override
412     public void readGoAwayFrame(int lastGoodStreamId, int statusCode) {
413         read = true;
414 
415         SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamId, statusCode);
416         ctx.fireChannelRead(spdyGoAwayFrame);
417     }
418 
419     @Override
420     public void readHeadersFrame(int streamId, boolean last) {
421         spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId, validateHeaders);
422         spdyHeadersFrame.setLast(last);
423     }
424 
425     @Override
426     public void readWindowUpdateFrame(int streamId, int deltaWindowSize) {
427         read = true;
428 
429         SpdyWindowUpdateFrame spdyWindowUpdateFrame = new DefaultSpdyWindowUpdateFrame(streamId, deltaWindowSize);
430         ctx.fireChannelRead(spdyWindowUpdateFrame);
431     }
432 
433     @Override
434     public void readHeaderBlock(ByteBuf headerBlock) {
435         try {
436             spdyHeaderBlockDecoder.decode(ctx.alloc(), headerBlock, spdyHeadersFrame);
437         } catch (Exception e) {
438             ctx.fireExceptionCaught(e);
439         } finally {
440             headerBlock.release();
441         }
442     }
443 
444     @Override
445     public void readHeaderBlockEnd() {
446         Object frame = null;
447         try {
448             spdyHeaderBlockDecoder.endHeaderBlock(spdyHeadersFrame);
449             frame = spdyHeadersFrame;
450             spdyHeadersFrame = null;
451         } catch (Exception e) {
452             ctx.fireExceptionCaught(e);
453         }
454         if (frame != null) {
455             read = true;
456 
457             ctx.fireChannelRead(frame);
458         }
459     }
460 
461     @Override
462     public void readUnknownFrame(int frameType, byte flags, ByteBuf payload) {
463         read = true;
464         ctx.fireChannelRead(newSpdyUnknownFrame(frameType, flags, payload));
465     }
466 
467     /**
468      * Create a SpdyUnknownFrame.
469      * */
470     protected SpdyFrame newSpdyUnknownFrame(int frameType, byte flags, ByteBuf payload) {
471         return new DefaultSpdyUnknownFrame(frameType, flags, payload);
472     }
473 
474     /**
475      * Check whether the unknown frame is valid, if not, the frame will be discarded,
476      * otherwise, the frame will be passed to {@link SpdyFrameDecoder#decodeUnknownFrame(int, byte, int, ByteBuf)}.
477      * <p>
478      * By default this method always returns {@code false}, sub-classes may override this.
479      **/
480     protected boolean isValidUnknownFrameHeader(@SuppressWarnings("unused") int streamId,
481                                                 @SuppressWarnings("unused") int type,
482                                                 @SuppressWarnings("unused") byte flags,
483                                                 @SuppressWarnings("unused") int length) {
484         return false;
485     }
486 
487     @Override
488     public void readFrameError(String message) {
489         ctx.fireExceptionCaught(INVALID_FRAME);
490     }
491 }