View Javadoc
1   /*
2    * Copyright 2015 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.protobuf;
17  
18  import com.google.protobuf.CodedInputStream;
19  import com.google.protobuf.nano.CodedInputByteBufferNano;
20  import io.netty.buffer.ByteBuf;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.handler.codec.ByteToMessageDecoder;
23  import io.netty.handler.codec.CorruptedFrameException;
24  import io.netty.handler.codec.TooLongFrameException;
25  
26  import java.util.List;
27  
28  import static io.netty.util.internal.ObjectUtil.checkPositive;
29  
30  /**
31   * A decoder that splits the received {@link ByteBuf}s dynamically by the
32   * value of the Google Protocol Buffers
33   * <a href="https://developers.google.com/protocol-buffers/docs/encoding#varints">Base
34   * 128 Varints</a> integer length field in the message. For example:
35   * <pre>
36   * BEFORE DECODE (302 bytes)       AFTER DECODE (300 bytes)
37   * +--------+---------------+      +---------------+
38   * | Length | Protobuf Data |----->| Protobuf Data |
39   * | 0xAC02 |  (300 bytes)  |      |  (300 bytes)  |
40   * +--------+---------------+      +---------------+
41   * </pre>
42   *
43   * @see CodedInputStream
44   * @see CodedInputByteBufferNano
45   */
46  public class ProtobufVarint32FrameDecoder extends ByteToMessageDecoder {
47  
48      private final int maxFrameLength;
49      private long bytesToDiscard;
50  
51      /**
52       * Creates a new instance with no frame length limit.
53       */
54      public ProtobufVarint32FrameDecoder() {
55          this(Integer.MAX_VALUE);
56      }
57  
58      /**
59       * Creates a new instance with the specified maximum frame length.
60       *
61       * @param maxFrameLength the maximum length of the frame.
62       *                       If the length exceeds this value,
63       *                       {@link TooLongFrameException} will be thrown.
64       */
65      public ProtobufVarint32FrameDecoder(int maxFrameLength) {
66          this.maxFrameLength = checkPositive(maxFrameLength, "maxFrameLength");
67      }
68  
69      @Override
70      protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
71              throws Exception {
72          if (bytesToDiscard > 0) {
73              int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
74              in.skipBytes(localBytesToDiscard);
75              bytesToDiscard -= localBytesToDiscard;
76              return;
77          }
78  
79          in.markReaderIndex();
80          int preIndex = in.readerIndex();
81          int length = readRawVarint32(in);
82          if (preIndex == in.readerIndex()) {
83              return;
84          }
85          if (length < 0) {
86              throw new CorruptedFrameException("negative length: " + length);
87          }
88  
89          if (length > maxFrameLength) {
90              long discard = length - in.readableBytes();
91              if (discard <= 0) {
92                  in.skipBytes(length);
93              } else {
94                  bytesToDiscard = discard;
95                  in.skipBytes(in.readableBytes());
96              }
97              throw new TooLongFrameException(
98                      "Frame length exceeds " + maxFrameLength
99                      + ": " + length);
100         }
101 
102         if (in.readableBytes() < length) {
103             in.resetReaderIndex();
104         } else {
105             out.add(in.readRetainedSlice(length));
106         }
107     }
108 
109     /**
110      * Reads variable length 32bit int from buffer
111      *
112      * @return decoded int if buffers readerIndex has been forwarded else nonsense value
113      */
114     static int readRawVarint32(ByteBuf buffer) {
115         if (buffer.readableBytes() < 4) {
116             return readRawVarint24(buffer);
117         }
118         int wholeOrMore = buffer.getIntLE(buffer.readerIndex());
119         int firstOneOnStop = ~wholeOrMore & 0x80808080;
120         if (firstOneOnStop == 0) {
121             return readRawVarint40(buffer, wholeOrMore);
122         }
123         int bitsToKeep = Integer.numberOfTrailingZeros(firstOneOnStop) + 1;
124         buffer.skipBytes(bitsToKeep >> 3);
125         int thisVarintMask = firstOneOnStop ^ (firstOneOnStop - 1);
126         int wholeWithContinuations = wholeOrMore & thisVarintMask;
127         // mix them up as per varint spec while dropping the continuation bits:
128         // 0x7F007F isolate the first byte and the third byte dropping the continuation bits
129         // 0x7F007F00 isolate the second byte and the fourth byte dropping the continuation bits
130         // the second and fourth byte are shifted to the right by 1, filling the gaps left by the first and third byte
131         // it means that the first and second bytes now occupy the first 14 bits (7 bits each)
132         // and the third and fourth bytes occupy the next 14 bits (7 bits each), with a gap between the 2s of 2 bytes
133         // and another gap of 2 bytes after the forth and third.
134         wholeWithContinuations = (wholeWithContinuations & 0x7F007F) | ((wholeWithContinuations & 0x7F007F00) >> 1);
135         // 0x3FFF isolate the first 14 bits i.e. the first and second bytes
136         // 0x3FFF0000 isolate the next 14 bits i.e. the third and forth bytes
137         // the third and forth bytes are shifted to the right by 2, filling the gaps left by the first and second bytes
138         return (wholeWithContinuations & 0x3FFF) | ((wholeWithContinuations & 0x3FFF0000) >> 2);
139     }
140 
141     private static int readRawVarint40(ByteBuf buffer, int wholeOrMore) {
142         byte lastByte;
143         if (buffer.readableBytes() == 4 || (lastByte = buffer.getByte(buffer.readerIndex() + 4)) < 0) {
144             throw new CorruptedFrameException("malformed varint.");
145         }
146         buffer.skipBytes(5);
147         // add it to wholeOrMore
148         return wholeOrMore & 0x7F |
149                (((wholeOrMore >> 8) & 0x7F) << 7) |
150                (((wholeOrMore >> 16) & 0x7F) << 14) |
151                (((wholeOrMore >> 24) & 0x7F) << 21) |
152                (lastByte << 28);
153     }
154 
155     private static int readRawVarint24(ByteBuf buffer) {
156         if (!buffer.isReadable()) {
157             return 0;
158         }
159         buffer.markReaderIndex();
160 
161         byte tmp = buffer.readByte();
162         if (tmp >= 0) {
163             return tmp;
164         }
165         int result = tmp & 127;
166         if (!buffer.isReadable()) {
167             buffer.resetReaderIndex();
168             return 0;
169         }
170         if ((tmp = buffer.readByte()) >= 0) {
171             return result | tmp << 7;
172         }
173         result |= (tmp & 127) << 7;
174         if (!buffer.isReadable()) {
175             buffer.resetReaderIndex();
176             return 0;
177         }
178         if ((tmp = buffer.readByte()) >= 0) {
179             return result | tmp << 14;
180         }
181         return result | (tmp & 127) << 14;
182     }
183 }