Merge RLP classes and tests but needs more scrutiny

This commit is contained in:
nicksavers 2014-05-14 10:47:45 +02:00
parent f50703f69c
commit 7eaf9f0dba
25 changed files with 723 additions and 1094 deletions

View File

@ -1,9 +1,9 @@
package org.ethereum.net.vo;
package org.ethereum.core;
import org.ethereum.crypto.HashUtil;
import org.ethereum.net.rlp.RLPElement;
import org.ethereum.net.rlp.RLPItem;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.util.RLPElement;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import org.ethereum.util.Utils;
import java.math.BigInteger;

View File

@ -1,10 +1,10 @@
package org.ethereum.net.vo;
package org.ethereum.core;
import org.ethereum.crypto.ECKey.ECDSASignature;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.ethereum.net.rlp.RLPItem;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import org.ethereum.util.Utils;
/**
@ -89,7 +89,7 @@ public class Transaction {
byte[] r = ((RLPItem) rawData.getElement(8)).getData();
byte[] s = ((RLPItem) rawData.getElement(9)).getData();
this.signature = ECDSASignature.fromComponents(r, s, v);
} else throw new Error("Wrong tx data element list size");
} else throw new RuntimeException("Wrong tx data element list size");
this.parsed = true;
}

View File

@ -1,144 +0,0 @@
package org.ethereum.crypto.vm;
/**
* Instruction set for the Ethereum Virtual Machine
*/
public enum OpCode {
/**
* Stop and Arithmetic Operations
*/
STOP(0x00),
ADD(0x01),
MUL(0x02),
SUB(0x03),
DIV(0x04),
SDIV(0x05),
MOD(0x06),
SMOD(0x07),
EXP(0x08),
NEG(0x09),
LT(0x0a),
GT(0x0b),
EQ(0x0c),
NOT(0x0d),
/**
* Bitwise Logic Operations
*/
AND(0x10),
OR(0x11),
XOR(0x12),
BYTE(0x13),
/**
* SHA3
*/
SHA3(0x20),
/**
* Environmental Information
*/
ADDRESS(0x30),
BALANCE(0x31),
ORIGIN(0x32),
CALLER(0x33),
CALLVALUE(0x34),
CALLDATALOAD(0x35),
CALLDATASIZE(0x36),
CALLDATACOPY(0x37),
CODESIZE(0x38),
CODECOPY(0x39),
GASPRICE(0x3a),
/**
* Block Information
*/
PREVHASH(0x40),
COINBASE(0x41),
TIMESTAMP(0x42),
NUMBER(0x43),
DIFFICULTY(0x44),
GASLIMIT(0x45),
/**
* Memory, Storage and Flow Operations
*/
POP(0x50),
DUP(0x51),
SWAP(0x52),
MLOAD(0x53),
MSTORE(0x54),
MSTORE8(0x55),
SLOAD(0x56),
SSTORE(0x57),
JUMP(0x58),
JUMPI(0x59),
PC(0x5a),
MSIZE(0x5b),
GAS(0x5c),
/**
* Push Operations
*/
PUSH1(0x60),
PUSH2(0x61),
PUSH3(0x62),
PUSH4(0x63),
PUSH5(0x64),
PUSH6(0x65),
PUSH7(0x66),
PUSH8(0x67),
PUSH9(0x68),
PUSH10(0x69),
PUSH11(0x6a),
PUSH12(0x6b),
PUSH13(0x6c),
PUSH14(0x6d),
PUSH15(0x6e),
PUSH16(0x6f),
PUSH17(0x70),
PUSH18(0x71),
PUSH19(0x72),
PUSH20(0x73),
PUSH21(0x74),
PUSH22(0x75),
PUSH23(0x76),
PUSH24(0x77),
PUSH25(0x78),
PUSH26(0x79),
PUSH27(0x7a),
PUSH28(0x7b),
PUSH29(0x7c),
PUSH30(0x7d),
PUSH31(0x7e),
PUSH32(0x7f),
/**
* System operations
*/
CREATE(0xf0),
CALL(0xf1),
RETURN(0xf2),
SUICIDE(0xff);
private int opcode;
private OpCode(int opcode) {
this.opcode = opcode;
}
public int getOpCode() {
return this.opcode;
}
}

View File

@ -2,10 +2,10 @@ package org.ethereum.manager;
import com.maxmind.geoip.Location;
import org.ethereum.core.Block;
import org.ethereum.core.Transaction;
import org.ethereum.geodb.IpGeoDB;
import org.ethereum.net.vo.Block;
import org.ethereum.net.vo.PeerData;
import org.ethereum.net.vo.Transaction;
import org.ethereum.net.client.PeerData;
import java.util.*;

View File

@ -1,16 +1,6 @@
package org.ethereum.net.client;
import static org.ethereum.net.Command.*;
import static org.ethereum.net.Command.DISCONNECT;
import static org.ethereum.net.Command.GET_CHAIN;
import static org.ethereum.net.Command.GET_PEERS;
import static org.ethereum.net.Command.GET_TRANSACTIONS;
import static org.ethereum.net.Command.HELLO;
import static org.ethereum.net.Command.NOT_IN_CHAIN;
import static org.ethereum.net.Command.PEERS;
import static org.ethereum.net.Command.PING;
import static org.ethereum.net.Command.PONG;
import static org.ethereum.net.Command.TRANSACTIONS;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@ -21,6 +11,7 @@ import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.ethereum.core.Block;
import org.ethereum.gui.PeerListener;
import org.ethereum.manager.MainData;
import org.ethereum.net.Command;
@ -33,9 +24,8 @@ import org.ethereum.net.message.NotInChainMessage;
import org.ethereum.net.message.PeersMessage;
import org.ethereum.net.message.StaticMessages;
import org.ethereum.net.message.TransactionsMessage;
import org.ethereum.net.rlp.RLP;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.net.vo.Block;
import org.ethereum.util.RLP;
import org.ethereum.util.RLPList;
import org.ethereum.util.Utils;
import org.spongycastle.util.encoders.Hex;
@ -140,8 +130,7 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
// got HELLO
if (Command.fromInt(command) == HELLO) {
System.out.println("[Recv: HELLO]" );
RLPList rlpList = new RLPList();
RLP.parseObjects(payload, rlpList);
RLPList rlpList = RLP.decode2(payload);
HelloMessage helloMessage = new HelloMessage(rlpList);
System.out.println(helloMessage.toString());
@ -152,8 +141,7 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
System.out.println("[Recv: DISCONNECT]");
if (peerListener != null) peerListener.console("[Recv: DISCONNECT]");
RLPList rlpList = new RLPList();
RLP.parseObjects(payload, rlpList);
RLPList rlpList = RLP.decode2(payload);
DisconnectMessage disconnectMessage = new DisconnectMessage(rlpList);
System.out.println(disconnectMessage);
@ -198,8 +186,7 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
System.out.println("[Recv: PEERS]");
if (peerListener != null) peerListener.console("[Recv: PEERS]");
RLPList rlpList = new RLPList();
RLP.parseObjects(payload, rlpList);
RLPList rlpList = RLP.decode2(payload);
PeersMessage peersMessage = new PeersMessage(rlpList);
MainData.instance.addPeers(peersMessage.getPeers());
@ -212,8 +199,7 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
System.out.println("Recv: TRANSACTIONS]");
if (peerListener != null) peerListener.console("Recv: TRANSACTIONS]");
RLPList rlpList = new RLPList();
RLP.parseObjects(payload, rlpList);
RLPList rlpList = RLP.decode2(payload);
TransactionsMessage transactionsMessage = new TransactionsMessage(rlpList);
MainData.instance.addTransactions(transactionsMessage.getTransactions());
@ -226,8 +212,7 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
System.out.println("[Recv: BLOCKS]");
if (peerListener != null) peerListener.console("[Recv: BLOCKS]");
RLPList rlpList = new RLPList();
RLP.parseObjects(payload, rlpList);
RLPList rlpList = RLP.decode2(payload);
BlocksMessage blocksMessage = new BlocksMessage(rlpList);
List<Block> blockList = blocksMessage.getBlockDataList();
@ -241,8 +226,7 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
System.out.println("[Recv: GET_CHAIN]");
if (peerListener != null) peerListener.console("[Recv: GET_CHAIN]");
RLPList rlpList = new RLPList();
RLP.parseObjects(payload, rlpList);
RLPList rlpList = RLP.decode2(payload);
GetChainMessage getChainMessage = new GetChainMessage(rlpList);
System.out.println(getChainMessage);
@ -253,8 +237,7 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
System.out.println("[Recv: NOT_IN_CHAIN]");
if (peerListener != null) peerListener.console("[Recv: NOT_IN_CHAIN]");
RLPList rlpList = new RLPList();
RLP.parseObjects(payload, rlpList);
RLPList rlpList = RLP.decode2(payload);
NotInChainMessage notInChainMessage = new NotInChainMessage(rlpList);
System.out.println(notInChainMessage);
@ -287,7 +270,6 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
private void sendMsg(Message msg, ChannelHandlerContext ctx){
byte[] data = msg.getPayload();
final ByteBuf buffer = ctx.alloc().buffer(data.length + 8);
byte[] packetLen = calcPacketLength(data);

View File

@ -1,4 +1,4 @@
package org.ethereum.net.vo;
package org.ethereum.net.client;
import org.spongycastle.util.encoders.Hex;

View File

@ -5,11 +5,11 @@ import java.util.List;
import static org.ethereum.net.Command.BLOCKS;
import org.ethereum.core.Block;
import org.ethereum.core.Transaction;
import org.ethereum.net.Command;
import org.ethereum.net.rlp.RLPItem;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.net.vo.Block;
import org.ethereum.net.vo.Transaction;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
/**
* www.ethereumJ.com
@ -28,12 +28,12 @@ public class BlocksMessage extends Message {
RLPList paramsList = (RLPList) rawData.getElement(0);
if ( Command.fromInt(((RLPItem)(paramsList).getElement(0)).getData()[0]) != BLOCKS){
if (Command.fromInt(((RLPItem) (paramsList).getElement(0)).getData()[0]) != BLOCKS) {
throw new Error("BlocksMessage: parsing for mal data");
}
for (int i = 1; i < paramsList.size(); ++i){
RLPList rlpData = ((RLPList)paramsList.getElement(i));
for (int i = 1; i < paramsList.size(); ++i) {
RLPList rlpData = ((RLPList) paramsList.getElement(i));
Block blockData = new Block(rlpData);
this.blockDataList.add(blockData);
}
@ -46,23 +46,22 @@ public class BlocksMessage extends Message {
}
public List<Block> getBlockDataList() {
if (!parsed) parseRLP();
if (!parsed)
parseRLP();
return blockDataList;
}
public String toString() {
StringBuffer sb = new StringBuffer();
for (Block blockData : this.getBlockDataList()){
sb.append(" ").append( blockData.toString() ).append("\n");
for (Block blockData : this.getBlockDataList()) {
sb.append(" ").append(blockData.toString()).append("\n");
List<Transaction> transactions = blockData.getTransactionsList();
for (Transaction transactionData : transactions){
for (Transaction transactionData : transactions) {
sb.append("[").append(transactionData).append("]\n");
}
}
return "Blocks Message [\n" +
sb.toString()
+ " ]";
return "Blocks Message [\n" + sb.toString() + " ]";
}
}

View File

@ -1,8 +1,9 @@
package org.ethereum.net.message;
import org.ethereum.net.rlp.RLPItem;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.net.Command;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import static org.ethereum.net.Command.DISCONNECT;
import static org.ethereum.net.message.ReasonCode.DISCONNECT_REQUESTED;

View File

@ -5,9 +5,10 @@ import java.util.ArrayList;
import java.util.List;
import static org.ethereum.net.Command.GET_CHAIN;
import org.ethereum.net.Command;
import org.ethereum.net.rlp.RLPItem;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import org.ethereum.util.Utils;
/**

View File

@ -4,9 +4,9 @@ import org.spongycastle.util.encoders.Hex;
import static org.ethereum.net.Command.HELLO;
import org.ethereum.net.rlp.RLP;
import org.ethereum.net.rlp.RLPItem;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.util.RLP;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import java.nio.ByteBuffer;

View File

@ -1,6 +1,6 @@
package org.ethereum.net.message;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.util.RLPList;
/**
* www.ethereumJ.com

View File

@ -4,8 +4,8 @@ import static org.ethereum.net.Command.NOT_IN_CHAIN;
import org.ethereum.net.Command;
import org.ethereum.net.message.Message;
import org.ethereum.net.rlp.RLPItem;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import org.ethereum.util.Utils;
/**

View File

@ -5,10 +5,11 @@ import java.util.ArrayList;
import java.util.List;
import static org.ethereum.net.Command.PEERS;
import org.ethereum.net.Command;
import org.ethereum.net.rlp.RLPItem;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.net.vo.PeerData;
import org.ethereum.net.client.PeerData;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
/**
* www.ethereumJ.com

View File

@ -1,10 +1,11 @@
package org.ethereum.net.message;
import static org.ethereum.net.Command.TRANSACTIONS;
import org.ethereum.core.Transaction;
import org.ethereum.net.Command;
import org.ethereum.net.rlp.RLPItem;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.net.vo.Transaction;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import java.util.ArrayList;
import java.util.List;

View File

@ -1,10 +0,0 @@
package org.ethereum.net.rlp;
/**
* www.ethereumJ.com
* User: Roman Mandeleil
* Created on: 21/04/14 16:28
*/
public interface RLPElement {
}

View File

@ -1,11 +1,20 @@
package org.ethereum.net.rlp;
package org.ethereum.util;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import static java.util.Arrays.copyOfRange;
import static org.spongycastle.util.Arrays.concatenate;
import java.util.List;
import org.ethereum.util.RLP;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
/**
* Recursive Length Prefix (RLP) encoding.
*
@ -34,6 +43,9 @@ import java.util.Queue;
*/
public class RLP {
/** Allow for content up to size of 2^64 bytes **/
private static double MAX_ITEM_LENGTH = Math.pow(256, 8);
/**
* Reason for threshold according to Vitalik Buterin:
* - 56 bytes maximizes the benefit of both options
@ -45,6 +57,13 @@ public class RLP {
**/
private static int SIZE_THRESHOLD = 56;
/** RLP encoding rules are defined as follows: */
/*
* For a single byte whose value is in the [0x00, 0x7f] range, that byte is
* its own RLP encoding.
*/
/*
* If a string is 0-55 bytes long, the RLP encoding consists of a single
* byte with value 0x80 plus the length of the string followed by the
@ -52,6 +71,16 @@ public class RLP {
*/
private static int OFFSET_SHORT_ITEM = 0x80;
/*
* If a string is more than 55 bytes long, the RLP encoding consists of a
* single byte with value 0xb7 plus the length of the length of the string
* in binary form, followed by the length of the string, followed by the
* string. For example, a length-1024 string would be encoded as
* \xb9\x04\x00 followed by the string. The range of the first byte is thus
* [0xb8, 0xbf].
*/
private static int OFFSET_LONG_ITEM = 0xb8;
/*
* If the total payload of a list (i.e. the combined length of all its
* items) is 0-55 bytes long, the RLP encoding consists of a single byte
@ -61,8 +90,21 @@ public class RLP {
*/
private static int OFFSET_SHORT_LIST = 0xc0;
private static byte decodeOneByteItem(byte[] data, int index) {
/*
* If the total payload of a list is more than 55 bytes long, the RLP
* encoding consists of a single byte with value 0xf7 plus the length of the
* length of the list in binary form, followed by the length of the list,
* followed by the concatenation of the RLP encodings of the items. The
* range of the first byte is thus [0xf8, 0xff].
*/
private static int OFFSET_LONG_LIST = 0xf8;
/* ******************************************************
* DECODING *
* ******************************************************/
private static byte decodeOneByteItem(byte[] data, int index) {
// null item
if ((data[index] & 0xFF) == OFFSET_SHORT_ITEM) {
return (byte) (data[index] - OFFSET_SHORT_ITEM);
@ -91,7 +133,7 @@ public class RLP {
pow--;
}
} else {
throw new Error("wrong decode attempt");
throw new RuntimeException("wrong decode attempt");
}
return value;
}
@ -122,7 +164,7 @@ public class RLP {
pow--;
}
} else {
throw new Error("wrong decode attempt");
throw new RuntimeException("wrong decode attempt");
}
return value;
}
@ -143,7 +185,7 @@ public class RLP {
value = new String(data, index + 1, length);
} else {
throw new Error("wrong decode attempt");
throw new RuntimeException("wrong decode attempt");
}
return value;
}
@ -163,7 +205,7 @@ public class RLP {
length = (byte) (data[index] - OFFSET_SHORT_ITEM);
} else {
throw new Error("wrong decode attempt");
throw new RuntimeException("wrong decode attempt");
}
byte[] valueBytes = new byte[length];
System.arraycopy(data, index, valueBytes, 0, length);
@ -186,7 +228,7 @@ public class RLP {
length = (byte) (data[index] - OFFSET_SHORT_ITEM);
} else {
throw new Error("wrong decode attempt");
throw new RuntimeException("wrong decode attempt");
}
byte[] valueBytes = new byte[length];
System.arraycopy(data, index, valueBytes, 0, length);
@ -209,7 +251,7 @@ public class RLP {
length = (byte) (data[index] - OFFSET_SHORT_ITEM);
} else {
throw new Error("wrong decode attempt");
throw new RuntimeException("wrong decode attempt");
}
byte[] valueBytes = new byte[length];
System.arraycopy(data, index, valueBytes, 0, length);
@ -278,13 +320,8 @@ public class RLP {
offset = offset + 1;
byte dByte = decodeOneByteItem(data, index + offset);
byte[] ip = new byte[4];
ip[0] = aByte;
ip[1] = bByte;
ip[2] = cByte;
ip[3] = dByte;
return ip;
// return IP address
return new byte[] { aByte, bByte, cByte, dByte } ;
}
public static int getFirstListElement(byte[] payload, int pos) {
@ -293,22 +330,14 @@ public class RLP {
return -1;
if ((payload[pos] & 0xFF) >= 0xF7) {
byte lengthOfLength = (byte) (payload[pos] - 0xF7);
return pos + lengthOfLength + 1;
}
if ((payload[pos] & 0xFF) >= OFFSET_SHORT_LIST && (payload[pos] & 0xFF) < 0xF7) {
byte length = (byte) ((payload[pos] & 0xFF) - OFFSET_SHORT_LIST);
return pos + 1;
}
if ((payload[pos] & 0xFF) >= 0xB7 && (payload[pos] & 0xFF) < OFFSET_SHORT_LIST) {
byte lengthOfLength = (byte) (payload[pos] - 0xB7);
int length = calcLength(lengthOfLength, payload, pos);
return pos + lengthOfLength + 1;
}
@ -355,6 +384,7 @@ public class RLP {
*/
public static void fullTraverse(byte[] msgData, int level, int startPos,
int endPos, int levelToIndex, Queue<Integer> index) {
try {
if (msgData == null || msgData.length == 0)
@ -443,7 +473,7 @@ public class RLP {
}
}
} catch (Throwable th) {
throw new Error("wire packet not parsed correctly",
throw new RuntimeException("wire packet not parsed correctly",
th.fillInStackTrace());
}
}
@ -468,6 +498,14 @@ public class RLP {
return length;
}
public static byte getCommandCode(byte[] data) {
byte command = 0;
int index = getFirstListElement(data, 0);
command = data[index];
command = ((int) (command & 0xFF) == OFFSET_SHORT_ITEM) ? 0 : command;
return command;
}
/**
* Parse wire byte[] message into RLP elements
*
@ -476,8 +514,10 @@ public class RLP {
* @param rlpList
* - outcome of recursive RLP structure
*/
public static void parseObjects(byte[] msgData, RLPList rlpList) {
RLP.fullTraverse(msgData, 0, 0, msgData.length, 1, rlpList);
public static RLPList decode2(byte[] msgData) {
RLPList rlpList = new RLPList();
fullTraverse(msgData, 0, 0, msgData.length, 1, rlpList);
return rlpList;
}
/**
@ -604,114 +644,126 @@ public class RLP {
}
}
} catch (Throwable th) {
throw new Error("wire packet not parsed correctly",
throw new RuntimeException("wire packet not parsed correctly",
th.fillInStackTrace());
}
}
private static String rlpEncode(Object item) {
if (item instanceof String) {
String str = ((String) item);
int length = str.length();
if (length == 1 && str.charAt(0) < OFFSET_SHORT_ITEM)
return str;
else
return encodeLength(str.length(), OFFSET_SHORT_ITEM) + str;
} else if (item instanceof List) {
List itemList = (List) item;
StringBuilder output = new StringBuilder();
for (Object oneItem : itemList)
output.append(rlpEncode(oneItem));
return encodeLength(output.toString().length(), OFFSET_SHORT_LIST)
+ output.toString();
}
throw new Error("unsupported type" + item.getClass());
}
/** Integer limitation goes up to 2^31-1 so length can never be bigger */
private static String encodeLength(int L, int offset) {
if (L < SIZE_THRESHOLD)
return "" + (char) (L + offset);
else if (L < Math.pow(256, 8)) {
String BL = toBinary(L);
return "" + (char) (BL.length() + offset + SIZE_THRESHOLD - 1) + BL;
} else
throw new Error("input too long");
}
private static String toBinary(int x) {
if (x == 0)
return "";
else
return toBinary(x >> 8) + ((char) (x & 0x00FF));
}
public static byte getCommandCode(byte[] data) {
byte command = 0;
int index = getFirstListElement(data, 0);
command = data[index];
command = ((int) (command & 0xFF) == OFFSET_SHORT_ITEM) ? 0 : command;
return command;
}
private static Object decode(char[] data) {
if (data == null || data.length == 0)
/**
* Reads any RLP encoded byte-array and returns all objects as byte-array or list of byte-arrays
*
* @param data RLP encoded byte-array
* @param pos position in the array to start reading
* @return DecodeResult encapsulates the decoded items as a single Object and the final read position
*/
public static DecodeResult decode(byte[] data, int pos) {
if (data == null || data.length < 1) {
return null;
}
if (data[0] >= 0xF7) {
/*
* It's a list with a payload more than 55 bytes
* data[0] - 0xF7 = how many next bytes allocated for the length of the list
*/
byte lengthOfLength = (byte) (data[0] - 0xF7);
int prefix = data[pos] & 0xFF;
if (prefix == OFFSET_SHORT_ITEM) {
return new DecodeResult(pos+1, new byte[0]); // means no length or 0
} else if (prefix < OFFSET_SHORT_ITEM) {
return new DecodeResult(pos+1, new byte[] { data[pos] }); // byte is its own RLP encoding
} else if (prefix < OFFSET_LONG_ITEM){
int len = prefix - OFFSET_SHORT_ITEM; // length of the encoded bytes
return new DecodeResult(pos+1+len, copyOfRange(data, pos+1, pos+1+len));
} else if (prefix < OFFSET_SHORT_LIST) {
int lenlen = prefix - OFFSET_LONG_ITEM + 1; // length of length the encoded bytes
int lenbytes = toInt(copyOfRange(data, pos+1, pos+1+lenlen)); // length of encoded bytes
return new DecodeResult(pos+1+lenlen+lenbytes, copyOfRange(data, pos+1+lenlen, pos+1+lenlen+lenbytes));
} else if (prefix < OFFSET_LONG_LIST) {
int len = prefix - OFFSET_SHORT_LIST; // length of the encoded list
int prevPos = pos; pos++;
return decodeList(data, pos, prevPos, len);
} else if (prefix < 0xFF) {
int lenlen = prefix - OFFSET_LONG_LIST + 1; // length of length the encoded list
int lenlist = toInt(copyOfRange(data, pos+1, pos+1+lenlen)); // length of encoded bytes
pos = pos + lenlen + 1;
int prevPos = lenlist;
return decodeList(data, pos, prevPos, lenlist);
} else {
throw new RuntimeException("Only byte values between 0x00 and 0xFF are supported, but got: " + prefix);
}
}
byte pow = (byte) (lengthOfLength - 1);
long length = 0;
for (int i = 1; i <= lengthOfLength; ++i) {
length += data[i] << (8 * pow);
pow--;
private static DecodeResult decodeList(byte[] data, int pos, int prevPos, int len) {
List<Object> slice = new ArrayList<Object>();
for (int i = 0; i < len;) {
// Get the next item in the data list and append it
DecodeResult result = decode(data, pos);
slice.add(result.getDecoded());
// Increment pos by the amount bytes in the previous read
prevPos = result.getPos();
i += (prevPos - pos);
pos = prevPos;
}
System.out.println(length);
// now we can parse an item for data[1]..data[length]
return new DecodeResult(pos, slice.toArray());
}
if (data[0] >= OFFSET_SHORT_LIST && data[0] < 0xF7)
/*
* It's a list with a payload less than 55 bytes
*/
;
if (data[0] >= 0xB7 && data[0] < OFFSET_SHORT_LIST) {
/*
* It's an item with a payload more than 55 bytes
* data[0] - 0xB7 = how much next bytes allocated for the length of the string
*/
;
byte lengthOfLength = (byte) (data[0] - 0xB7);
byte pow = (byte) (lengthOfLength - 1);
long length = 0;
for (int i = 1; i <= lengthOfLength; ++i) {
length += data[i] << (8 * pow);
pow--;
}
System.out.println(length);
// now we can parse an item for data[1]..data[length]
}
if (data[0] >= OFFSET_SHORT_ITEM && data[0] < 0xB7) {
/*
* It's an item less than 55 bytes long,
* data[0] - OFFSET_SHORT_ITEM == length of the item
/**
* Cast hex encoded value from byte[] to int
*
* Limited to Integer.MAX_VALUE: 2^32-1
*
* @param b array contains the hex values
* @return int value of all hex values together.
*/
;
public static int toInt(byte[] b) {
if (b == null || b.length == 0) {
return 0;
}
return new BigInteger(b).intValue();
}
/* ******************************************************
* ENCODING *
* ******************************************************/
/**
* Turn Object into its RLP encoded equivalent of a byte-array
* Support for String, Integer, BigInteger and Lists of any of these types.
*
* @param item as object or List of objects
* @return byte[] RLP encoded
*/
public static byte[] encode(Object input) {
Value val = new Value(input);
if (val.isList()) {
List<Object> inputArray = val.asList();
if (inputArray.size() == 0) {
return encodeLength(inputArray.size(), OFFSET_SHORT_LIST);
}
byte[] output = new byte[0];
for (Object object : inputArray) {
output = concatenate(output, encode(object));
}
byte[] prefix = encodeLength(output.length, OFFSET_SHORT_LIST);
return concatenate(prefix, output);
} else {
byte[] inputAsBytes = toBytes(input);
if(inputAsBytes.length == 1) {
return inputAsBytes;
} else {
byte[] firstByte = encodeLength(inputAsBytes.length, OFFSET_SHORT_ITEM);
return concatenate(firstByte, inputAsBytes);
}
}
}
/** Integer limitation goes up to 2^31-1 so length can never be bigger than MAX_ITEM_LENGTH */
public static byte[] encodeLength(int length, int offset) {
if (length < SIZE_THRESHOLD) {
byte firstByte = (byte) (length + offset);
return new byte[] { firstByte };
} else if (length < MAX_ITEM_LENGTH) {
byte[] binaryLength = BigInteger.valueOf(length).toByteArray();
byte firstByte = (byte) (binaryLength.length + offset + SIZE_THRESHOLD - 1 );
return concatenate(new byte[] { firstByte }, binaryLength);
} else {
throw new RuntimeException("Input too long");
}
if (data[0] == OFFSET_SHORT_ITEM)
/* null item */
;
if (data[0] < OFFSET_SHORT_ITEM)
/* single byte item */
;
return null;
}
public static byte[] encodeByte(byte singleByte) {
@ -816,4 +868,26 @@ public class RLP {
}
return data;
}
/*
* Utility function to convert Objects into byte arrays
*/
private static byte[] toBytes(Object input) {
if (input instanceof byte[]) {
return (byte[]) input;
} else if (input instanceof String) {
String inputString = (String) input;
return inputString.getBytes();
} else if(input instanceof Integer) {
Integer inputInt = (Integer) input;
return (inputInt == 0) ? new byte[0] : BigInteger.valueOf(inputInt.longValue()).toByteArray();
} else if(input instanceof BigInteger) {
BigInteger inputBigInt = (BigInteger) input;
return (inputBigInt == BigInteger.ZERO) ? new byte[0] : inputBigInt.toByteArray();
} else if (input instanceof Value) {
Value val = (Value) input;
return toBytes(val.asObj());
}
throw new RuntimeException("Unsupported type: Only accepting String, Integer and BigInteger for now");
}
}

View File

@ -0,0 +1,8 @@
package org.ethereum.util;
/**
* Wrapper class for decoded elements from an RLP encoded byte array.
*/
public interface RLPElement {
}

View File

@ -1,4 +1,4 @@
package org.ethereum.net.rlp;
package org.ethereum.util;
/**
* www.ethereumJ.com

View File

@ -1,16 +1,14 @@
package org.ethereum.net.rlp;
package org.ethereum.util;
import java.util.ArrayList;
import java.util.List;
import org.ethereum.util.Utils;
/**
* www.ethereumJ.com
* User: Roman Mandeleil
* Created on: 21/04/14 16:26
*/
public class RLPList implements RLPElement{
public class RLPList implements RLPElement {
byte[] rlpData;
List<RLPElement> list;
@ -46,7 +44,7 @@ public class RLPList implements RLPElement{
public static void recursivePrint(RLPElement element) {
if (element == null)
throw new Error("RLPElement object can't be null");
throw new RuntimeException("RLPElement object can't be null");
if (element instanceof RLPList) {
RLPList rlpList = (RLPList) element;

View File

@ -1,213 +0,0 @@
package org.ethereum.util;
import java.math.BigInteger;
import java.util.ArrayList;
import static java.util.Arrays.copyOfRange;
import static org.spongycastle.util.Arrays.concatenate;
import java.util.List;
/**
* Recursive Length Prefix (RLP) encoding.
*
* The purpose of RLP is to encode arbitrarily nested arrays of binary data, and
* RLP is the main encoding method used to serialize objects in Ethereum. The
* only purpose of RLP is to encode structure; encoding specific atomic data
* types (eg. strings, ints, floats) is left up to higher-order protocols; in
* Ethereum the standard is that integers are represented in big endian binary
* form. If one wishes to use RLP to encode a dictionary, the two suggested
* canonical forms are to either use [[k1,v1],[k2,v2]...] with keys in
* lexicographic order or to use the higher-level Patricia Tree encoding as
* Ethereum does.
*
* The RLP encoding function takes in an item. An item is defined as follows:
*
* - A string (ie. byte array) is an item - A list of items is an item
*
* For example, an empty string is an item, as is the string containing the word
* "cat", a list containing any number of strings, as well as more complex data
* structures like ["cat",["puppy","cow"],"horse",[[]],"pig",[""],"sheep"]. Note
* that in the context of the rest of this article, "string" will be used as a
* synonym for "a certain number of bytes of binary data"; no special encodings
* are used and no knowledge about the content of the strings is implied.
*
* See: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP
*/
public class RlpEncoder extends CompactEncoder {
/** Allow for content up to size of 2^64 bytes **/
private static double MAX_ITEM_LENGTH = Math.pow(256, 8);
/**
[5:30:35 PM] Vitalik Buterin: 56 bytes maximizes the benefit of both options
[5:30:41 PM] Vitalik Buterin: if we went with 60
[5:31:03 PM] Vitalik Buterin: then we would have only had 4 slots for long strings so RLP would not have been able to store objects above 4gb
[5:31:08 PM] Vitalik Buterin: if we went with 48
[5:31:18 PM] Vitalik Buterin: then RLP would be fine for 2^128 space, but that's way too much
[5:31:32 PM] Vitalik Buterin: so 56 and 2^64 space seems like the right place to put the cutoff
[5:31:44 PM] Vitalik Buterin: also, that's where Bitcoin's varint does the cutof
**/
private static int SIZE_THRESHOLD = 56;
/** RLP encoding rules are defined as follows: */
/*
* For a single byte whose value is in the [0x00, 0x7f] range, that byte is
* its own RLP encoding.
*/
/*
* If a string is 0-55 bytes long, the RLP encoding consists of a single
* byte with value 0x80 plus the length of the string followed by the
* string. The range of the first byte is thus [0x80, 0xb7].
*/
private static int offsetShortItem = 0x80;
/*
* If a string is more than 55 bytes long, the RLP encoding consists of a
* single byte with value 0xb7 plus the length of the length of the string
* in binary form, followed by the length of the string, followed by the
* string. For example, a length-1024 string would be encoded as
* \xb9\x04\x00 followed by the string. The range of the first byte is thus
* [0xb8, 0xbf].
*/
private static int offsetLongItem = 0xb8;
/*
* If the total payload of a list (i.e. the combined length of all its
* items) is 0-55 bytes long, the RLP encoding consists of a single byte
* with value 0xc0 plus the length of the list followed by the concatenation
* of the RLP encodings of the items. The range of the first byte is thus
* [0xc0, 0xf7].
*/
private static int offsetShortList = 0xc0;
/*
* If the total payload of a list is more than 55 bytes long, the RLP
* encoding consists of a single byte with value 0xf7 plus the length of the
* length of the list in binary form, followed by the length of the list,
* followed by the concatenation of the RLP encodings of the items. The
* range of the first byte is thus [0xf8, 0xff].
*/
private static int offsetLongList = 0xf8;
private static int maxPrefix = 0xff;
public static byte[] encode(Object input) {
Value val = new Value(input);
if (val.isList()) {
List<Object> inputArray = val.asList();
if (inputArray.size() == 0) {
return encodeLength(inputArray.size(), offsetShortList);
}
byte[] output = new byte[0];
for (Object object : inputArray) {
output = concatenate(output, encode(object));
}
byte[] prefix = encodeLength(output.length, offsetShortList);
return concatenate(prefix, output);
} else {
byte[] inputAsHex = asHex(input);
if(inputAsHex.length == 1) {
return inputAsHex;
} else {
byte[] firstByte = encodeLength(inputAsHex.length, offsetShortItem);
return concatenate(firstByte, inputAsHex);
}
}
}
public static DecodeResult decode(byte[] data, int pos) {
if (data == null || data.length < 1) {
return null;
}
int prefix = data[pos] & maxPrefix;
if (prefix == offsetShortItem) {
return new DecodeResult(pos+1, new byte[0]); // means no length or 0
} else if (prefix < offsetShortItem) {
return new DecodeResult(pos+1, new byte[] { data[pos] }); // byte is its own RLP encoding
} else if (prefix < offsetLongItem){
int len = prefix - offsetShortItem; // length of the encoded bytes
return new DecodeResult(pos+1+len, copyOfRange(data, pos+1, pos+1+len));
} else if (prefix < offsetShortList) {
int lenlen = prefix - offsetLongItem + 1; // length of length the encoded bytes
int lenbytes = toInt(copyOfRange(data, pos+1, pos+1+lenlen)); // length of encoded bytes
return new DecodeResult(pos+1+lenlen+lenbytes, copyOfRange(data, pos+1+lenlen, pos+1+lenlen+lenbytes));
} else if (prefix < offsetLongList) {
int len = prefix - offsetShortList; // length of the encoded list
int prevPos = pos; pos++;
return decodeList(data, pos, prevPos, len);
} else if (prefix < maxPrefix) {
int lenlen = prefix - offsetLongList + 1; // length of length the encoded list
int lenlist = toInt(copyOfRange(data, pos+1, pos+1+lenlen)); // length of encoded bytes
pos = pos + lenlen + 1;
int prevPos = lenlist;
return decodeList(data, pos, prevPos, lenlist);
} else {
throw new RuntimeException("Only byte values between 0x00 and 0xFF are supported, but got: " + prefix);
}
}
/** Integer limitation goes up to 2^31-1 so length can never be bigger than MAX_ITEM_LENGTH */
public static byte[] encodeLength(int length, int offset) {
if (length < SIZE_THRESHOLD) {
byte firstByte = (byte) (length + offset);
return new byte[] { firstByte };
} else if (length < MAX_ITEM_LENGTH) {
byte[] binaryLength = BigInteger.valueOf(length).toByteArray();
byte firstByte = (byte) (binaryLength.length + offset + SIZE_THRESHOLD - 1 );
return concatenate(new byte[] { firstByte }, binaryLength);
} else {
throw new RuntimeException("Input too long");
}
}
private static DecodeResult decodeList(byte[] data, int pos, int prevPos, int len) {
List<Object> slice = new ArrayList<Object>();
for (int i = 0; i < len;) {
// Get the next item in the data list and append it
DecodeResult result = decode(data, pos);
slice.add(result.getDecoded());
// Increment pos by the amount bytes in the previous read
prevPos = result.getPos();
i += (prevPos - pos);
pos = prevPos;
}
return new DecodeResult(pos, slice.toArray());
}
public static byte[] asHex(Object input) {
if (input instanceof byte[]) {
return (byte[]) input;
} else if (input instanceof String) {
String inputString = (String) input;
return inputString.getBytes();
} else if(input instanceof Integer) {
Integer inputInt = (Integer) input;
return (inputInt == 0) ? new byte[0] : BigInteger.valueOf(inputInt.longValue()).toByteArray();
} else if(input instanceof BigInteger) {
BigInteger inputBigInt = (BigInteger) input;
return (inputBigInt == BigInteger.ZERO) ? new byte[0] : inputBigInt.toByteArray();
} else if (input instanceof Value) {
Value val = (Value) input;
return asHex(val.asObj());
}
throw new RuntimeException("Unsupported type: Only accepting String, Integer and BigInteger for now");
}
/**
* Cast hex encoded value from byte[] to int
*
* Limited to Integer.MAX_VALUE: 2^32-1
*
* @param b array contains the hex values
* @return int value of all hex values together.
*/
public static int toInt(byte[] b) {
if (b == null || b.length == 0) {
return 0;
}
return new BigInteger(b).intValue();
}
}

View File

@ -15,7 +15,7 @@ public class Value {
public void fromRlpEncoded(byte[] data) {
if (data.length != 0) {
this.value = RlpEncoder.decode(data, 0).getDecoded();
this.value = RLP.decode(data, 0).getDecoded();
}
}
@ -104,7 +104,7 @@ public class Value {
* *****************/
public byte[] encode() {
return RlpEncoder.encode(value);
return RLP.encode(value);
}
public boolean cmp(Value o) {

View File

@ -1,11 +1,10 @@
package org.ethereum.block;
import org.spongycastle.util.encoders.Hex;
import org.ethereum.core.Block;
import org.ethereum.crypto.HashUtil;
import org.ethereum.net.rlp.RLP;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.net.vo.Block;
import org.ethereum.util.RlpEncoder;
import org.ethereum.util.RLP;
import org.ethereum.util.RLPList;
import org.junit.Test;
import static org.junit.Assert.*;
@ -129,7 +128,7 @@ public class BlockTest {
public void testGenesisFromNew() {
System.out.println(CPP_PoC5_GENESIS_HEX_RLP_ENCODED);
Object genesisItems = RlpEncoder.decode(CPP_PoC5_GENESIS_HEX_RLP_ENCODED.getBytes(), 0).getDecoded();
Object genesisItems = RLP.decode(CPP_PoC5_GENESIS_HEX_RLP_ENCODED.getBytes(), 0).getDecoded();
// TODO: verify genesis items with expected values
/* From: https://ethereum.etherpad.mozilla.org/11
@ -183,8 +182,7 @@ public class BlockTest {
byte[] payload = Hex.decode(blocksMsg);
RLPList rlpList = new RLPList();
RLP.parseObjects(payload, rlpList);
RLPList rlpList = RLP.decode2(payload);
Block blockData = new Block(rlpList);
RLPList.recursivePrint(rlpList);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,425 +0,0 @@
package org.ethereum.util;
import static org.ethereum.util.RlpEncoder.toInt;
import static org.ethereum.util.RlpTestData.expected14;
import static org.ethereum.util.RlpTestData.expected16;
import static org.ethereum.util.RlpTestData.result01;
import static org.ethereum.util.RlpTestData.result02;
import static org.ethereum.util.RlpTestData.result03;
import static org.ethereum.util.RlpTestData.result04;
import static org.ethereum.util.RlpTestData.result05;
import static org.ethereum.util.RlpTestData.result06;
import static org.ethereum.util.RlpTestData.result07;
import static org.ethereum.util.RlpTestData.result08;
import static org.ethereum.util.RlpTestData.result09;
import static org.ethereum.util.RlpTestData.result10;
import static org.ethereum.util.RlpTestData.result11;
import static org.ethereum.util.RlpTestData.result12;
import static org.ethereum.util.RlpTestData.result13;
import static org.ethereum.util.RlpTestData.result14;
import static org.ethereum.util.RlpTestData.result15;
import static org.ethereum.util.RlpTestData.result16;
import static org.ethereum.util.RlpTestData.test01;
import static org.ethereum.util.RlpTestData.test02;
import static org.ethereum.util.RlpTestData.test03;
import static org.ethereum.util.RlpTestData.test04;
import static org.ethereum.util.RlpTestData.test05;
import static org.ethereum.util.RlpTestData.test06;
import static org.ethereum.util.RlpTestData.test07;
import static org.ethereum.util.RlpTestData.test08;
import static org.ethereum.util.RlpTestData.test09;
import static org.ethereum.util.RlpTestData.test10;
import static org.ethereum.util.RlpTestData.test11;
import static org.ethereum.util.RlpTestData.test12;
import static org.ethereum.util.RlpTestData.test13;
import static org.ethereum.util.RlpTestData.test14;
import static org.ethereum.util.RlpTestData.test15;
import static org.ethereum.util.RlpTestData.test16;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.math.BigInteger;
import java.util.Arrays;
import org.junit.Test;
import com.cedarsoftware.util.DeepEquals;
public class RlpEncoderTest {
/************************************
* Test data from: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP
*
* Using assertEquals(String, String) instead of assertArrayEquals to see the actual content when the test fails.
*/
@Test(expected = RuntimeException.class)
public void testEncodeNull() {
RlpEncoder.encode(null);
}
@Test
public void testEncodeEmptyString() {
String test = "";
String expected = "80";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(test, bytesToAscii(decodeResult));
}
@Test
public void testEncodeShortString() {
String test = "dog";
String expected = "83646f67";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(test, bytesToAscii(decodeResult));
}
@Test
public void testEncodeSingleCharacter() {
String test = "d";
String expected = "64";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(test, bytesToAscii(decodeResult));
}
@Test
public void testEncodeLongString() {
String test = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"; // length = 56
String expected = "b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(test, bytesToAscii(decodeResult));
}
@Test
public void testEncodeZero() {
Integer test = new Integer(0);
String expected = "80";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
int result = toInt(decodeResult);
assertEquals(test, Integer.valueOf(result));
}
@Test
public void testEncodeSmallInteger() {
Integer test = new Integer(15);
String expected = "0f";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
int result = toInt(decodeResult);
assertEquals(test, Integer.valueOf(result));
}
@Test
public void testEncodeMediumInteger() {
Integer test = new Integer(1000);
String expected = "8203e8";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
int result = toInt(decodeResult);
assertEquals(test, Integer.valueOf(result));
test = new Integer(1024);
expected = "820400";
encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
result = toInt(decodeResult);
assertEquals(test, Integer.valueOf(result));
}
@Test
public void testEncodeBigInteger() {
BigInteger test = new BigInteger("100102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 16);
String expected = "a0100102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(test, new BigInteger(decodeResult));
}
@Test
public void TestEncodeEmptyList() {
String[] test = new String[0];
String expected = "c0";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertTrue(decodeResult.length == 0);
}
@Test
public void testEncodeShortStringList() {
String[] test = new String[] { "cat", "dog" };
String expected = "c88363617483646f67";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals("cat", bytesToAscii((byte[]) decodeResult[0]));
assertEquals("dog", bytesToAscii((byte[]) decodeResult[1]));
test = new String[] { "dog", "god", "cat" };
expected = "cc83646f6783676f6483636174";
encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals("dog", bytesToAscii((byte[]) decodeResult[0]));
assertEquals("god", bytesToAscii((byte[]) decodeResult[1]));
assertEquals("cat", bytesToAscii((byte[]) decodeResult[2]));
}
@Test
public void testEncodeLongStringList() {
String element1 = "cat";
String element2 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
String[] test = new String[] { element1, element2 };
String expected = "f83e83636174b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974";
byte[] encoderesult = (byte[]) RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(element1, bytesToAscii((byte[]) decodeResult[0]));
assertEquals(element2, bytesToAscii((byte[]) decodeResult[1]));
}
//multilist:
//in: [ 1, ["cat"], "dog", [ 2 ] ],
//out: "cc01c48363617483646f67c102"
//in: [ [ ["cat"], ["dog"] ], [ [1] [2] ], [] ],
//out: "cdc88363617483646f67c20102c0"
@Test
public void testEncodeMultiList() {
Object[] test = new Object[] { 1, new Object[] { "cat" }, "dog", new Object[] { 2 } };
String expected = "cc01c48363617483646f67c102";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(1, toInt( (byte[]) decodeResult[0] ));
assertEquals("cat", bytesToAscii( ((byte[]) ((Object[]) decodeResult[1])[0] )));
assertEquals("dog", bytesToAscii( (byte[]) decodeResult[2]));
assertEquals(2, toInt( ((byte[]) ((Object[]) decodeResult[3])[0] )));
test = new Object[] { new Object[] { "cat", "dog" }, new Object[] { 1, 2 }, new Object[] { } };
expected = "cdc88363617483646f67c20102c0";
encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals("cat", bytesToAscii( ((byte[]) ((Object[]) decodeResult[0])[0] )));
assertEquals("dog", bytesToAscii( ((byte[]) ((Object[]) decodeResult[0])[1] )));
assertEquals(1, toInt( ((byte[]) ((Object[]) decodeResult[1])[0] )));
assertEquals(2, toInt( ((byte[]) ((Object[]) decodeResult[1])[1] )));
assertTrue( ( ((Object[]) decodeResult[2]).length == 0 ));
}
@Test
public void testEncodeEmptyListOfList() {
// list = [ [ [], [] ], [] ],
Object[] test = new Object[] { new Object[] { new Object[] {}, new Object[] {} }, new Object[] {} };
String expected = "c4c2c0c0c0";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertTrue( decodeResult.length == 2 );
assertTrue( ( (Object[]) (decodeResult[0] ) ).length == 2);
assertTrue( ( (Object[]) (decodeResult[1] ) ).length == 0);
assertTrue( ( (Object[]) ( (Object[]) ( decodeResult[0] ) )[0]).length == 0);
assertTrue( ( (Object[]) ( (Object[]) ( decodeResult[0] ) )[1]).length == 0);
}
//The set theoretical representation of two
@Test
public void testEncodeRepOfTwoListOfList() {
//list: [ [], [[]], [ [], [[]] ] ]
Object[] test = new Object[] { new Object[] { }, new Object[] { new Object[] {} }, new Object[] { new Object[] {}, new Object[] { new Object[] { } } } };
String expected = "c7c0c1c0c3c0c1c0";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertTrue( decodeResult.length == 3 );
assertTrue( ( (Object[]) (decodeResult[0]) ).length == 0);
assertTrue( ( (Object[]) (decodeResult[1]) ).length == 1);
assertTrue( ( (Object[]) (decodeResult[2]) ).length == 2);
assertTrue( ( (Object[]) ( (Object[]) (decodeResult[1]) )[0]).length == 0);
assertTrue( ( (Object[]) ( (Object[]) (decodeResult[2]) )[0]).length == 0);
assertTrue( ( (Object[]) ( (Object[]) (decodeResult[2]) )[1]).length == 1);
assertTrue( ( (Object[]) ( (Object[]) ( (Object[]) (decodeResult[2]) )[1] )[0]).length == 0);
}
@Test
public void testRlpEncode() {
assertEquals(result01, asHex(RlpEncoder.encode(test01)));
assertEquals(result02, asHex(RlpEncoder.encode(test02)));
assertEquals(result03, asHex(RlpEncoder.encode(test03)));
assertEquals(result04, asHex(RlpEncoder.encode(test04)));
assertEquals(result05, asHex(RlpEncoder.encode(test05)));
assertEquals(result06, asHex(RlpEncoder.encode(test06)));
assertEquals(result07, asHex(RlpEncoder.encode(test07)));
assertEquals(result08, asHex(RlpEncoder.encode(test08)));
assertEquals(result09, asHex(RlpEncoder.encode(test09)));
assertEquals(result10, asHex(RlpEncoder.encode(test10)));
assertEquals(result11, asHex(RlpEncoder.encode(test11)));
assertEquals(result12, asHex(RlpEncoder.encode(test12)));
assertEquals(result13, asHex(RlpEncoder.encode(test13)));
assertEquals(result14, asHex(RlpEncoder.encode(test14)));
assertEquals(result15, asHex(RlpEncoder.encode(test15)));
assertEquals(result16, asHex(RlpEncoder.encode(test16)));
}
@Test
public void testRlpDecode() {
int pos = 0;
byte[] decodedByte;
byte[] decodedData;
Object[] decodedList;
decodedByte = (byte[]) RlpEncoder.decode(fromHex(result01), pos).getDecoded();
assertEquals(test01, toInt(decodedByte));
decodedData = (byte[]) RlpEncoder.decode(fromHex(result02), pos).getDecoded();
assertEquals(test02, bytesToAscii(decodedData));
decodedData = (byte[]) RlpEncoder.decode(fromHex(result03), pos).getDecoded();
assertEquals(test03, bytesToAscii(decodedData));
decodedData = (byte[]) RlpEncoder.decode(fromHex(result04), pos).getDecoded();
assertEquals(test04, bytesToAscii(decodedData));
decodedData = (byte[]) RlpEncoder.decode(fromHex(result05), pos).getDecoded();
assertEquals(test05, bytesToAscii(decodedData));
decodedList = (Object[]) RlpEncoder.decode(fromHex(result06), pos).getDecoded();
assertEquals(test06[0], bytesToAscii((byte[]) decodedList[0]));
assertEquals(test06[1], bytesToAscii((byte[]) decodedList[1]));
decodedList = (Object[]) RlpEncoder.decode(fromHex(result07), pos).getDecoded();
assertEquals(test07[0], bytesToAscii((byte[]) decodedList[0]));
assertEquals(test07[1], bytesToAscii((byte[]) decodedList[1]));
assertEquals(test07[2], bytesToAscii((byte[]) decodedList[2]));
// 1
decodedData = (byte[]) RlpEncoder.decode(fromHex(result08), pos).getDecoded();
assertEquals(test08, toInt(decodedData));
// 10
decodedData = (byte[]) RlpEncoder.decode(fromHex(result09), pos).getDecoded();
assertEquals(test09, toInt(decodedData));
// 100
decodedData = (byte[]) RlpEncoder.decode(fromHex(result10), pos).getDecoded();
assertEquals(test10, toInt(decodedData));
// 1000
decodedData = (byte[]) RlpEncoder.decode(fromHex(result11), pos).getDecoded();
assertEquals(test11, toInt(decodedData));
decodedData = (byte[]) RlpEncoder.decode(fromHex(result12), pos).getDecoded();
assertTrue(test12.compareTo(new BigInteger(decodedData)) == 0);
decodedData = (byte[]) RlpEncoder.decode(fromHex(result13), pos).getDecoded();
assertTrue(test13.compareTo(new BigInteger(decodedData)) == 0);
// Need to test with different expected value, because decoding doesn't recognize types
Object testObject1 = RlpEncoder.decode(fromHex(result14), pos).getDecoded();
assertTrue(DeepEquals.deepEquals(expected14, testObject1));
Object testObject2 = RlpEncoder.decode(fromHex(result15), pos).getDecoded();
assertTrue(DeepEquals.deepEquals(test15, testObject2));
// Need to test with different expected value, because decoding doesn't recognize types
Object testObject3 = RlpEncoder.decode(fromHex(result16), pos).getDecoded();
assertTrue(DeepEquals.deepEquals(expected16, testObject3));
}
@Test
public void testEncodeLength() {
int length;
int offset;
byte[] encodedLength;
String expected;
// length < 56
length = 1; offset = 128;
encodedLength = RlpEncoder.encodeLength(length, offset);
expected = "81";
assertEquals(expected, asHex(encodedLength));
// 56 > length < 2^64
length = 56; offset = 192;
encodedLength = RlpEncoder.encodeLength(length, offset);
expected = "f838";
assertEquals(expected, asHex(encodedLength));
// length > 2^64
// TODO: Fix this test - when casting double to int, information gets lost since 'int' is max (2^31)-1
double maxLength = Math.pow(256, 8); offset = 192;
try {
encodedLength = RlpEncoder.encodeLength( (int) maxLength, offset);
System.out.println("length: " + length + ", offset: " + offset + ", encoded: " + Arrays.toString(encodedLength));
fail("Expecting RuntimeException: 'Input too long'");
} catch(RuntimeException e) {
// Success!
}
}
// Code from: http://stackoverflow.com/a/9855338/459349
protected final static char[] hexArray = "0123456789abcdef".toCharArray();
private static String asHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
// Code from: http://stackoverflow.com/a/4785776/459349
private String bytesToAscii(byte[] b) {
String hex = asHex(b);
StringBuilder output = new StringBuilder();
for (int i = 0; i < hex.length(); i+=2) {
String str = hex.substring(i, i+2);
output.append((char)Integer.parseInt(str, 16));
}
return output.toString();
}
// Code from: http://stackoverflow.com/a/140861/459349
public static byte[] fromHex(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}