package com.limegroup.gnutella.messages; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.limewire.core.settings.ConnectionSettings; import org.limewire.io.IpPort; import org.limewire.io.NetworkUtils; import org.limewire.security.SecurityToken; import com.google.inject.Inject; import com.google.inject.Singleton; import com.limegroup.gnutella.ApplicationServices; import com.limegroup.gnutella.ConnectionManager; import com.limegroup.gnutella.NetworkManager; import com.limegroup.gnutella.Response; import com.limegroup.gnutella.UploadManager; import com.limegroup.gnutella.util.DataUtils; import com.limegroup.gnutella.xml.LimeXMLDocumentHelper; import com.limegroup.gnutella.xml.LimeXMLUtils; @Singleton public class OutgoingQueryReplyFactoryImpl implements OutgoingQueryReplyFactory { private final QueryReplyFactory queryReplyFactory; private final UploadManager uploadManager; private final NetworkManager networkManager; private final ApplicationServices applicationServices; private final ConnectionManager connectionManager; @Inject public OutgoingQueryReplyFactoryImpl(QueryReplyFactory queryReplyFactory, UploadManager uploadManager, NetworkManager networkManager, ApplicationServices applicationServices, ConnectionManager connectionManager) { this.queryReplyFactory = queryReplyFactory; this.uploadManager = uploadManager; this.networkManager = networkManager; this.applicationServices = applicationServices; this.connectionManager = connectionManager; } public List<QueryReply> createReplies(Response[] responses, QueryRequest queryRequest, SecurityToken securityToken, int responsesPerReply) { // We only want to return a "reply to multicast query" QueryReply // if the request travelled a single hop. boolean isMulticast = queryRequest.isMulticast() && (queryRequest.getTTL() + queryRequest.getHops()) == 1; byte ttl = isMulticast ? 1 : (byte)(queryRequest.getHops() + 1); return createReplies(responses, responsesPerReply, securityToken, queryRequest.getGUID(), ttl, isMulticast, queryRequest.canDoFirewalledTransfer()); } public List<QueryReply> createReplies(Response[] responses, int responsesPerReply, SecurityToken securityToken, byte[] guid, byte ttl, boolean isMulticast, boolean requestorCanDoFWT) { List<Response[]> splitResponses = splitResponses(responses, responsesPerReply); List<QueryReply> replies = new ArrayList<QueryReply>(); for (Response[] bundle : splitResponses) { replies.addAll(createReplies(bundle, securityToken, guid, ttl, isMulticast, requestorCanDoFWT)); } return replies; } static List<Response[]> splitResponses(Response[] responses, int responsesPerReply) { if (responses.length <= responsesPerReply) { return Collections.singletonList(responses); } List<Response[]> results = new ArrayList<Response[]>(); for (int i = 0; i < responses.length; i += responsesPerReply) { Response[] copy = new Response[Math.min(responsesPerReply, responses.length - i)]; System.arraycopy(responses, i, copy, 0, copy.length); results.add(copy); } return results; } /** * @param securityToken might be null, otherwise must be sent in GGEP * of QHD with header "SO" */ public List<QueryReply> createReplies(Response[] responses, SecurityToken securityToken, byte[] guid, byte ttl, boolean isMulticast, boolean requestorCanDoFWT) { if (responses.length == 0) { return Collections.emptyList(); } // We should mark our hits if the remote end can do a firewalled // transfer AND so can we AND we don't accept tcp incoming AND our // external address is valid (needed for input into the reply) boolean isFWTransfer = requestorCanDoFWT && networkManager.canDoFWT() && !networkManager.acceptedIncomingConnection(); // see if there are any open slots // Note: if we are busy, non-metafile results would be filtered. // by this point. boolean isBusy = !uploadManager.mayBeServiceable(); boolean hasUploaded = uploadManager.hadSuccesfulUpload(); byte[] clientGUID = applicationServices.getMyGUID(); long speed = uploadManager.measuredUploadSpeed(); boolean measuredSpeed = true; if (speed == -1) { speed = ConnectionSettings.CONNECTION_SPEED.getValue(); measuredSpeed = false; } List<QueryReply> queryReplies = new ArrayList<QueryReply>(); QueryReply queryReply = null; // pick the right address & port depending on multicast & fwtrans // if we cannot find a valid address & port, exit early. int port = -1; byte[] ip = null; // first try using multicast addresses & ports, but if they're // invalid, fallback to non multicast. if(isMulticast) { ip = networkManager.getNonForcedAddress(); port = networkManager.getNonForcedPort(); if(!NetworkUtils.isValidPort(port) || !NetworkUtils.isValidAddress(ip)) isMulticast = false; } if(!isMulticast) { // see if we have a valid FWTrans address. if not, fall back. if(isFWTransfer) { port = networkManager.getStableUDPPort(); ip = networkManager.getExternalAddress(); if(!NetworkUtils.isValidAddress(ip) || !NetworkUtils.isValidPort(port)) isFWTransfer = false; } // if we still don't have a valid address here, exit early. if(!isFWTransfer) { ip = networkManager.getAddress(); port = networkManager.getPort(); if(!NetworkUtils.isValidAddress(ip) || !NetworkUtils.isValidPort(port)) return Collections.emptyList(); } } // get the xml collection string... String xmlCollectionString = LimeXMLDocumentHelper.getAggregateString(responses); if (xmlCollectionString == null) xmlCollectionString = ""; byte[] xmlBytes = null; try { xmlBytes = xmlCollectionString.getBytes("UTF-8"); } catch(UnsupportedEncodingException ueex) { throw new IllegalStateException(ueex); } // get the *latest* push proxies if we have not accepted an incoming // connection in this session boolean notIncoming = !networkManager.acceptedIncomingConnection(); Set<? extends IpPort> proxies = notIncoming ? connectionManager.getPushProxies() : null; // it may be too big.... if (xmlBytes.length > QueryReply.XML_MAX_SIZE) { // ok, need to partition responses up once again and send out // multiple query replies..... List<Response[]> splitResps = new LinkedList<Response[]>(); splitAndAddResponses(splitResps, responses); while (!splitResps.isEmpty()) { Response[] currResps = splitResps.remove(0); String currXML = LimeXMLDocumentHelper.getAggregateString(currResps); byte[] currXMLBytes = null; try { currXMLBytes = currXML.getBytes("UTF-8"); } catch(UnsupportedEncodingException ueex) { throw new IllegalStateException(ueex); } if ((currXMLBytes.length > QueryReply.XML_MAX_SIZE) && (currResps.length > 1)) splitAndAddResponses(splitResps, currResps); else { // create xml bytes if possible... byte[] xmlCompressed = null; if (!currXML.equals("")) xmlCompressed = LimeXMLUtils.compress(currXMLBytes); else //there is no XML xmlCompressed = DataUtils.EMPTY_BYTE_ARRAY; // create the new queryReply queryReply = queryReplyFactory.createQueryReply(guid, ttl, port, ip, speed, currResps, clientGUID, xmlCompressed, notIncoming, isBusy, hasUploaded, measuredSpeed, false /* chat */, isMulticast, isFWTransfer, proxies, securityToken); queryReplies.add(queryReply); } } } else { // xml is small enough, no problem..... // get xml bytes if possible.... byte[] xmlCompressed = null; if (!xmlCollectionString.equals("")) xmlCompressed = LimeXMLUtils.compress(xmlBytes); else //there is no XML xmlCompressed = DataUtils.EMPTY_BYTE_ARRAY; // create the new queryReply queryReply = queryReplyFactory.createQueryReply(guid, ttl, port, ip, speed, responses, clientGUID, xmlCompressed, notIncoming, isBusy, hasUploaded, measuredSpeed, false /* chat */, isMulticast, isFWTransfer, proxies, securityToken); queryReplies.add(queryReply); } return queryReplies; } /** @return Simply splits the input array into two (almost) equally sized * arrays. */ private static Response[][] splitResponses(Response[] in) { int middle = in.length/2; Response[][] retResps = new Response[2][]; retResps[0] = new Response[middle]; retResps[1] = new Response[in.length-middle]; for (int i = 0; i < middle; i++) retResps[0][i] = in[i]; for (int i = 0; i < (in.length-middle); i++) retResps[1][i] = in[i+middle]; return retResps; } private static void splitAndAddResponses(List<Response[]> addTo, Response[] toSplit) { Response[][] splits = splitResponses(toSplit); addTo.add(splits[0]); addTo.add(splits[1]); } }