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 }