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

bt.net.PeerConnectionFactory Maven / Gradle / Ivy

There is a newer version: 1.10
Show newest version
/*
 * Copyright (c) 2016—2017 Andrei Tomashpolskiy and individual contributors.
 *
 * 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 bt.net;

import bt.event.EventSource;
import bt.metainfo.TorrentId;
import bt.net.buffer.BorrowedBuffer;
import bt.net.buffer.IBufferManager;
import bt.net.crypto.CipherBufferMutator;
import bt.net.crypto.MSEHandshakeProcessor;
import bt.net.pipeline.ChannelHandler;
import bt.net.pipeline.ChannelPipeline;
import bt.net.pipeline.ChannelPipelineBuilder;
import bt.net.pipeline.IChannelPipelineFactory;
import bt.net.pipeline.SocketChannelHandler;
import bt.protocol.Message;
import bt.protocol.crypto.MSECipher;
import bt.protocol.handler.MessageHandler;
import bt.runtime.Config;
import bt.torrent.TorrentRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.time.Duration;
import java.util.Objects;
import java.util.Optional;

public class PeerConnectionFactory implements IPeerConnectionFactory {
    private static final Logger LOGGER = LoggerFactory.getLogger(PeerConnectionFactory.class);

    private static final Duration socketTimeout = Duration.ofSeconds(30);

    private MessageHandler protocol;

    private Selector selector;
    private IConnectionHandlerFactory connectionHandlerFactory;
    private IChannelPipelineFactory channelPipelineFactory;
    private IBufferManager bufferManager;
    private MSEHandshakeProcessor cryptoHandshakeProcessor;
    private DataReceiver dataReceiver;
    private EventSource eventSource;

    private InetSocketAddress localOutgoingSocketAddress;

    public PeerConnectionFactory(Selector selector,
                                 IConnectionHandlerFactory connectionHandlerFactory,
                                 IChannelPipelineFactory channelPipelineFactory,
                                 MessageHandler protocol,
                                 TorrentRegistry torrentRegistry,
                                 IBufferManager bufferManager,
                                 DataReceiver dataReceiver,
                                 EventSource eventSource,
                                 Config config) {

        this.protocol = protocol;
        this.selector = selector;
        this.connectionHandlerFactory = connectionHandlerFactory;
        this.channelPipelineFactory = channelPipelineFactory;
        this.bufferManager = bufferManager;
        this.cryptoHandshakeProcessor = new MSEHandshakeProcessor(torrentRegistry, protocol, config);
        this.dataReceiver = dataReceiver;
        this.eventSource = eventSource;
        this.localOutgoingSocketAddress = new InetSocketAddress(config.getAcceptorAddress(), 0);
    }

    @Override
    public ConnectionResult createOutgoingConnection(Peer peer, TorrentId torrentId) {
        Objects.requireNonNull(peer);
        Objects.requireNonNull(torrentId);

        InetAddress inetAddress = peer.getInetAddress();
        int port = peer.getPort();

        SocketChannel channel;
        try {
            channel = getChannel(inetAddress, port);
        } catch (IOException e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Failed to establish connection with peer: {}. Reason: {} ({})",
                        peer, e.getClass().getName(), e.getMessage());
            }
            return ConnectionResult.failure("I/O error", e);
        }

        return createConnection(peer, torrentId, channel, false);
    }

    private SocketChannel getChannel(InetAddress inetAddress, int port) throws IOException {
        InetSocketAddress remoteAddress = new InetSocketAddress(inetAddress, port);
        SocketChannel outgoingChannel = selector.provider().openSocketChannel();
        outgoingChannel.socket().bind(localOutgoingSocketAddress);
        outgoingChannel.socket().setSoTimeout((int) socketTimeout.toMillis());
        outgoingChannel.socket().setSoLinger(false, 0);
        outgoingChannel.connect(remoteAddress);
        return outgoingChannel;
    }

    @Override
    public ConnectionResult createIncomingConnection(Peer peer, SocketChannel channel) {
        return createConnection(peer, null, channel, true);
    }

    private ConnectionResult createConnection(Peer peer, TorrentId torrentId, SocketChannel channel, boolean incoming) {
        BorrowedBuffer in = bufferManager.borrowByteBuffer();
        BorrowedBuffer out = bufferManager.borrowByteBuffer();
        try {
            return _createConnection(peer, torrentId, channel, incoming, in, out);
        } catch (Exception e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Failed to establish connection with peer: {}. Reason: {} ({})",
                        peer, e.getClass().getName(), e.getMessage());
            }
            closeQuietly(channel);
            releaseBuffer(in);
            releaseBuffer(out);
            return ConnectionResult.failure("Unexpected error", e);
        }
    }

    private ConnectionResult _createConnection(
            Peer peer,
            TorrentId torrentId,
            SocketChannel channel,
            boolean incoming,
            BorrowedBuffer in,
            BorrowedBuffer out) throws IOException {

        // sanity check
        if (!incoming && torrentId == null) {
            throw new IllegalStateException("Requested outgoing connection without torrent ID. Peer: " + peer);
        }

        channel.configureBlocking(false);

        ByteBuffer inBuffer = in.lockAndGet();
        ByteBuffer outBuffer = out.lockAndGet();
        Optional cipherOptional;
        try {
            if (incoming) {
                cipherOptional = cryptoHandshakeProcessor.negotiateIncoming(peer, channel, inBuffer, outBuffer);
            } else {
                cipherOptional = cryptoHandshakeProcessor.negotiateOutgoing(peer, channel, torrentId, inBuffer, outBuffer);
            }
        } finally {
            in.unlock();
            out.unlock();
        }

        ChannelPipeline pipeline = createPipeline(peer, channel, in, out, cipherOptional);
        ChannelHandler channelHandler = new SocketChannelHandler(channel, in, out, pipeline::bindHandler, dataReceiver);
        channelHandler.register();

        PeerConnection connection = new SocketPeerConnection(peer, channelHandler);
        ConnectionHandler connectionHandler;
        if (incoming) {
            connectionHandler = connectionHandlerFactory.getIncomingHandler();
        } else {
            connectionHandler = connectionHandlerFactory.getOutgoingHandler(torrentId);
        }
        boolean inited = initConnection(connection, connectionHandler);
        if (inited) {
            subscribeHandler(connection.getTorrentId(), channelHandler);
            return ConnectionResult.success(connection);
        } else {
            connection.closeQuietly();
            return ConnectionResult.failure("Handshake failed");
        }
    }

    private void subscribeHandler(TorrentId torrentId, ChannelHandler channelHandler) {
        eventSource.onTorrentStarted(event -> {
            if (event.getTorrentId().equals(torrentId)) {
                channelHandler.activate();
            }
        });
        eventSource.onTorrentStopped(event -> {
            if (event.getTorrentId().equals(torrentId)) {
                channelHandler.deactivate();
            }
        });
    }

    private ChannelPipeline createPipeline(
            Peer peer,
            ByteChannel channel,
            BorrowedBuffer in,
            BorrowedBuffer out,
            Optional cipherOptional) {

        ChannelPipelineBuilder builder = channelPipelineFactory.buildPipeline(peer);
        builder.channel(channel);
        builder.protocol(protocol);
        builder.inboundBuffer(in);
        builder.outboundBuffer(out);

        cipherOptional.ifPresent(cipher -> {
            builder.decoders(new CipherBufferMutator(cipher.getDecryptionCipher()));
            builder.encoders(new CipherBufferMutator(cipher.getEncryptionCipher()));
        });

        return builder.build();
    }

    private boolean initConnection(PeerConnection newConnection, ConnectionHandler connectionHandler) {
        boolean success = connectionHandler.handleConnection(newConnection);
        if (success) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Successfully initialized newly established connection to peer: {}, handshake handler: {}",
                        newConnection.getRemotePeer(), connectionHandler.getClass().getName());
            }
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Failed to initialize newly established connection to peer: {}, handshake handler: {}",
                        newConnection.getRemotePeer(), connectionHandler.getClass().getName());
            }
        }
        return success;
    }

    private void closeQuietly(SocketChannel channel) {
        if (channel != null && channel.isOpen()) {
            try {
                channel.close();
            } catch (IOException e1) {
                try {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Failed to close outgoing channel: {}. Reason: {} ({})",
                                channel.getRemoteAddress(), e1.getClass().getName(), e1.getMessage());
                    }
                } catch (IOException e2) {
                    // ignore
                }
            }
        }
    }

    private void releaseBuffer(BorrowedBuffer buffer) {
        try {
            buffer.release();
        } catch (Exception e) {
            LOGGER.error("Failed to release buffer", e);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy