/*
* Copyright 2009 Thomas Bocek
*
* 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 net.tomp2p.rpc;
import io.netty.channel.ChannelHandlerContext;
import java.io.IOException;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
import net.tomp2p.connection2.ChannelCreator;
import net.tomp2p.connection2.ConnectionBean;
import net.tomp2p.connection2.ConnectionConfiguration;
import net.tomp2p.connection2.PeerBean;
import net.tomp2p.connection2.RequestHandler;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Message2;
import net.tomp2p.message.Message2.Type;
import net.tomp2p.message.TrackerData;
import net.tomp2p.p2p.builder.AddTrackerBuilder;
import net.tomp2p.p2p.builder.GetTrackerBuilder;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.storage.Data;
import net.tomp2p.storage.TrackerStorage;
import net.tomp2p.storage.TrackerStorage.ReferrerType;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TrackerRPC extends DispatchHandler {
private static final Logger LOG = LoggerFactory.getLogger(TrackerRPC.class);
public static final byte TRACKER_ADD_COMMAND = 8;
public static final byte TRACKER_GET_COMMAND = 9;
public static final int MAX_MSG_SIZE_UDP = 35;
// final private ConnectionConfiguration p2pConfiguration;
/**
* @param peerBean
* @param connectionBean
*/
public TrackerRPC(final PeerBean peerBean, final ConnectionBean connectionBean) {
super(peerBean, connectionBean, TRACKER_ADD_COMMAND, TRACKER_GET_COMMAND);
}
public static boolean isPrimary(FutureResponse response) {
return response.getRequest().getType() == Type.REQUEST_3;
}
public static boolean isSecondary(FutureResponse response) {
return response.getRequest().getType() == Type.REQUEST_1;
}
public FutureResponse addToTracker(final PeerAddress remotePeer, AddTrackerBuilder builder,
ChannelCreator channelCreator) {
Utils.nullCheck(remotePeer, builder.getLocationKey(), builder.getDomainKey());
final Message2 message = createMessage(remotePeer, TRACKER_ADD_COMMAND,
builder.isPrimary() ? Type.REQUEST_3 : Type.REQUEST_1);
if (builder.isMessageSign()) {
message.setPublicKeyAndSign(peerBean().getKeyPair());
}
message.setKey(builder.getLocationKey());
message.setKey(builder.getDomainKey());
if (builder.getBloomFilter() != null) {
message.setBloomFilter(builder.getBloomFilter());
}
final FutureResponse futureResponse = new FutureResponse(message);
final TrackerRequest<FutureResponse> requestHandler = new TrackerRequest<FutureResponse>(
futureResponse, peerBean(), connectionBean(), message, builder.getLocationKey(),
builder.getDomainKey(), builder);
TrackerData trackerData = new TrackerData(new HashMap<PeerAddress, Data>(), null);
trackerData.put(peerBean().serverPeerAddress(), builder.getAttachement());
message.setTrackerData(trackerData);
if (builder.isForceTCP()) {
return requestHandler.sendTCP(channelCreator);
} else {
return requestHandler.sendUDP(channelCreator);
}
}
public FutureResponse getFromTracker(final PeerAddress remotePeer, GetTrackerBuilder builder,
ChannelCreator channelCreator) {
//final Number160 locationKey,
//final Number160 domainKey, boolean expectAttachement, boolean signMessage,
//Set<Number160> knownPeers,
Utils.nullCheck(remotePeer, builder.getLocationKey(), builder.getDomainKey());
final Message2 message = createMessage(remotePeer, TRACKER_GET_COMMAND, Type.REQUEST_1);
if (builder.isSignMessage()) {
message.setPublicKeyAndSign(peerBean().getKeyPair());
}
message.setKey(builder.getLocationKey());
message.setKey(builder.getDomainKey());
//TODO: make this always a bloom filter
if (builder.getKnownPeers() != null && (builder.getKnownPeers() instanceof SimpleBloomFilter)) {
message.setBloomFilter((SimpleBloomFilter<Number160>) builder.getKnownPeers());
}
FutureResponse futureResponse = new FutureResponse(message);
final TrackerRequest<FutureResponse> requestHandler = new TrackerRequest<FutureResponse>(
futureResponse, peerBean(), connectionBean(), message, builder.getLocationKey(), builder.getDomainKey(), builder);
if ((builder.isExpectAttachement() || builder.isForceTCP())) {
return requestHandler.sendTCP(channelCreator);
} else {
return requestHandler.sendUDP(channelCreator);
}
}
@Override
public Message2 handleResponse(Message2 message, boolean sign) throws Exception {
if (!((message.getType() == Type.REQUEST_1 || message.getType() == Type.REQUEST_3)
&& message.getKey(0) != null && message.getKey(1) != null)) {
throw new IllegalArgumentException("Message content is wrong");
}
final Message2 responseMessage = createResponseMessage(message, Type.OK);
// get data
Number160 locationKey = message.getKey(0);
Number160 domainKey = message.getKey(1);
SimpleBloomFilter<Number160> knownPeers = message.getBloomFilter(0);
PublicKey publicKey = message.getPublicKey();
//
final TrackerStorage trackerStorage = peerBean().trackerStorage();
TrackerData meshPeers = trackerStorage.meshPeers(locationKey, domainKey);
boolean couldProvideMoreData = false;
if (meshPeers != null) {
if (knownPeers != null) {
meshPeers = Utils.disjunction(meshPeers, knownPeers);
}
int size = meshPeers.size();
meshPeers = Utils.limit(meshPeers, TrackerRPC.MAX_MSG_SIZE_UDP);
couldProvideMoreData = size > meshPeers.size();
responseMessage.setTrackerData(meshPeers);
}
if (couldProvideMoreData) {
responseMessage.setType(Message2.Type.PARTIALLY_OK);
}
if (message.getCommand() == TRACKER_ADD_COMMAND) {
TrackerData trackerData = message.getTrackerData(0);
if (trackerData.size() != 1) {
responseMessage.setType(Message2.Type.EXCEPTION);
} else {
Map.Entry<PeerAddress, Data> entry = trackerData.getPeerAddresses().entrySet().iterator()
.next();
if (!trackerStorage.put(locationKey, domainKey, entry.getKey(), publicKey, entry.getValue())) {
responseMessage.setType(Message2.Type.DENIED);
LOG.debug("tracker NOT put on({}) locationKey:{}, domainKey:{}, address:{}", peerBean()
.serverPeerAddress(), locationKey, domainKey, entry.getKey());
} else {
LOG.debug("tracker put on({}) locationKey:{}, domainKey:{}, address: {} sizeP: {}",
peerBean().serverPeerAddress(), locationKey, domainKey, entry.getKey(),
trackerStorage.sizePrimary(locationKey, domainKey));
}
}
} else {
LOG.debug("tracker get on({}) locationKey:{}, domainKey:{}, address:{}, returning: {}",
peerBean().serverPeerAddress(), locationKey, domainKey, message.getSender(),
(meshPeers == null ? "0" : meshPeers.size()));
}
if (sign) {
responseMessage.setPublicKeyAndSign(peerBean().getKeyPair());
}
return responseMessage;
}
private class TrackerRequest<K> extends RequestHandler<FutureResponse> {
private final Message2 message;
private final Number160 locationKey;
private final Number160 domainKey;
public TrackerRequest(FutureResponse futureResponse, PeerBean peerBean,
ConnectionBean connectionBean, Message2 message, Number160 locationKey, Number160 domainKey,
ConnectionConfiguration configuration) {
super(futureResponse, peerBean, connectionBean, configuration);
this.message = message;
this.locationKey = locationKey;
this.domainKey = domainKey;
}
protected void channelRead0(final ChannelHandlerContext ctx, final Message2 responseMessage)
throws Exception {
preHandleMessage(responseMessage, peerBean().trackerStorage(), this.message.getRecipient(),
locationKey, domainKey);
super.channelRead0(ctx, responseMessage);
}
}
private void preHandleMessage(Message2 message, TrackerStorage trackerStorage, PeerAddress referrer,
Number160 locationKey, Number160 domainKey) throws IOException, ClassNotFoundException {
// Since I might become a tracker as well, we keep this information
// about those trackers.
TrackerData tmp = message.getTrackerData(0);
// no data found
if (tmp == null || tmp.size() == 0) {
return;
}
for (Map.Entry<PeerAddress, Data> trackerData : tmp.getPeerAddresses().entrySet()) {
// we don't know the public key, since this is not first hand
// information.
// TTL will be set in tracker storage, so don't worry about it here.
trackerStorage.putReferred(locationKey, domainKey, trackerData.getKey(), referrer,
trackerData.getValue(), ReferrerType.MESH);
}
}
}