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