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

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

There is a newer version: 2.0.4
Show newest version
/*
 * Copyright 2015 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.InternalContext;
import ch.dissem.bitmessage.entity.GetData;
import ch.dissem.bitmessage.entity.MessagePayload;
import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.entity.Version;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.NetworkHandler.MessageListener;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT;
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC;
import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE;
import static ch.dissem.bitmessage.networking.AbstractConnection.State.DISCONNECTED;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;

/**
 * A connection to a specific node
 */
class Connection extends AbstractConnection {
    public static final int READ_TIMEOUT = 2000;
    private static final Logger LOG = LoggerFactory.getLogger(Connection.class);
    private static final int CONNECT_TIMEOUT = 5000;

    private final long startTime;
    private final Socket socket;
    private final ReaderRunnable reader = new ReaderRunnable();
    private final WriterRunnable writer = new WriterRunnable();

    private InputStream in;
    private OutputStream out;
    private boolean socketInitialized;

    public Connection(InternalContext context, Mode mode, Socket socket,
                      Set requestedObjectsMap) throws IOException {
        this(context, mode, socket, requestedObjectsMap,
                new NetworkAddress.Builder().ip(socket.getInetAddress()).port(socket.getPort()).stream(1).build(),
                0);
    }

    public Connection(InternalContext context, Mode mode, NetworkAddress node,
                      Set requestedObjectsMap) {
        this(context, mode, new Socket(), requestedObjectsMap,
                node, 0);
    }

    private Connection(InternalContext context, Mode mode, Socket socket,
                       Set commonRequestedObjects, NetworkAddress node, long syncTimeout) {
        super(context, mode, node, commonRequestedObjects, syncTimeout);
        this.startTime = UnixTime.now();
        this.socket = socket;
    }

    public static Connection sync(InternalContext ctx, InetAddress address, int port, MessageListener listener,
                                  long timeoutInSeconds) throws IOException {
        return new Connection(ctx, SYNC, new Socket(address, port),
                new HashSet(),
                new NetworkAddress.Builder().ip(address).port(port).stream(1).build(),
                timeoutInSeconds);
    }

    public long getStartTime() {
        return startTime;
    }

    public Mode getMode() {
        return mode;
    }

    public State getState() {
        return state;
    }

    public NetworkAddress getNode() {
        return node;
    }

    @Override
    protected void send(MessagePayload payload) {
        try {
            if (payload instanceof GetData) {
                requestedObjects.addAll(((GetData) payload).getInventory());
            }
            synchronized (this) {
                new NetworkMessage(payload).write(out);
            }
        } catch (IOException e) {
            LOG.error(e.getMessage(), e);
            disconnect();
        }
    }

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

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

    private synchronized void initSocket(Socket socket) throws IOException {
        if (!socketInitialized) {
            if (!socket.isConnected()) {
                LOG.trace("Trying to connect to node " + node);
                socket.connect(new InetSocketAddress(node.toInetAddress(), node.getPort()), CONNECT_TIMEOUT);
            }
            socket.setSoTimeout(READ_TIMEOUT);
            in = socket.getInputStream();
            out = socket.getOutputStream();
            socketInitialized = true;
        }
    }

    public ReaderRunnable getReader() {
        return reader;
    }

    public WriterRunnable getWriter() {
        return writer;
    }

    public class ReaderRunnable implements Runnable {
        @Override
        public void run() {
            try (Socket socket = Connection.this.socket) {
                initSocket(socket);
                if (mode == CLIENT || mode == SYNC) {
                    send(new Version.Builder().defaults(ctx.getClientNonce()).addrFrom(host).addrRecv(node).build());
                }
                while (state != DISCONNECTED) {
                    if (mode != SYNC) {
                        if (state == ACTIVE && requestedObjects.isEmpty() && sendingQueue.isEmpty()) {
                            Thread.sleep(1000);
                        } else {
                            Thread.sleep(100);
                        }
                    }
                    receive();
                }
            } catch (Exception e) {
                LOG.trace("Reader disconnected from node " + node + ": " + e.getMessage());
            } finally {
                disconnect();
                try {
                    socket.close();
                } catch (Exception e) {
                    LOG.debug(e.getMessage(), e);
                }
            }
        }

        private void receive() throws InterruptedException {
            try {
                NetworkMessage msg = Factory.getNetworkMessage(version, in);
                if (msg == null)
                    return;
                handleMessage(msg.getPayload());
                if (socket.isClosed() || syncFinished(msg) || checkOpenRequests()) disconnect();
            } catch (SocketTimeoutException ignore) {
                if (state == ACTIVE && syncFinished(null)) disconnect();
            }
        }

    }

    private boolean checkOpenRequests() {
        return !requestedObjects.isEmpty() && lastObjectTime > 0 && (UnixTime.now() - lastObjectTime) > 2 * MINUTE;
    }

    public class WriterRunnable implements Runnable {
        @Override
        public void run() {
            try (Socket socket = Connection.this.socket) {
                initSocket(socket);
                while (state != DISCONNECTED) {
                    if (sendingQueue.isEmpty()) {
                        Thread.sleep(1000);
                    } else {
                        send(sendingQueue.poll());
                    }
                }
            } catch (IOException | InterruptedException e) {
                LOG.trace("Writer disconnected from node " + node + ": " + e.getMessage());
                disconnect();
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy