RLPx message structure introduced
This commit is contained in:
parent
e0cebbba86
commit
656e7b2673
|
@ -0,0 +1,31 @@
|
|||
package org.ethereum.net.rlpx;
|
||||
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.util.RLP;
|
||||
|
||||
public class FindNodeMessage extends Message {
|
||||
|
||||
public static Message create(byte[] target) {
|
||||
|
||||
long expiration = System.currentTimeMillis();
|
||||
|
||||
/* RLP Encode data */
|
||||
byte[] rlpToken = RLP.encodeElement(target);
|
||||
byte[] rlpExp = RLP.encodeElement(ByteUtil.longToBytes(expiration));
|
||||
|
||||
byte[] type = new byte[]{3};
|
||||
byte[] data = RLP.encodeList(rlpToken, rlpExp);
|
||||
|
||||
FindNodeMessage findNode = new FindNodeMessage();
|
||||
findNode.encode(type, data);
|
||||
|
||||
return findNode;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FindNodeMessage: " + super.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package org.ethereum.net.rlpx;
|
||||
|
||||
import org.ethereum.crypto.ECKey;
|
||||
import org.ethereum.util.FastByteComparisons;
|
||||
import org.spongycastle.util.BigIntegers;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import static org.ethereum.crypto.HashUtil.sha3;
|
||||
import static org.ethereum.util.ByteUtil.merge;
|
||||
|
||||
public class Message {
|
||||
|
||||
byte[] mdc;
|
||||
byte[] signature;
|
||||
byte[] type;
|
||||
byte[] data;
|
||||
|
||||
public static Message decode(byte[] wire) {
|
||||
|
||||
if (wire.length < 98) throw new Error("Bad message");
|
||||
|
||||
byte[] mdc = new byte[32];
|
||||
System.arraycopy(wire, 0, mdc, 0, 32);
|
||||
|
||||
byte[] signature = new byte[65];
|
||||
System.arraycopy(wire, 32, signature, 0, 65);
|
||||
|
||||
byte[] type = new byte[1];
|
||||
type[0] = wire[97];
|
||||
|
||||
byte[] data = new byte[wire.length - 98];
|
||||
System.arraycopy(wire, 98, data, 0, data.length);
|
||||
|
||||
byte[] mdcCheck = sha3(wire, 32, wire.length - 32);
|
||||
|
||||
int check = FastByteComparisons.compareTo(mdc, 0, mdc.length, mdcCheck, 0, mdcCheck.length);
|
||||
|
||||
if (check != 0) throw new Error("MDC check failed");
|
||||
|
||||
Message msg;
|
||||
if (type[0] == 1) msg = new PingMessage();
|
||||
else if (type[0] == 2) msg = new PongMessage();
|
||||
else if (type[0] == 3) msg = new FindNodeMessage();
|
||||
else if (type[0] == 4) msg = new NeighborsMessage();
|
||||
else throw new Error("Unknown RLPx message");
|
||||
|
||||
msg.mdc = mdc;
|
||||
msg.signature = signature;
|
||||
msg.type = type;
|
||||
msg.data = data;
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
public Message encode(byte[] type, byte[] data) {
|
||||
|
||||
/* [1] Calc sha3 - prepare for sig */
|
||||
byte[] payload = new byte[type.length + data.length];
|
||||
payload[0] = type[0];
|
||||
System.arraycopy(data, 0, payload, 1, data.length);
|
||||
byte[] forSig = sha3(payload);
|
||||
|
||||
/* [2] Crate signature*/
|
||||
ECKey privKey = ECKey.fromPrivate(Hex.decode("3ecb44df2159c26e0f995712d4f39b6f6e499b40749b1cf1246c37f9516cb6a4"));
|
||||
ECKey.ECDSASignature signature = privKey.sign(forSig);
|
||||
|
||||
byte[] sigBytes =
|
||||
merge(new byte[]{signature.v}, BigIntegers.asUnsignedByteArray(signature.r),
|
||||
BigIntegers.asUnsignedByteArray(signature.s));
|
||||
|
||||
// [3] calculate MDC
|
||||
byte[] forSha = merge(sigBytes, type, data);
|
||||
byte[] mdc = sha3(forSha);
|
||||
|
||||
// wrap all the data in to the packet
|
||||
this.mdc = mdc;
|
||||
this.signature = sigBytes;
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getPacket() {
|
||||
byte[] packet = merge(mdc, signature, type, data);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public byte[] getMdc() {
|
||||
return mdc;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public byte[] getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"mdc=" + Hex.toHexString(mdc) +
|
||||
", signature=" + Hex.toHexString(signature) +
|
||||
", type=" + Hex.toHexString(type) +
|
||||
", data=" + Hex.toHexString(data) +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package org.ethereum.net.rlpx;
|
||||
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.util.RLP;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class NeighborsMessage extends Message {
|
||||
|
||||
public static Message create(List<Node> nodes) {
|
||||
|
||||
long expiration = System.currentTimeMillis();
|
||||
|
||||
byte[][] nodeRLPs = new byte[nodes.size()][];
|
||||
|
||||
/* RLP Encode data */
|
||||
int i = 0;
|
||||
for (Node node : nodes){
|
||||
nodeRLPs[i] = node.getRLP();
|
||||
++i;
|
||||
}
|
||||
|
||||
byte[] rlpListNodes = RLP.encodeList(nodeRLPs);
|
||||
byte[] rlpExp = RLP.encodeElement(ByteUtil.longToBytes(expiration));
|
||||
|
||||
byte[] type = new byte[]{4};
|
||||
byte[] data = RLP.encodeList(rlpListNodes, rlpExp);
|
||||
|
||||
NeighborsMessage neighborsMessage = new NeighborsMessage();
|
||||
neighborsMessage.encode(type, data);
|
||||
|
||||
return neighborsMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NeighborsMessage: " + super.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package org.ethereum.net.rlpx;
|
||||
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.util.RLP;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class Node {
|
||||
|
||||
byte[] id;
|
||||
String ip;
|
||||
int port;
|
||||
|
||||
public Node(byte[] id, String ip, int port) {
|
||||
this.id = id;
|
||||
this.ip = ip;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public byte[] getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(byte[] id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public byte[] getRLP(){
|
||||
|
||||
byte[] rlpId = RLP.encodeElement(id);
|
||||
byte[] rlpIp = RLP.encodeElement(ip.getBytes(Charset.forName("UTF-8")));
|
||||
byte[] rlpPort = RLP.encodeElement(ByteUtil.longToBytes(port));
|
||||
|
||||
byte[] data = RLP.encodeList(rlpId, rlpIp, rlpPort);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package org.ethereum.net.rlpx;
|
||||
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.util.RLP;
|
||||
|
||||
public class PingMessage extends Message {
|
||||
|
||||
public static Message create(String ip, int port){
|
||||
|
||||
long expiration = System.currentTimeMillis();
|
||||
|
||||
/* RLP Encode data */
|
||||
byte[] rlpIp = RLP.encodeElement(ip.getBytes());
|
||||
byte[] rlpPort = RLP.encodeElement(ByteUtil.longToBytes(port));
|
||||
byte[] rlpExp = RLP.encodeElement(ByteUtil.longToBytes(expiration));
|
||||
|
||||
byte[] type = new byte[]{1};
|
||||
byte[] data = RLP.encodeList(rlpIp, rlpPort, rlpExp);
|
||||
|
||||
PingMessage ping = new PingMessage();
|
||||
ping.encode(type, data);
|
||||
|
||||
return ping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PingMessage: " + super.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.ethereum.net.rlpx;
|
||||
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.util.RLP;
|
||||
|
||||
public class PongMessage extends Message {
|
||||
|
||||
public static Message create(byte[] token) {
|
||||
|
||||
long expiration = System.currentTimeMillis();
|
||||
|
||||
/* RLP Encode data */
|
||||
byte[] rlpToken = RLP.encodeElement(token);
|
||||
byte[] rlpExp = RLP.encodeElement(ByteUtil.longToBytes(expiration));
|
||||
|
||||
byte[] type = new byte[]{2};
|
||||
byte[] data = RLP.encodeList(rlpToken, rlpExp);
|
||||
|
||||
PongMessage pong = new PongMessage();
|
||||
pong.encode(type, data);
|
||||
|
||||
return pong;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PongMessage: " + super.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package test.ethereum.net;
|
||||
|
||||
import org.ethereum.net.rlpx.*;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.ethereum.crypto.HashUtil.sha3;
|
||||
import static org.ethereum.util.ByteUtil.merge;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class RLPXTest {
|
||||
|
||||
private static final org.slf4j.Logger logger = LoggerFactory.getLogger("test");
|
||||
|
||||
@Test // ping test
|
||||
public void test1(){
|
||||
|
||||
String ip = "85.65.19.231";
|
||||
int port = 30303;
|
||||
long expiration = System.currentTimeMillis();
|
||||
|
||||
Message ping = PingMessage.create(ip, port);
|
||||
logger.info("{}", ping);
|
||||
|
||||
byte[] wire = ping.getPacket();
|
||||
PingMessage ping2 = (PingMessage)Message.decode(wire);
|
||||
logger.info("{}", ping2);
|
||||
|
||||
assertEquals(ping.toString(), ping2.toString());
|
||||
}
|
||||
|
||||
@Test // pong test
|
||||
public void test2(){
|
||||
|
||||
byte[] token = sha3("+++".getBytes(Charset.forName("UTF-8")));
|
||||
|
||||
Message pong = PongMessage.create(token);
|
||||
logger.info("{}", pong);
|
||||
|
||||
byte[] wire = pong.getPacket();
|
||||
PongMessage pong2 = (PongMessage)Message.decode(wire);
|
||||
logger.info("{}", pong);
|
||||
|
||||
assertEquals(pong.toString(), pong2.toString());
|
||||
}
|
||||
|
||||
@Test // neighbors message
|
||||
public void test3(){
|
||||
|
||||
String ip = "85.65.19.231";
|
||||
int port = 30303;
|
||||
|
||||
byte[] part1 = sha3("007".getBytes(Charset.forName("UTF-8")));
|
||||
byte[] part2 = sha3("007".getBytes(Charset.forName("UTF-8")));
|
||||
byte[] id = merge(part1, part2);
|
||||
|
||||
Node node = new Node(id, ip, port);
|
||||
|
||||
List<Node> nodes = Arrays.asList(node);
|
||||
Message neighbors = NeighborsMessage.create(nodes);
|
||||
logger.info("{}", neighbors);
|
||||
|
||||
byte[] wire = neighbors.getPacket();
|
||||
NeighborsMessage neighbors2 = (NeighborsMessage)Message.decode(wire);
|
||||
logger.info("{}", neighbors2);
|
||||
|
||||
assertEquals(neighbors.toString(), neighbors2.toString());
|
||||
}
|
||||
|
||||
@Test // find node message
|
||||
public void test4(){
|
||||
|
||||
byte[] id = sha3("+++".getBytes(Charset.forName("UTF-8")));
|
||||
|
||||
Message findNode = FindNodeMessage.create(id);
|
||||
logger.info("{}", findNode);
|
||||
|
||||
byte[] wire = findNode.getPacket();
|
||||
FindNodeMessage findNode2 = (FindNodeMessage)Message.decode(wire);
|
||||
logger.info("{}", findNode2);
|
||||
|
||||
assertEquals(findNode.toString(), findNode2.toString());
|
||||
}
|
||||
|
||||
|
||||
@Test (expected = Error.class)// failure on MDC
|
||||
public void test5(){
|
||||
|
||||
byte[] id = sha3("+++".getBytes(Charset.forName("UTF-8")));
|
||||
|
||||
Message findNode = FindNodeMessage.create(id);
|
||||
logger.info("{}", findNode);
|
||||
|
||||
byte[] wire = findNode.getPacket();
|
||||
wire[64] = 0;
|
||||
|
||||
FindNodeMessage findNode2 = (FindNodeMessage)Message.decode(wire);
|
||||
logger.info("{}", findNode2);
|
||||
|
||||
assertEquals(findNode.toString(), findNode2.toString());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue