/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.net.rlpx;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import org.ethereum.crypto.ECIESCoder;
import org.ethereum.crypto.ECKey;
import org.ethereum.net.client.Capability;
import org.ethereum.net.message.ReasonCode;
import org.ethereum.net.p2p.DisconnectMessage;
import org.ethereum.net.p2p.PingMessage;
import org.ethereum.net.rlpx.EncryptionHandshake.Secrets;
import org.spongycastle.crypto.digests.KeccakDigest;
import org.spongycastle.util.encoders.Hex;
import java.io.*;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
/**
* Created by devrandom on 2015-04-09.
*/
public class Handshaker {
private final ECKey myKey;
private final byte[] nodeId;
private Secrets secrets;
public static void main(String[] args) throws IOException, URISyntaxException {
URI uri = new URI(args[0]);
if (!uri.getScheme().equals("enode"))
throw new RuntimeException("expecting URL in the format enode://PUBKEY@HOST:PORT");
new Handshaker().doHandshake(uri.getHost(), uri.getPort(), uri.getUserInfo());
}
public Handshaker() {
myKey = new ECKey();
nodeId = myKey.getNodeId();
System.out.println("Node ID " + Hex.toHexString(nodeId));
}
/**
* Sample output:
* <pre>
Node ID b7fb52ddb1f269fef971781b9568ad65d30ac3b6055ebd6a0a762e6b67a7c92bd7c1fdf3c7c722d65ae70bfe6a9a58443297485aa29e3acd9bdf2ee0df4f5c45
packet f86b0399476574682f76302e392e372f6c696e75782f676f312e342e32ccc5836574683cc5837368680280b840f1c041a7737e8e06536d9defb92cb3db6ecfeb1b1208edfca6953c0c683a31ff0a478a832bebb6629e4f5c13136478842cc87a007729f3f1376f4462eb424ded
[eth:60, shh:2]
packet type 16
packet f8453c7b80a0fd4af92a79c7fc2fd8bf0d342f2e832e1d4f485c85b9152d2039e03bc604fdcaa0fd4af92a79c7fc2fd8bf0d342f2e832e1d4f485c85b9152d2039e03bc604fdca
packet type 24
packet c102
packet type 3
packet c0
packet type 1
packet c180
</pre>
*/
public void doHandshake(String host, int port, String remoteIdHex) throws IOException {
byte[] remoteId = Hex.decode(remoteIdHex);
EncryptionHandshake initiator = new EncryptionHandshake(ECKey.fromNodeId(remoteId).getPubKeyPoint());
Socket sock = new Socket(host, port);
InputStream inp = sock.getInputStream();
OutputStream out = sock.getOutputStream();
AuthInitiateMessage initiateMessage = initiator.createAuthInitiate(null, myKey);
byte[] initiatePacket = initiator.encryptAuthMessage(initiateMessage);
out.write(initiatePacket);
byte[] responsePacket = new byte[AuthResponseMessage.getLength() + ECIESCoder.getOverhead()];
int n = inp.read(responsePacket);
if (n < responsePacket.length)
throw new IOException("could not read, got " + n);
initiator.handleAuthResponse(myKey, initiatePacket, responsePacket);
byte[] buf = new byte[initiator.getSecrets().getEgressMac().getDigestSize()];
new KeccakDigest(initiator.getSecrets().getEgressMac()).doFinal(buf, 0);
new KeccakDigest(initiator.getSecrets().getIngressMac()).doFinal(buf, 0);
RlpxConnection conn = new RlpxConnection(initiator.getSecrets(), inp, out);
HandshakeMessage handshakeMessage = new HandshakeMessage(
3,
"computronium1",
Lists.newArrayList(
new Capability("eth", (byte) 60),
new Capability("shh", (byte) 2)
),
3333,
nodeId
);
conn.sendProtocolHandshake(handshakeMessage);
conn.handleNextMessage();
if (!Arrays.equals(remoteId, conn.getHandshakeMessage().nodeId))
throw new IOException("returns node ID doesn't match the node ID we dialed to");
System.out.println(conn.getHandshakeMessage().caps);
conn.writeMessage(new PingMessage());
conn.writeMessage(new DisconnectMessage(ReasonCode.PEER_QUITING));
conn.handleNextMessage();
while (true) {
try {
conn.handleNextMessage();
} catch (EOFException e) {
break;
}
}
this.secrets = initiator.getSecrets();
}
public Secrets getSecrets() {
return secrets;
}
private void delay(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Throwables.propagate(e);
}
}
}