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
25 import java.util.List;
26
27 /**
28 * A decoder that splits the received {@link ByteBuf}s dynamically by the
29 * value of the Google Protocol Buffers
30 * <a href="https://developers.google.com/protocol-buffers/docs/encoding#varints">Base
31 * 128 Varints</a> integer length field in the message. For example:
32 * <pre>
33 * BEFORE DECODE (302 bytes) AFTER DECODE (300 bytes)
34 * +--------+---------------+ +---------------+
35 * | Length | Protobuf Data |----->| Protobuf Data |
36 * | 0xAC02 | (300 bytes) | | (300 bytes) |
37 * +--------+---------------+ +---------------+
38 * </pre>
39 *
40 * @see CodedInputStream
41 * @see CodedInputByteBufferNano
42 */
43 public class ProtobufVarint32FrameDecoder extends ByteToMessageDecoder {
44
45 // TODO maxFrameLength + safe skip + fail-fast option
46 // (just like LengthFieldBasedFrameDecoder)
47
48 @Override
49 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
50 throws Exception {
51 in.markReaderIndex();
52 int preIndex = in.readerIndex();
53 int length = readRawVarint32(in);
54 if (preIndex == in.readerIndex()) {
55 return;
56 }
57 if (length < 0) {
58 throw new CorruptedFrameException("negative length: " + length);
59 }
60
61 if (in.readableBytes() < length) {
62 in.resetReaderIndex();
63 } else {
64 out.add(in.readRetainedSlice(length));
65 }
66 }
67
68 /**
69 * Reads variable length 32bit int from buffer
70 *
71 * @return decoded int if buffers readerIndex has been forwarded else nonsense value
72 */
73 static int readRawVarint32(ByteBuf buffer) {
74 if (buffer.readableBytes() < 4) {
75 return readRawVarint24(buffer);
76 }
77 int wholeOrMore = buffer.getIntLE(buffer.readerIndex());
78 int firstOneOnStop = ~wholeOrMore & 0x80808080;
79 if (firstOneOnStop == 0) {
80 return readRawVarint40(buffer, wholeOrMore);
81 }
82 int bitsToKeep = Integer.numberOfTrailingZeros(firstOneOnStop) + 1;
83 buffer.skipBytes(bitsToKeep >> 3);
84 int thisVarintMask = firstOneOnStop ^ (firstOneOnStop - 1);
85 int wholeWithContinuations = wholeOrMore & thisVarintMask;
86 // mix them up as per varint spec while dropping the continuation bits:
87 // 0x7F007F isolate the first byte and the third byte dropping the continuation bits
88 // 0x7F007F00 isolate the second byte and the fourth byte dropping the continuation bits
89 // the second and fourth byte are shifted to the right by 1, filling the gaps left by the first and third byte
90 // it means that the first and second bytes now occupy the first 14 bits (7 bits each)
91 // and the third and fourth bytes occupy the next 14 bits (7 bits each), with a gap between the 2s of 2 bytes
92 // and another gap of 2 bytes after the forth and third.
93 wholeWithContinuations = (wholeWithContinuations & 0x7F007F) | ((wholeWithContinuations & 0x7F007F00) >> 1);
94 // 0x3FFF isolate the first 14 bits i.e. the first and second bytes
95 // 0x3FFF0000 isolate the next 14 bits i.e. the third and forth bytes
96 // the third and forth bytes are shifted to the right by 2, filling the gaps left by the first and second bytes
97 return (wholeWithContinuations & 0x3FFF) | ((wholeWithContinuations & 0x3FFF0000) >> 2);
98 }
99
100 private static int readRawVarint40(ByteBuf buffer, int wholeOrMore) {
101 byte lastByte;
102 if (buffer.readableBytes() == 4 || (lastByte = buffer.getByte(buffer.readerIndex() + 4)) < 0) {
103 throw new CorruptedFrameException("malformed varint.");
104 }
105 buffer.skipBytes(5);
106 // add it to wholeOrMore
107 return wholeOrMore & 0x7F |
108 (((wholeOrMore >> 8) & 0x7F) << 7) |
109 (((wholeOrMore >> 16) & 0x7F) << 14) |
110 (((wholeOrMore >> 24) & 0x7F) << 21) |
111 (lastByte << 28);
112 }
113
114 private static int readRawVarint24(ByteBuf buffer) {
115 if (!buffer.isReadable()) {
116 return 0;
117 }
118 buffer.markReaderIndex();
119
120 byte tmp = buffer.readByte();
121 if (tmp >= 0) {
122 return tmp;
123 }
124 int result = tmp & 127;
125 if (!buffer.isReadable()) {
126 buffer.resetReaderIndex();
127 return 0;
128 }
129 if ((tmp = buffer.readByte()) >= 0) {
130 return result | tmp << 7;
131 }
132 result |= (tmp & 127) << 7;
133 if (!buffer.isReadable()) {
134 buffer.resetReaderIndex();
135 return 0;
136 }
137 if ((tmp = buffer.readByte()) >= 0) {
138 return result | tmp << 14;
139 }
140 return result | (tmp & 127) << 14;
141 }
142 }