/*
* 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.swarm.bzz;
import org.ethereum.net.client.Capability;
import org.ethereum.net.swarm.Key;
import org.ethereum.net.swarm.NetStore;
import org.ethereum.net.swarm.Util;
import org.ethereum.util.Functional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import static java.lang.Math.min;
/**
* The class is the lowest level right above the network layer.
* Responsible for BZZ handshaking, brokering inbound messages
* and delivering outbound messages.
*
* Created by Anton Nashatyrev on 18.06.2015.
*/
public class BzzProtocol implements Functional.Consumer<BzzMessage> {
private final static Logger LOG = LoggerFactory.getLogger("net.bzz");
private final static AtomicLong idGenerator = new AtomicLong(0);
public final static int Version = 0;
public final static long ProtocolLength = 8;
public final static long ProtocolMaxMsgSize = 10 * 1024 * 1024;
public final static int NetworkId = 0;
public final static int Strategy = 0;
private NetStore netStore;
private Functional.Consumer<BzzMessage> messageSender;
private PeerAddress node;
private boolean handshaken = false;
private boolean handshakeOut = false;
private List<BzzMessage> pendingHandshakeOutMessages = new ArrayList<>();
private List<BzzMessage> pendingHandshakeInMessages = new ArrayList<>();
public BzzProtocol(NetStore netStore) {
this.netStore = netStore;
}
/**
* Installs the message sender.
* Normally this is BzzHandler which just sends the message to the peer over the wire
* In the testing environment this could be a special handler which delivers the message
* without network stack
*/
public void setMessageSender(Functional.Consumer<BzzMessage> messageSender) {
this.messageSender = messageSender;
}
public void start() {
handshakeOut();
}
/**
* Gets the address of the Peer connected to this instance.
*/
public PeerAddress getNode() {
return node;
}
/**
* Sends the Status message to the peer
*/
private void handshakeOut() {
if (!handshakeOut) {
handshakeOut = true;
BzzStatusMessage outStatus = new BzzStatusMessage(Version, "honey",
netStore.getSelfAddress(), NetworkId,
Collections.singletonList(new Capability(Capability.BZZ, (byte) 0)));
LOG.info("Outbound handshake: " + outStatus);
sendMessageImpl(outStatus);
}
}
/**
* Handles inbound Status Message
*/
private void handshakeIn(BzzStatusMessage msg) {
if (!handshaken) {
LOG.info("Inbound handshake: " + msg);
netStore.statHandshakes.add(1);
// TODO check status parameters
node = msg.getAddr();
netStore.getHive().addPeer(this);
handshaken = true;
handshakeOut();
start();
if (!pendingHandshakeOutMessages.isEmpty()) {
LOG.info("Send pending handshake messages: " + pendingHandshakeOutMessages.size());
for (BzzMessage pmsg : pendingHandshakeOutMessages) {
sendMessageImpl(pmsg);
}
}
pendingHandshakeOutMessages = null;
// ping the peer for self neighbours
sendMessageImpl(new BzzRetrieveReqMessage(Key.zeroKey()));
if (!pendingHandshakeInMessages.isEmpty()) {
LOG.info("Processing pending handshake inbound messages: " + pendingHandshakeInMessages.size());
for (BzzMessage pmsg : pendingHandshakeInMessages) {
handleMsg(pmsg);
}
}
pendingHandshakeInMessages = null;
} else {
LOG.warn("Double inbound status message (ignore): " + msg);
}
}
public synchronized void sendMessage(BzzMessage msg) {
if (handshaken) {
sendMessageImpl(msg);
} else {
pendingHandshakeOutMessages.add(msg);
}
}
private void sendMessageImpl(BzzMessage msg) {
netStore.statOutMsg.add(1);
msg.setId(idGenerator.incrementAndGet());
LOG.debug("<=== (to " + addressToShortString(getNode()) + ") " + msg);
messageSender.accept(msg);
}
@Override
public void accept(BzzMessage bzzMessage) {
handleMsg(bzzMessage);
}
private void handleMsg(BzzMessage msg) {
synchronized (netStore) {
netStore.statInMsg.add(1);
msg.setPeer(this);
if (LOG.isDebugEnabled()) {
LOG.debug(" ===>(from " + addressToShortString(getNode()) + ") " + msg);
}
if (msg.getCommand() == BzzMessageCodes.STATUS) {
handshakeIn((BzzStatusMessage) msg);
} else {
if (!handshaken) {
pendingHandshakeInMessages.add(msg);
} else {
switch (msg.getCommand()) {
case STORE_REQUEST:
netStore.addStoreRequest((BzzStoreReqMessage) msg);
break;
case RETRIEVE_REQUEST:
netStore.addRetrieveRequest((BzzRetrieveReqMessage) msg);
break;
case PEERS:
netStore.getHive().addPeerRecords((BzzPeersMessage) msg);
break;
default:
LOG.error("Invalid BZZ command: " + msg.getCommand() + ": " + msg);
}
}
}
}
}
public static String addressToShortString(PeerAddress addr) {
if (addr == null) return "<null>";
String s = Hex.toHexString(addr.getId());
s = s.substring(0, min(8, s.length()));
return s + "@" + Util.ipBytesToString(addr.getIp()) + ":" + addr.getPort();
}
@Override
public String toString() {
return "BzzProtocol[" + netStore.getSelfAddress() + " => " + node + "]";
}
}