Coverage Summary for Class: FrameCodec (org.ethereum.net.rlpx)

Class Method, % Line, %
FrameCodec 0% (0/8) 0% (0/113)
FrameCodec$Frame 0% (0/6) 0% (0/14)
Total 0% (0/14) 0% (0/127)


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 io.netty.buffer.ByteBuf; 23 import io.netty.buffer.ByteBufInputStream; 24 import io.netty.buffer.ByteBufOutputStream; 25 import org.bouncycastle.crypto.StreamCipher; 26 import org.bouncycastle.crypto.digests.KeccakDigest; 27 import org.bouncycastle.crypto.engines.AESEngine; 28 import org.bouncycastle.crypto.modes.SICBlockCipher; 29 import org.bouncycastle.crypto.params.KeyParameter; 30 import org.bouncycastle.crypto.params.ParametersWithIV; 31 import org.ethereum.util.RLP; 32  33 import java.io.*; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.List; 37  38 import static org.ethereum.util.RLP.decode2OneItem; 39  40 /** 41  * Created by devrandom on 2015-04-11. 42  */ 43 public class FrameCodec { 44  private final StreamCipher enc; 45  private final StreamCipher dec; 46  private final KeccakDigest egressMac; 47  private final KeccakDigest ingressMac; 48  private final byte[] mac; 49  boolean isHeadRead; 50  private int totalBodySize; 51  private int contextId = -1; 52  private int totalFrameSize = -1; 53  54  public FrameCodec(EncryptionHandshake.Secrets secrets) { 55  this.mac = secrets.mac; 56  AESEngine encCipher = new AESEngine(); 57  enc = new SICBlockCipher(encCipher); 58  enc.init(true, new ParametersWithIV(new KeyParameter(secrets.aes), new byte[encCipher.getBlockSize()])); 59  AESEngine decCipher = new AESEngine(); 60  dec = new SICBlockCipher(decCipher); 61  dec.init(false, new ParametersWithIV(new KeyParameter(secrets.aes), new byte[decCipher.getBlockSize()])); 62  egressMac = secrets.egressMac; 63  ingressMac = secrets.ingressMac; 64  } 65  66  private AESEngine makeMacCipher() { 67  // Stateless AES encryption 68  AESEngine macc = new AESEngine(); 69  macc.init(true, new KeyParameter(mac)); 70  return macc; 71  } 72  73  public static class Frame { 74  long type; 75  int size; 76  InputStream payload; 77  78  int totalFrameSize = -1; 79  int contextId = -1; 80  81  82  public Frame(long type, int size, InputStream payload) { 83  this.type = type; 84  this.size = size; 85  this.payload = payload; 86  } 87  88  public Frame(int type, byte[] payload) { 89  this.type = type; 90  this.size = payload.length; 91  this.payload = new ByteArrayInputStream(payload); 92  } 93  94  public int getSize() { 95  return size; 96  } 97  98  public long getType() {return type;} 99  100  public InputStream getStream() { 101  return payload; 102  } 103  public boolean isChunked() { 104  return contextId >= 0; 105  } 106  107  } 108  109  public void writeFrame(Frame frame, ByteBuf buf) throws IOException { 110  writeFrame(frame, new ByteBufOutputStream(buf)); 111  } 112  113  public void writeFrame(Frame frame, OutputStream out) throws IOException { 114  byte[] headBuffer = new byte[32]; 115  byte[] ptype = RLP.encodeInt((int) frame.type); // FIXME encodeLong 116  int totalSize = frame.size + ptype.length; 117  headBuffer[0] = (byte)(totalSize >> 16); 118  headBuffer[1] = (byte)(totalSize >> 8); 119  headBuffer[2] = (byte)(totalSize); 120  121  List<byte[]> headerDataElems = new ArrayList<>(); 122  headerDataElems.add(RLP.encodeInt(0)); 123  if (frame.contextId >= 0) { 124  headerDataElems.add(RLP.encodeInt(frame.contextId)); 125  } 126  if (frame.totalFrameSize >= 0) { 127  headerDataElems.add(RLP.encodeInt(frame.totalFrameSize)); 128  } 129  130  byte[] headerData = RLP.encodeList(headerDataElems.toArray(new byte[0][])); 131  System.arraycopy(headerData, 0, headBuffer, 3, headerData.length); 132  133  enc.processBytes(headBuffer, 0, 16, headBuffer, 0); 134  135  // Header MAC 136  updateMac(egressMac, headBuffer, 0, headBuffer, 16, true); 137  138  byte[] buff = new byte[256]; 139  out.write(headBuffer); 140  enc.processBytes(ptype, 0, ptype.length, buff, 0); 141  out.write(buff, 0, ptype.length); 142  egressMac.update(buff, 0, ptype.length); 143  while (true) { 144  int n = frame.payload.read(buff); 145  if (n <= 0) { 146  break; 147  } 148  enc.processBytes(buff, 0, n, buff, 0); 149  egressMac.update(buff, 0, n); 150  out.write(buff, 0, n); 151  } 152  int padding = 16 - (totalSize % 16); 153  byte[] pad = new byte[16]; 154  if (padding < 16) { 155  enc.processBytes(pad, 0, padding, buff, 0); 156  egressMac.update(buff, 0, padding); 157  out.write(buff, 0, padding); 158  } 159  160  // Frame MAC 161  byte[] macBuffer = new byte[egressMac.getDigestSize()]; 162  doSum(egressMac, macBuffer); // fmacseed 163  updateMac(egressMac, macBuffer, 0, macBuffer, 0, true); 164  out.write(macBuffer, 0, 16); 165  } 166  167  public List<Frame> readFrames(ByteBuf buf) throws IOException { 168  return readFrames(new ByteBufInputStream(buf)); 169  } 170  171  public List<Frame> readFrames(DataInput inp) throws IOException { 172  if (!isHeadRead) { 173  byte[] headBuffer = new byte[32]; 174  try { 175  inp.readFully(headBuffer); 176  } catch (EOFException e) { 177  return null; 178  } 179  180  // Header MAC 181  updateMac(ingressMac, headBuffer, 0, headBuffer, 16, false); 182  183  dec.processBytes(headBuffer, 0, 16, headBuffer, 0); 184  totalBodySize = headBuffer[0]; 185  totalBodySize = (totalBodySize << 8) + (headBuffer[1] & 0xFF); 186  totalBodySize = (totalBodySize << 8) + (headBuffer[2] & 0xFF); 187  188  if (totalBodySize < 0) { 189  return null; 190  } 191  192  decode2OneItem(headBuffer, 3); 193  194  contextId = -1; 195  totalFrameSize = -1; 196  isHeadRead = true; 197  } 198  199  int padding = 16 - (totalBodySize % 16); 200  if (padding == 16) { 201  padding = 0; 202  } 203  int macSize = 16; 204  byte[] buffer = new byte[totalBodySize + padding + macSize]; 205  try { 206  inp.readFully(buffer); 207  } catch (EOFException e) { 208  return null; 209  } 210  int frameSize = buffer.length - macSize; 211  ingressMac.update(buffer, 0, frameSize); 212  dec.processBytes(buffer, 0, frameSize, buffer, 0); 213  int pos = 0; 214  long type = RLP.decodeInt(buffer, pos); // FIXME long 215  pos = RLP.getNextElementIndex(buffer, pos); 216  InputStream payload = new ByteArrayInputStream(buffer, pos, totalBodySize - pos); 217  int size = totalBodySize - pos; 218  byte[] macBuffer = new byte[ingressMac.getDigestSize()]; 219  220  // Frame MAC 221  doSum(ingressMac, macBuffer); // fmacseed 222  updateMac(ingressMac, macBuffer, 0, buffer, frameSize, false); 223  224  isHeadRead = false; 225  Frame frame = new Frame(type, size, payload); 226  frame.contextId = contextId; 227  frame.totalFrameSize = totalFrameSize; 228  return Collections.singletonList(frame); 229  } 230  231  private byte[] updateMac(KeccakDigest mac, byte[] seed, int offset, byte[] out, int outOffset, boolean egress) throws IOException { 232  byte[] aesBlock = new byte[mac.getDigestSize()]; 233  doSum(mac, aesBlock); 234  makeMacCipher().processBlock(aesBlock, 0, aesBlock, 0); 235  // Note that although the mac digest size is 32 bytes, we only use 16 bytes in the computation 236  int length = 16; 237  for (int i = 0; i < length; i++) { 238  aesBlock[i] ^= seed[i + offset]; 239  } 240  mac.update(aesBlock, 0, length); 241  byte[] result = new byte[mac.getDigestSize()]; 242  doSum(mac, result); 243  if (egress) { 244  System.arraycopy(result, 0, out, outOffset, length); 245  } else { 246  for (int i = 0; i < length; i++) { 247  if (out[i + outOffset] != result[i]) { 248  throw new IOException("MAC mismatch"); 249  } 250  } 251  } 252  return result; 253  } 254  255  private void doSum(KeccakDigest mac, byte[] out) { 256  // doFinal without resetting the MAC by using clone of digest state 257  new KeccakDigest(mac).doFinal(out, 0); 258  } 259  260 }