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.ExtensionRegistry;
19 import com.google.protobuf.ExtensionRegistryLite;
20 import com.google.protobuf.Message;
21 import com.google.protobuf.MessageLite;
22 import io.netty.buffer.ByteBuf;
23 import io.netty.buffer.ByteBufUtil;
24 import io.netty.channel.ChannelHandler.Sharable;
25 import io.netty.channel.ChannelHandlerContext;
26 import io.netty.channel.ChannelPipeline;
27 import io.netty.handler.codec.ByteToMessageDecoder;
28 import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
29 import io.netty.handler.codec.LengthFieldPrepender;
30 import io.netty.handler.codec.MessageToMessageDecoder;
31 import io.netty.util.internal.ObjectUtil;
32
33 import java.util.List;
34
35 /**
36 * Decodes a received {@link ByteBuf} into a
37 * <a href="https://github.com/google/protobuf">Google Protocol Buffers</a>
38 * {@link Message} and {@link MessageLite}. Please note that this decoder must
39 * be used with a proper {@link ByteToMessageDecoder} such as {@link ProtobufVarint32FrameDecoder}
40 * or {@link LengthFieldBasedFrameDecoder} if you are using a stream-based
41 * transport such as TCP/IP. A typical setup for TCP/IP would be:
42 * <pre>
43 * {@link ChannelPipeline} pipeline = ...;
44 *
45 * // Decoders
46 * pipeline.addLast("frameDecoder",
47 * new {@link LengthFieldBasedFrameDecoder}(1048576, 0, 4, 0, 4));
48 * pipeline.addLast("protobufDecoder",
49 * new {@link ProtobufDecoder}(MyMessage.getDefaultInstance()));
50 *
51 * // Encoder
52 * pipeline.addLast("frameEncoder", new {@link LengthFieldPrepender}(4));
53 * pipeline.addLast("protobufEncoder", new {@link ProtobufEncoder}());
54 * </pre>
55 * and then you can use a {@code MyMessage} instead of a {@link ByteBuf}
56 * as a message:
57 * <pre>
58 * void channelRead({@link ChannelHandlerContext} ctx, Object msg) {
59 * MyMessage req = (MyMessage) msg;
60 * MyMessage res = MyMessage.newBuilder().setText(
61 * "Did you say '" + req.getText() + "'?").build();
62 * ch.write(res);
63 * }
64 * </pre>
65 */
66 @Sharable
67 public class ProtobufDecoder extends MessageToMessageDecoder<ByteBuf> {
68
69 private static final boolean HAS_PARSER;
70
71 static {
72 boolean hasParser = false;
73 try {
74 // MessageLite.getParserForType() is not available until protobuf 2.5.0.
75 MessageLite.class.getDeclaredMethod("getParserForType");
76 hasParser = true;
77 } catch (Throwable t) {
78 // Ignore
79 }
80
81 HAS_PARSER = hasParser;
82 }
83
84 private final MessageLite prototype;
85 private final ExtensionRegistryLite extensionRegistry;
86
87 /**
88 * Creates a new instance.
89 */
90 public ProtobufDecoder(MessageLite prototype) {
91 this(prototype, null);
92 }
93
94 public ProtobufDecoder(MessageLite prototype, ExtensionRegistry extensionRegistry) {
95 this(prototype, (ExtensionRegistryLite) extensionRegistry);
96 }
97
98 public ProtobufDecoder(MessageLite prototype, ExtensionRegistryLite extensionRegistry) {
99 this.prototype = ObjectUtil.checkNotNull(prototype, "prototype").getDefaultInstanceForType();
100 this.extensionRegistry = extensionRegistry;
101 }
102
103 @Override
104 protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out)
105 throws Exception {
106 final byte[] array;
107 final int offset;
108 final int length = msg.readableBytes();
109 if (msg.hasArray()) {
110 array = msg.array();
111 offset = msg.arrayOffset() + msg.readerIndex();
112 } else {
113 array = ByteBufUtil.getBytes(msg, msg.readerIndex(), length, false);
114 offset = 0;
115 }
116
117 if (extensionRegistry == null) {
118 if (HAS_PARSER) {
119 out.add(prototype.getParserForType().parseFrom(array, offset, length));
120 } else {
121 out.add(prototype.newBuilderForType().mergeFrom(array, offset, length).build());
122 }
123 } else {
124 if (HAS_PARSER) {
125 out.add(prototype.getParserForType().parseFrom(
126 array, offset, length, extensionRegistry));
127 } else {
128 out.add(prototype.newBuilderForType().mergeFrom(
129 array, offset, length, extensionRegistry).build());
130 }
131 }
132 }
133 }