package com.limegroup.gnutella; import static com.limegroup.gnutella.Constants.MAX_FILE_SIZE; import com.limegroup.gnutella.downloader.RemoteFileDescFactory; import com.limegroup.gnutella.messages.QueryReply; import com.limegroup.gnutella.xml.LimeXMLDocument; import org.limewire.collection.IntervalSet; import org.limewire.io.Address; import org.limewire.io.ConnectableImpl; import org.limewire.io.IpPort; import org.limewire.service.ErrorService; import org.limewire.util.ByteUtils; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.UnknownHostException; import java.util.Collections; import java.util.Locale; import java.util.Set; class ResponseImpl implements Response { //private static final Log LOG = LogFactory.getLog(Response.class); /** Both index and size must fit into 4 unsigned bytes; see * constructor for details. */ private final long index; private final long size; /** * The bytes for the name string, guaranteed to be non-null. */ private final byte[] nameBytes; /** The name of the file matching the search. This does NOT * include the double null terminator. */ private final String name; /** * The size of the byte array for the response object */ private final int incomingNameByteArraySize; /** The document representing the XML in this response. */ private LimeXMLDocument document; /** * The <tt>Set</tt> of <tt>URN</tt> instances for this <tt>Response</tt>, * as specified in HUGE v0.94. This is guaranteed to be non-null, * although it is often empty. */ private final Set<URN> urns; /** * The bytes between the nulls for the <tt>Response</tt>, as specified * in HUGE v0.94. This is guaranteed to be non-null, although it can be * an empty array. */ private final byte[] extBytes; /** * The cached RemoteFileDesc created from this Response. */ private volatile RemoteFileDesc cachedRFD; /** * If this is a response for a metafile, i.e. a file * that itself triggers another download. */ private final boolean isMetaFile; /** The alternate locations for this Response. */ private final Set<? extends IpPort> alternateLocations; /** The creation time for this Response. */ private final long creationTime; /** Ranges carried in this response, null if none */ private final IntervalSet ranges; /** If the ranges carried in this response are verified */ private final boolean verified; /** * Overloaded constructor that allows the creation of Responses with * meta-data and a <tt>Set</tt> of <tt>URN</tt> instances. This * is the primary constructor that establishes all of the class's * invariants, does any necessary parameter validation, etc. * * If extensions is non-null, it is used as the extBytes instead * of creating them from the urns and locations. * * @param index the index of the file referenced in the response * @param size the size of the file (in bytes) * @param name the name of the file * @param incomingNameByteArraySize the number of bytes that were * used to encode the file name in the message, used to compute * the correct message length when reading from the network * @param urns the <tt>Set</tt> of <tt>URN</tt> instances associated * with the file * @param doc the <tt>LimeXMLDocument</tt> instance associated with * the file * @param alternateLocations Other hosts with this file * @param extensions The raw unparsed extension bytes. * @param ranges Ranges of data to be represented by this response */ public ResponseImpl(long index, long size, String name, int incomingNameByteArraySize, Set<? extends URN> urns, LimeXMLDocument doc, Set<? extends IpPort> alternateLocations, long creationTime, byte[] extensions, IntervalSet ranges, boolean verified) { if( (index & 0xFFFFFFFF00000000L)!=0 ) throw new IllegalArgumentException("invalid index: " + index); // see note in createFromStream about Integer.MAX_VALUE if (size < 0 || size > MAX_FILE_SIZE) throw new IllegalArgumentException("invalid size: " + size); this.index=index; this.size=size; if (name == null) this.name = ""; else this.name = name; isMetaFile = this.name.toLowerCase(Locale.US).endsWith(".torrent"); byte[] temp = null; try { temp = this.name.getBytes("UTF-8"); } catch(UnsupportedEncodingException namex) { //b/c this should never happen, we will show and error //if it ever does for some reason. ErrorService.error(namex); } this.nameBytes = temp; if (urns == null) this.urns = Collections.emptySet(); else this.urns = Collections.unmodifiableSet(urns); this.alternateLocations = alternateLocations; this.creationTime = creationTime; this.extBytes = extensions; this.incomingNameByteArraySize = incomingNameByteArraySize; this.document = doc; this.ranges = ranges; this.verified = verified; } public void writeToStream(OutputStream os) throws IOException { ByteUtils.int2leb((int)index, os); if (size > Integer.MAX_VALUE) ByteUtils.int2leb(0xFFFFFFFF, os); else ByteUtils.int2leb((int)size, os); for (int i = 0; i < nameBytes.length; i++) os.write(nameBytes[i]); //Write first null terminator. os.write(0); // write HUGE v0.93 General Extension Mechanism extensions // (currently just URNs) for (int i = 0; i < extBytes.length; i++) os.write(extBytes[i]); //add the second null terminator os.write(0); } public void setDocument(LimeXMLDocument doc) { document = doc; } public int getIncomingLength() { // must match same number of bytes of Response when initially read from the network if(incomingNameByteArraySize != -1){ return 8 + // index and size incomingNameByteArraySize + 1 + // null extBytes.length + 1; // final null } return 8 + nameBytes.length + 1 + extBytes.length + 1; } public long getIndex() { return index; } public long getSize() { return size; } public String getName() { return name; } public LimeXMLDocument getDocument() { return document; } public Set<URN> getUrns() { return urns; } public Set<? extends IpPort> getLocations() { return alternateLocations; } public long getCreateTime() { return creationTime; } public boolean isMetaFile() { return isMetaFile; } public byte[] getExtBytes() { return extBytes; } public IntervalSet getRanges() { return ranges; } public boolean isVerified() { return verified; } public RemoteFileDesc toRemoteFileDesc(QueryReply queryReply, Address address, RemoteFileDescFactory remoteFileDescFactory, PushEndpointFactory pushEndpointFactory) throws UnknownHostException { // TODO fberger move this to query reply, since all responses can share a common address if (address == null) { if (queryReply.isFirewalled()) { address = pushEndpointFactory.createPushEndpoint(queryReply.getClientGUID(), queryReply.getPushProxies(), PushEndpoint.PLAIN, queryReply.getFWTransferVersion(), new ConnectableImpl(queryReply.getIP(), queryReply.getPort(), queryReply.isTLSCapable())); } else { address = new ConnectableImpl(queryReply.getIP(), queryReply.getPort(), queryReply.isTLSCapable()); } } if (cachedRFD != null && cachedRFD.getAddress().equals(address)) { return cachedRFD; } else { RemoteFileDesc rfd = remoteFileDescFactory.createRemoteFileDesc(address, getIndex(), getName(), getSize(), queryReply.getClientGUID(), queryReply.getSpeed(), queryReply.calculateQualityOfService(), queryReply.getSupportsBrowseHost(), getDocument(), getUrns(), queryReply.isReplyToMulticastQuery(), queryReply.getVendor(), getCreateTime()); cachedRFD = rfd; return rfd; } } /** * Overrides equals to check that these two responses are equal. * Raw extension bytes are not checked, because they may be * extensions that do not change equality, such as * otherLocations. */ @Override public boolean equals(Object o) { if(o == this) return true; if (! (o instanceof Response)) return false; Response r=(Response)o; return getIndex() == r.getIndex() && getSize() == r.getSize() && getName().equals(r.getName()) && ((getDocument() == null) ? (r.getDocument() == null) : getDocument().equals(r.getDocument())) && getUrns().equals(r.getUrns()); } @Override public int hashCode() { return (int)((31 * 31 * getName().hashCode() + 31 * getSize()+getIndex())); } /** * Overrides Object.toString to print out a more informative message. */ @Override public String toString() { return ("index: "+index+"\r\n"+ "size: "+size+"\r\n"+ "name: "+name+"\r\n"+ "xml document: "+document+"\r\n"+ "urns: "+urns); } }