View Javadoc
1   /*
2    * Copyright 2013 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.memcache.binary;
17  
18  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
19  
20  import io.netty.buffer.ByteBuf;
21  import io.netty.buffer.Unpooled;
22  import io.netty.channel.ChannelHandlerContext;
23  import io.netty.handler.codec.CorruptedFrameException;
24  import io.netty.handler.codec.DecoderResult;
25  import io.netty.handler.codec.memcache.AbstractMemcacheObjectDecoder;
26  import io.netty.handler.codec.memcache.DefaultLastMemcacheContent;
27  import io.netty.handler.codec.memcache.DefaultMemcacheContent;
28  import io.netty.handler.codec.memcache.LastMemcacheContent;
29  import io.netty.handler.codec.memcache.MemcacheContent;
30  import io.netty.util.internal.UnstableApi;
31  
32  import java.util.List;
33  
34  /**
35   * Decoder for both {@link BinaryMemcacheRequest} and {@link BinaryMemcacheResponse}.
36   * <p/>
37   * The difference in the protocols (header) is implemented by the subclasses.
38   */
39  @UnstableApi
40  public abstract class AbstractBinaryMemcacheDecoder<M extends BinaryMemcacheMessage>
41      extends AbstractMemcacheObjectDecoder {
42  
43      public static final int DEFAULT_MAX_CHUNK_SIZE = 8192;
44  
45      private final int chunkSize;
46  
47      private M currentMessage;
48      private int alreadyReadChunkSize;
49  
50      private State state = State.READ_HEADER;
51  
52      /**
53       * Create a new {@link AbstractBinaryMemcacheDecoder} with default settings.
54       */
55      protected AbstractBinaryMemcacheDecoder() {
56          this(DEFAULT_MAX_CHUNK_SIZE);
57      }
58  
59      /**
60       * Create a new {@link AbstractBinaryMemcacheDecoder} with custom settings.
61       *
62       * @param chunkSize the maximum chunk size of the payload.
63       */
64      protected AbstractBinaryMemcacheDecoder(int chunkSize) {
65          checkPositiveOrZero(chunkSize, "chunkSize");
66  
67          this.chunkSize = chunkSize;
68      }
69  
70      @Override
71      protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
72          switch (state) {
73              case READ_HEADER: try {
74                  if (in.readableBytes() < 24) {
75                      return;
76                  }
77                  resetDecoder();
78  
79                  currentMessage = decodeHeader(in);
80                  validateHeader(currentMessage);
81                  state = State.READ_EXTRAS;
82              } catch (Exception e) {
83                  resetDecoder();
84                  out.add(invalidMessage(e));
85                  return;
86              }
87              case READ_EXTRAS: try {
88                  int extrasLength = currentMessage.extrasLength() & 0xFF;
89                  if (extrasLength > 0) {
90                      if (in.readableBytes() < extrasLength) {
91                          return;
92                      }
93  
94                      currentMessage.setExtras(in.readRetainedSlice(extrasLength));
95                  }
96  
97                  state = State.READ_KEY;
98              } catch (Exception e) {
99                  resetDecoder();
100                 out.add(invalidMessage(e));
101                 return;
102             }
103             case READ_KEY: try {
104                 int keyLength = currentMessage.keyLength() & 0xFFFF;
105                 if (keyLength > 0) {
106                     if (in.readableBytes() < keyLength) {
107                         return;
108                     }
109 
110                     currentMessage.setKey(in.readRetainedSlice(keyLength));
111                 }
112                 out.add(currentMessage.retain());
113                 state = State.READ_CONTENT;
114             } catch (Exception e) {
115                 resetDecoder();
116                 out.add(invalidMessage(e));
117                 return;
118             }
119             case READ_CONTENT: try {
120                 int valueLength = currentMessage.totalBodyLength()
121                     - (currentMessage.keyLength() & 0xFFFF)
122                     - (currentMessage.extrasLength() & 0xFF);
123                 int toRead = in.readableBytes();
124                 if (valueLength > 0) {
125                     if (toRead == 0) {
126                         return;
127                     }
128 
129                     if (toRead > chunkSize) {
130                         toRead = chunkSize;
131                     }
132 
133                     int remainingLength = valueLength - alreadyReadChunkSize;
134                     if (toRead > remainingLength) {
135                         toRead = remainingLength;
136                     }
137 
138                     ByteBuf chunkBuffer = in.readRetainedSlice(toRead);
139 
140                     MemcacheContent chunk;
141                     if ((alreadyReadChunkSize += toRead) >= valueLength) {
142                         chunk = new DefaultLastMemcacheContent(chunkBuffer);
143                     } else {
144                         chunk = new DefaultMemcacheContent(chunkBuffer);
145                     }
146 
147                     out.add(chunk);
148                     if (alreadyReadChunkSize < valueLength) {
149                         return;
150                     }
151                 } else {
152                     out.add(LastMemcacheContent.EMPTY_LAST_CONTENT);
153                 }
154 
155                 resetDecoder();
156                 state = State.READ_HEADER;
157                 return;
158             } catch (Exception e) {
159                 resetDecoder();
160                 out.add(invalidChunk(e));
161                 return;
162             }
163             case BAD_MESSAGE:
164                 in.skipBytes(actualReadableBytes());
165                 return;
166             default:
167                 throw new Error("Unknown state reached: " + state);
168         }
169     }
170 
171     /**
172      * Validates the length fields of a decoded header against each other.
173      * <p/>
174      * A header that fails this check can not be framed, so decoding stops permanently rather than attempting to
175      * resynchronize, see the class documentation.
176      * <p/>
177      * Note that {@code keyLength} and {@code extrasLength} are unsigned in the protocol and are interpreted as such
178      * here, see {@link BinaryMemcacheMessage#keyLength()} and {@link BinaryMemcacheMessage#extrasLength()}.
179      *
180      * @param header the decoded header to validate.
181      * @throws CorruptedFrameException if the lengths in the header are inconsistent.
182      */
183     private static void validateHeader(final BinaryMemcacheMessage header) {
184         final int totalBodyLength = header.totalBodyLength();
185         // The protocol defines totalBodyLength as an unsigned 32 bit value. A body that does not fit into a
186         // positive int can not be represented, and is never legitimate.
187         if (totalBodyLength < 0) {
188             throw new CorruptedFrameException(
189                 "totalBodyLength must neither be negative nor be larger than " + Integer.MAX_VALUE + ", but was: "
190                     + (totalBodyLength & 0xFFFFFFFFL));
191         }
192         // The protocol defines totalBodyLength as the "Length in bytes of extra + key + value", so the extras and
193         // the key on their own can never be longer than the total body.
194         final int extrasAndKeyLength = (header.extrasLength() & 0xFF) + (header.keyLength() & 0xFFFF);
195         if (extrasAndKeyLength > totalBodyLength) {
196             throw new CorruptedFrameException(
197                 "extrasLength + keyLength must not be larger than totalBodyLength, but was: "
198                     + extrasAndKeyLength + " > " + totalBodyLength);
199         }
200     }
201 
202     /**
203      * Helper method to create a message indicating a invalid decoding result.
204      *
205      * @param cause the cause of the decoding failure.
206      * @return a valid message indicating failure.
207      */
208     private M invalidMessage(Exception cause) {
209         state = State.BAD_MESSAGE;
210         M message = buildInvalidMessage();
211         message.setDecoderResult(DecoderResult.failure(cause));
212         return message;
213     }
214 
215     /**
216      * Helper method to create a content chunk indicating a invalid decoding result.
217      *
218      * @param cause the cause of the decoding failure.
219      * @return a valid content chunk indicating failure.
220      */
221     private MemcacheContent invalidChunk(Exception cause) {
222         state = State.BAD_MESSAGE;
223         MemcacheContent chunk = new DefaultLastMemcacheContent(Unpooled.EMPTY_BUFFER);
224         chunk.setDecoderResult(DecoderResult.failure(cause));
225         return chunk;
226     }
227 
228     /**
229      * When the channel goes inactive, release all frames to prevent data leaks.
230      *
231      * @param ctx handler context
232      * @throws Exception
233      */
234     @Override
235     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
236         super.channelInactive(ctx);
237 
238         resetDecoder();
239     }
240 
241     /**
242      * Prepare for next decoding iteration.
243      */
244     protected void resetDecoder() {
245         if (currentMessage != null) {
246             currentMessage.release();
247             currentMessage = null;
248         }
249         alreadyReadChunkSize = 0;
250     }
251 
252     /**
253      * Decode and return the parsed {@link BinaryMemcacheMessage}.
254      *
255      * @param in the incoming buffer.
256      * @return the decoded header.
257      */
258     protected abstract M decodeHeader(ByteBuf in);
259 
260     /**
261      * Helper method to create a upstream message when the incoming parsing did fail.
262      *
263      * @return a message indicating a decoding failure.
264      */
265     protected abstract M buildInvalidMessage();
266 
267     /**
268      * Contains all states this decoder can possibly be in.
269      * <p/>
270      * Note that most of the states can be optional, the only one required is reading
271      * the header ({@link #READ_HEADER}. All other steps depend on the length fields
272      * in the header and will be executed conditionally.
273      */
274     enum State {
275         /**
276          * Currently reading the header portion.
277          */
278         READ_HEADER,
279 
280         /**
281          * Currently reading the extras portion (optional).
282          */
283         READ_EXTRAS,
284 
285         /**
286          * Currently reading the key portion (optional).
287          */
288         READ_KEY,
289 
290         /**
291          * Currently reading the value chunks (optional).
292          */
293         READ_CONTENT,
294 
295         /**
296          * Something went wrong while decoding the message or chunks.
297          */
298         BAD_MESSAGE
299     }
300 
301 }