Coverage Summary for Class: MessageCodec (org.ethereum.net.rlpx)
Class |
Class, %
|
Method, %
|
Line, %
|
MessageCodec |
0%
(0/1)
|
0%
(0/15)
|
0%
(0/99)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 RSK Labs Ltd.
4 * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package org.ethereum.net.rlpx;
21
22 import com.google.common.io.ByteStreams;
23 import io.netty.channel.ChannelHandlerContext;
24 import io.netty.handler.codec.MessageToMessageCodec;
25 import org.apache.commons.lang3.tuple.Pair;
26 import org.ethereum.config.SystemProperties;
27 import org.ethereum.listener.EthereumListener;
28 import org.ethereum.net.client.Capability;
29 import org.ethereum.net.eth.EthVersion;
30 import org.ethereum.net.eth.message.Eth62MessageFactory;
31 import org.ethereum.net.eth.message.EthMessageCodes;
32 import org.ethereum.net.message.Message;
33 import org.ethereum.net.p2p.P2pMessageCodes;
34 import org.ethereum.net.p2p.P2pMessageFactory;
35 import org.ethereum.net.server.Channel;
36 import org.ethereum.util.ByteUtil;
37 import org.ethereum.util.LRUMap;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import java.io.IOException;
42 import java.util.*;
43 import java.util.concurrent.atomic.AtomicInteger;
44
45 import static java.lang.Math.min;
46 import static org.ethereum.net.rlpx.FrameCodec.Frame;
47
48 /**
49 * The Netty codec which encodes/decodes RPLx frames to subprotocol Messages
50 */
51 public class MessageCodec extends MessageToMessageCodec<Frame, Message> {
52
53 private static final Logger loggerWire = LoggerFactory.getLogger("wire");
54 private static final Logger loggerNet = LoggerFactory.getLogger("net");
55
56 public static final int NO_FRAMING = Integer.MAX_VALUE >> 1;
57
58 private int maxFramePayloadSize = NO_FRAMING;
59
60 private Channel channel;
61 private MessageCodesResolver messageCodesResolver;
62
63 private P2pMessageFactory p2pMessageFactory;
64 private Eth62MessageFactory ethMessageFactory;
65 private EthVersion ethVersion;
66
67 private final EthereumListener ethereumListener;
68
69 private boolean supportChunkedFrames = true;
70
71 Map<Integer, Pair<? extends List<Frame>, AtomicInteger>> incompleteFrames = new LRUMap<>(1, 16);
72 // LRU avoids OOM on invalid peers
73 AtomicInteger contextIdCounter = new AtomicInteger(1);
74
75 public MessageCodec(EthereumListener ethereumListener, SystemProperties config) {
76 this.ethereumListener = ethereumListener;
77 this.maxFramePayloadSize = config.rlpxMaxFrameSize();
78 }
79
80 @Override
81 protected void decode(ChannelHandlerContext ctx, Frame frame, List<Object> out) throws Exception {
82 if (frame.isChunked()) {
83 if (!supportChunkedFrames && frame.totalFrameSize > 0) {
84 throw new RuntimeException("Faming is not supported in this configuration.");
85 }
86
87 Pair<? extends List<Frame>, AtomicInteger> frameParts = incompleteFrames.get(frame.contextId);
88 if (frameParts == null) {
89 if (frame.totalFrameSize < 0) {
90 // TODO: refactor this logic (Cpp sends non-chunked frames with context-id)
91 Message message = decodeMessage(Collections.singletonList(frame));
92 out.add(message);
93 return;
94 } else {
95 frameParts = Pair.of(new ArrayList<Frame>(), new AtomicInteger(0));
96 incompleteFrames.put(frame.contextId, frameParts);
97 }
98 } else {
99 if (frame.totalFrameSize >= 0) {
100 loggerNet.warn("Non-initial chunked frame shouldn't contain totalFrameSize field (context-id: {}, totalFrameSize: {}). Discarding this frame and all previous.", frame.contextId, frame.totalFrameSize);
101 incompleteFrames.remove(frame.contextId);
102 return;
103 }
104 }
105
106 frameParts.getLeft().add(frame);
107 int curSize = frameParts.getRight().addAndGet(frame.size);
108
109 if (loggerWire.isDebugEnabled()) {
110 loggerWire.debug("Recv: Chunked ({} of {}) [size: {}]", curSize, frameParts.getLeft().get(0).totalFrameSize, frame.getSize());
111 }
112
113 if (curSize > frameParts.getLeft().get(0).totalFrameSize) {
114 loggerNet.warn("The total frame chunks size ({}) is greater than expected ({}). Discarding the frame.", curSize, frameParts.getLeft().get(0).totalFrameSize);
115 incompleteFrames.remove(frame.contextId);
116 return;
117 }
118 if (curSize == frameParts.getLeft().get(0).totalFrameSize) {
119 Message message = decodeMessage(frameParts.getLeft());
120 incompleteFrames.remove(frame.contextId);
121 out.add(message);
122 }
123 } else {
124 Message message = decodeMessage(Collections.singletonList(frame));
125 out.add(message);
126 }
127 }
128
129 private Message decodeMessage(List<Frame> frames) throws IOException {
130 long frameType = frames.get(0).getType();
131
132 byte[] payload = new byte[frames.size() == 1 ? frames.get(0).getSize() : frames.get(0).totalFrameSize];
133 int pos = 0;
134 for (Frame frame : frames) {
135 pos += ByteStreams.read(frame.getStream(), payload, pos, frame.getSize());
136 }
137
138 if (loggerWire.isDebugEnabled()) {
139 loggerWire.debug("Recv: Encoded: {} [{}]", frameType, ByteUtil.toHexString(payload));
140 }
141
142 Message msg = createMessage((byte) frameType, payload);
143
144 loggerNet.trace("From: \t{} \tRecv: \t{}", channel, msg);
145
146 ethereumListener.onRecvMessage(channel, msg);
147
148 channel.getNodeStatistics().rlpxInMessages.add();
149 return msg;
150 }
151
152 @Override
153 protected void encode(ChannelHandlerContext ctx, Message msg, List<Object> out) throws Exception {
154 String output = String.format("To: \t%s \tSend: \t%s", ctx.channel().remoteAddress(), msg);
155 ethereumListener.trace(output);
156
157 loggerNet.trace("To: \t{} \tSend: \t{}", channel, msg);
158
159 byte[] encoded = msg.getEncoded();
160
161 if (loggerWire.isDebugEnabled()) {
162 loggerWire.debug("Send: Encoded: {} [{}]", getCode(msg.getCommand()), ByteUtil.toHexString(encoded));
163 }
164
165 List<Frame> frames = splitMessageToFrames(msg);
166
167 out.addAll(frames);
168
169 channel.getNodeStatistics().rlpxOutMessages.add();
170 }
171
172 private List<Frame> splitMessageToFrames(Message msg) {
173 byte code = getCode(msg.getCommand());
174 List<Frame> ret = new ArrayList<>();
175 byte[] bytes = msg.getEncoded();
176 int curPos = 0;
177 while(curPos < bytes.length) {
178 int newPos = min(curPos + maxFramePayloadSize, bytes.length);
179 byte[] frameBytes = curPos == 0 && newPos == bytes.length ? bytes :
180 Arrays.copyOfRange(bytes, curPos, newPos);
181 ret.add(new Frame(code, frameBytes));
182 curPos = newPos;
183 }
184
185 if (ret.size() > 1) {
186 // frame has been split
187 int contextId = contextIdCounter.getAndIncrement();
188 ret.get(0).totalFrameSize = bytes.length;
189 loggerWire.debug("Message (size {}) split to {} frames. Context-id: {}", bytes.length ,ret.size(), contextId);
190 for (Frame frame : ret) {
191 frame.contextId = contextId;
192 }
193 }
194 return ret;
195 }
196
197 public void setSupportChunkedFrames(boolean supportChunkedFrames) {
198 this.supportChunkedFrames = supportChunkedFrames;
199 if (!supportChunkedFrames) {
200 setMaxFramePayloadSize(NO_FRAMING);
201 }
202 }
203
204 private byte getCode(Enum msgCommand){
205 byte code = 0;
206
207 if (msgCommand instanceof P2pMessageCodes){
208 code = messageCodesResolver.withP2pOffset(((P2pMessageCodes) msgCommand).asByte());
209 }
210
211 if (msgCommand instanceof EthMessageCodes){
212 code = messageCodesResolver.withEthOffset(((EthMessageCodes) msgCommand).asByte());
213 }
214
215 return code;
216 }
217
218 private Message createMessage(byte code, byte[] payload) {
219
220 byte resolved = messageCodesResolver.resolveP2p(code);
221 if (p2pMessageFactory != null && P2pMessageCodes.inRange(resolved)) {
222 return p2pMessageFactory.create(resolved, payload);
223 }
224
225 resolved = messageCodesResolver.resolveEth(code);
226 if (ethMessageFactory != null && EthMessageCodes.inRange(resolved, ethVersion)) {
227 return ethMessageFactory.create(resolved, payload);
228 }
229
230 throw new IllegalArgumentException("No such message: " + code + " [" + ByteUtil.toHexString(payload) + "]");
231 }
232
233 public void setChannel(Channel channel){
234 this.channel = channel;
235 }
236
237 public void setEthVersion(EthVersion ethVersion) {
238 this.ethVersion = ethVersion;
239 }
240
241 public void setMaxFramePayloadSize(int maxFramePayloadSize) {
242 this.maxFramePayloadSize = maxFramePayloadSize;
243 }
244
245 public void initMessageCodes(List<Capability> caps) {
246 this.messageCodesResolver = new MessageCodesResolver(caps);
247 }
248
249 public void setP2pMessageFactory(P2pMessageFactory p2pMessageFactory) {
250 this.p2pMessageFactory = p2pMessageFactory;
251 }
252
253 public void setEthMessageFactory(Eth62MessageFactory ethMessageFactory) {
254 this.ethMessageFactory = ethMessageFactory;
255 }
256
257 }