bt.torrent.messaging.RoutingPeerWorker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bt-core Show documentation
Show all versions of bt-core Show documentation
BitTorrent Client Library (Core)
The newest version!
/*
* Copyright (c) 2016—2021 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.torrent.messaging;
import bt.net.ConnectionKey;
import bt.protocol.Cancel;
import bt.protocol.Choke;
import bt.protocol.Interested;
import bt.protocol.Message;
import bt.protocol.NotInterested;
import bt.protocol.Piece;
import bt.protocol.Unchoke;
import java.util.Deque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.function.Consumer;
/**
* Messaging interface, that encapsulates all messaging agents (consumers and producers)
* for a particular torrent processing session.
* Routes incoming messages directly to interested consumers, based on message types.
*
* @since 1.0
*/
class RoutingPeerWorker implements PeerWorker {
private ConnectionState connectionState;
private MessageRouter router;
private MessageContext context;
private Deque outgoingMessages;
private Choker choker;
private Consumer onUpdateConnection = this::postMessage;
private Consumer onGet = this::postMessage;
public RoutingPeerWorker(ConnectionKey connectionKey, MessageRouter router) {
this.connectionState = new ConnectionState();
this.router = router;
this.context = new MessageContext(connectionKey, connectionState);
this.outgoingMessages = new LinkedBlockingDeque<>();
this.choker = Choker.choker();
}
@Override
public ConnectionState getConnectionState() {
return connectionState;
}
@Override
public void accept(Message message) {
router.consume(message, context);
updateConnection();
}
private void postMessage(Message message) {
if (isUrgent(message)) {
addUrgent(message);
} else {
add(message);
}
}
private boolean isUrgent(Message message) {
// TODO: this should be done based on priorities
Class extends Message> messageType = message.getClass();
return Choke.class.equals(messageType) || Unchoke.class.equals(messageType) || Cancel.class.equals(messageType);
}
private void add(Message message) {
outgoingMessages.add(message);
}
private void addUrgent(Message message) {
outgoingMessages.addFirst(message);
}
@Override
public Message get() {
if (outgoingMessages.isEmpty()) {
router.produce(onGet, context);
updateConnection();
}
return postProcessOutgoingMessage(outgoingMessages.poll());
}
private Message postProcessOutgoingMessage(Message message) {
if (message == null) {
return null;
}
Class extends Message> messageType = message.getClass();
if (Piece.class.equals(messageType)) {
Piece piece = (Piece) message;
// check that peer hadn't sent cancel while we were preparing the requested block
if (isCancelled(piece)) {
// dispose of message
return null;
} else {
connectionState.incrementUploaded(piece.getLength());
}
}
if (Interested.class.equals(messageType)) {
connectionState.setInterested(true);
}
if (NotInterested.class.equals(messageType)) {
connectionState.setInterested(false);
}
if (Choke.class.equals(messageType)) {
connectionState.setShouldChoke(true);
}
if (Unchoke.class.equals(messageType)) {
connectionState.setShouldChoke(false);
}
return message;
}
private boolean isCancelled(Piece piece) {
int pieceIndex = piece.getPieceIndex(),
offset = piece.getOffset(),
length = piece.getLength();
return connectionState.getCancelledPeerRequests().remove(Mapper.mapper().buildKey(pieceIndex, offset, length));
}
private void updateConnection() {
choker.handleConnection(connectionState, onUpdateConnection);
}
}