All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.dissem.bitmessage.networking.AbstractConnection Maven / Gradle / Ivy

There is a newer version: 2.0.4
Show newest version
/*
 * Copyright 2016 Christian Basler
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ch.dissem.bitmessage.networking;

import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.*;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.ports.NetworkHandler;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;

import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER;
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
import static ch.dissem.bitmessage.networking.AbstractConnection.State.*;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;

/**
 * Contains everything used by both the old streams-oriented NetworkHandler and the new NioNetworkHandler,
 * respectively their connection objects.
 */
public abstract class AbstractConnection {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractConnection.class);
    protected final InternalContext ctx;
    protected final Mode mode;
    protected final NetworkAddress host;
    protected final NetworkAddress node;
    protected final NetworkHandler.MessageListener listener;
    protected final Map ivCache;
    protected final Deque sendingQueue;
    protected final Set commonRequestedObjects;
    protected final Set requestedObjects;

    protected volatile State state;
    protected long lastObjectTime;

    private final long syncTimeout;
    private long syncReadTimeout = Long.MAX_VALUE;

    protected long peerNonce;
    protected int version;
    protected long[] streams;
    private boolean verackSent;
    private boolean verackReceived;

    public AbstractConnection(InternalContext context, Mode mode,
                              NetworkAddress node,
                              Set commonRequestedObjects,
                              long syncTimeout) {
        this.ctx = context;
        this.mode = mode;
        this.host = new NetworkAddress.Builder().ipv6(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).port(0).build();
        this.node = node;
        this.listener = context.getNetworkListener();
        this.syncTimeout = (syncTimeout > 0 ? UnixTime.now(+syncTimeout) : 0);
        this.requestedObjects = Collections.newSetFromMap(new ConcurrentHashMap(10_000));
        this.ivCache = new ConcurrentHashMap<>();
        this.sendingQueue = new ConcurrentLinkedDeque<>();
        this.state = CONNECTING;
        this.commonRequestedObjects = commonRequestedObjects;
    }

    public Mode getMode() {
        return mode;
    }

    public NetworkAddress getNode() {
        return node;
    }

    public State getState() {
        return state;
    }

    public long[] getStreams() {
        return streams;
    }

    protected void handleMessage(MessagePayload payload) {
        switch (state) {
            case ACTIVE:
                receiveMessage(payload);
                break;

            case DISCONNECTED:
                break;

            default:
                handleCommand(payload);
                break;
        }
    }

    private void receiveMessage(MessagePayload messagePayload) {
        switch (messagePayload.getCommand()) {
            case INV:
                receiveMessage((Inv) messagePayload);
                break;
            case GETDATA:
                receiveMessage((GetData) messagePayload);
                break;
            case OBJECT:
                receiveMessage((ObjectMessage) messagePayload);
                break;
            case ADDR:
                receiveMessage((Addr) messagePayload);
                break;
            case CUSTOM:
            case VERACK:
            case VERSION:
            default:
                throw new IllegalStateException("Unexpectedly received '" + messagePayload.getCommand() + "' command");
        }
    }

    private void receiveMessage(Inv inv) {
        int originalSize = inv.getInventory().size();
        updateIvCache(inv.getInventory());
        List missing = ctx.getInventory().getMissing(inv.getInventory(), streams);
        missing.removeAll(commonRequestedObjects);
        LOG.trace("Received inventory with " + originalSize + " elements, of which are "
            + missing.size() + " missing.");
        send(new GetData.Builder().inventory(missing).build());
    }

    private void receiveMessage(GetData getData) {
        for (InventoryVector iv : getData.getInventory()) {
            ObjectMessage om = ctx.getInventory().getObject(iv);
            if (om != null) sendingQueue.offer(om);
        }
    }

    private void receiveMessage(ObjectMessage objectMessage) {
        requestedObjects.remove(objectMessage.getInventoryVector());
        if (ctx.getInventory().contains(objectMessage)) {
            LOG.trace("Received object " + objectMessage.getInventoryVector() + " - already in inventory");
            return;
        }
        try {
            listener.receive(objectMessage);
            cryptography().checkProofOfWork(objectMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES);
            ctx.getInventory().storeObject(objectMessage);
            // offer object to some random nodes so it gets distributed throughout the network:
            ctx.getNetworkHandler().offer(objectMessage.getInventoryVector());
            lastObjectTime = UnixTime.now();
        } catch (InsufficientProofOfWorkException e) {
            LOG.warn(e.getMessage());
            // DebugUtils.saveToFile(objectMessage); // this line must not be committed active
        } catch (IOException e) {
            LOG.error("Stream " + objectMessage.getStream() + ", object type " + objectMessage.getType() + ": " + e.getMessage(), e);
        } finally {
            if (!commonRequestedObjects.remove(objectMessage.getInventoryVector())) {
                LOG.debug("Received object that wasn't requested.");
            }
        }
    }

    private void receiveMessage(Addr addr) {
        LOG.trace("Received " + addr.getAddresses().size() + " addresses.");
        ctx.getNodeRegistry().offerAddresses(addr.getAddresses());
    }

    private void updateIvCache(List inventory) {
        cleanupIvCache();
        Long now = UnixTime.now();
        for (InventoryVector iv : inventory) {
            ivCache.put(iv, now);
        }
    }

    public void offer(InventoryVector iv) {
        sendingQueue.offer(new Inv.Builder()
            .addInventoryVector(iv)
            .build());
        updateIvCache(Collections.singletonList(iv));
    }

    public boolean knowsOf(InventoryVector iv) {
        return ivCache.containsKey(iv);
    }

    private void cleanupIvCache() {
        Long fiveMinutesAgo = UnixTime.now(-5 * MINUTE);
        for (Map.Entry entry : ivCache.entrySet()) {
            if (entry.getValue() < fiveMinutesAgo) {
                ivCache.remove(entry.getKey());
            }
        }
    }

    private void handleCommand(MessagePayload payload) {
        switch (payload.getCommand()) {
            case VERSION:
                handleVersion((Version) payload);
                break;
            case VERACK:
                if (verackSent) {
                    activateConnection();
                }
                verackReceived = true;
                break;
            case CUSTOM:
                MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) payload);
                if (response == null) {
                    disconnect();
                } else {
                    send(response);
                }
                break;
            default:
                throw new NodeException("Command 'version' or 'verack' expected, but was '"
                    + payload.getCommand() + "'");
        }
    }

    private void activateConnection() {
        LOG.info("Successfully established connection with node " + node);
        state = ACTIVE;
        node.setTime(UnixTime.now());
        if (mode != SYNC) {
            sendAddresses();
            ctx.getNodeRegistry().offerAddresses(Collections.singletonList(node));
        }
        sendInventory();
    }

    private void sendAddresses() {
        List addresses = ctx.getNodeRegistry().getKnownAddresses(1000, streams);
        sendingQueue.offer(new Addr.Builder().addresses(addresses).build());
    }

    private void sendInventory() {
        List inventory = ctx.getInventory().getInventory(streams);
        for (int i = 0; i < inventory.size(); i += 50000) {
            sendingQueue.offer(new Inv.Builder()
                .inventory(inventory.subList(i, Math.min(inventory.size(), i + 50000)))
                .build());
        }
    }

    private void handleVersion(Version version) {
        if (version.getNonce() == ctx.getClientNonce()) {
            LOG.info("Tried to connect to self, disconnecting.");
            disconnect();
        } else if (version.getVersion() >= BitmessageContext.CURRENT_VERSION) {
            this.peerNonce = version.getNonce();
            if (peerNonce == ctx.getClientNonce()) disconnect();

            this.version = version.getVersion();
            this.streams = version.getStreams();
            verackSent = true;
            send(new VerAck());
            if (mode == SERVER) {
                send(new Version.Builder().defaults(ctx.getClientNonce()).addrFrom(host).addrRecv(node).build());
            }
            if (verackReceived) {
                activateConnection();
            }
        } else {
            LOG.info("Received unsupported version " + version.getVersion() + ", disconnecting.");
            disconnect();
        }
    }

    @SuppressWarnings("RedundantIfStatement")
    protected boolean syncFinished(NetworkMessage msg) {
        if (mode != SYNC) {
            return false;
        }
        if (Thread.interrupted()) {
            return true;
        }
        if (state != ACTIVE) {
            return false;
        }
        if (syncTimeout < UnixTime.now()) {
            LOG.info("Synchronization timed out");
            return true;
        }
        if (!sendingQueue.isEmpty()) {
            syncReadTimeout = System.currentTimeMillis() + 1000;
            return false;
        }
        if (msg == null) {
            return syncReadTimeout < System.currentTimeMillis();
        } else {
            syncReadTimeout = System.currentTimeMillis() + 1000;
            return false;
        }
    }

    public void disconnect() {
        state = DISCONNECTED;

        // Make sure objects that are still missing are requested from other nodes
        ctx.getNetworkHandler().request(requestedObjects);
    }

    protected abstract void send(MessagePayload payload);

    public enum Mode {SERVER, CLIENT, SYNC}

    public enum State {CONNECTING, ACTIVE, DISCONNECTED}

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AbstractConnection that = (AbstractConnection) o;
        return Objects.equals(node, that.node);
    }

    @Override
    public int hashCode() {
        return Objects.hash(node);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy