package com.limegroup.gnutella.messages.vendor;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.messages.BadGGEPBlockException;
import com.limegroup.gnutella.messages.BadGGEPPropertyException;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.messages.GGEP;
/**
* An UDP equivalent of the HEAD request method with a twist.
*
* Eventually, it will be routed like a push request
* to firewalled alternate locations.
*
* As long as the pinging host can receive solicited udp
* it can be firewalled as well.
*
* Illustration of [firewalled] NodeA pinging firewalled host NodeB:
*
*
* NodeA --------(PUSH_PING,udp)-------->Push
* <-------------------(udp)--------- Proxy
* /|\ | (tcp)
* | |
* | \|/
* NodeB
*
*/
public class HeadPing extends VendorMessage {
public static final int VERSION = 1;
/**
* requsted content of the pong
*/
public static final int PLAIN = 0x0;
public static final int INTERVALS = 0x1;
public static final int ALT_LOCS = 0x2;
public static final int PUSH_ALTLOCS=0x4;
public static final int FWT_PUSH_ALTLOCS=0x8;
public static final int GGEP_PING=0x10;
/**
* a ggep field name containing the client guid of the node we would like
* this ping routed to.
*/
private static final String GGEP_PUSH = "PUSH";
/**
* the feature mask.
*/
public static final int FEATURE_MASK=0x1F;
/** The URN of the file being requested */
private final URN _urn;
/** The format of the response that we desire */
private final byte _features;
/** The GGEP fields in this pong, if any */
private GGEP _ggep;
/**
* The client GUID of the host we wish this ping routed to.
* null if pinging directly.
*/
private final GUID _clientGUID;
/**
* creates a message object with data from the network.
*/
protected HeadPing(byte[] guid, byte ttl, byte hops,
int version, byte[] payload)
throws BadPacketException {
super(guid, ttl, hops, F_LIME_VENDOR_ID, F_UDP_HEAD_PING, version, payload);
//see if the payload is valid
if (getVersion() == VERSION && (payload == null || payload.length < 42))
throw new BadPacketException();
_features = (byte) (payload [0] & FEATURE_MASK);
//parse the urn string.
String urnStr = new String(payload,1,41);
if (!URN.isUrn(urnStr))
throw new BadPacketException("udp head request did not contain an urn");
URN urn = null;
try {
urn = URN.createSHA1Urn(urnStr);
}catch(IOException oops) {
throw new BadPacketException("failed to parse an urn");
}finally {
_urn = urn;
}
// parse the GGEP if any
GGEP g = null;
if ((_features & GGEP_PING) == GGEP_PING) {
if (payload.length < 43)
throw new BadPacketException("no ggep was found.");
try {
g = new GGEP(payload, 42, null);
} catch (BadGGEPBlockException bpx) {
throw new BadPacketException("invalid ggep block");
}
}
_ggep = g;
// extract the client guid if any
GUID clientGuid = null;
if (_ggep != null) {
try {
clientGuid = new GUID(_ggep.getBytes(GGEP_PUSH));
} catch (BadGGEPPropertyException noGuid) {}
}
_clientGUID=clientGuid;
}
/**
* creates a new udp head request.
* @param sha1 the urn to get information about.
* @param features which features to include in the response
*/
public HeadPing(GUID g, URN sha1, int features) {
this (g,sha1, null, features);
}
public HeadPing(GUID g, URN sha1, GUID clientGUID, int features) {
super(F_LIME_VENDOR_ID, F_UDP_HEAD_PING, VERSION,
derivePayload(sha1, clientGUID, features));
_features = (byte)(features & FEATURE_MASK);
_urn = sha1;
_clientGUID = clientGUID;
setGUID(g);
}
/**
* creates a plain udp head request
*/
public HeadPing (URN urn) {
this(new GUID(GUID.makeGuid()),urn, PLAIN);
}
/**
* creates a duplicate ping with ttl and hops appropriate for a new
* vendor message
*/
public HeadPing (HeadPing original) {
super(F_LIME_VENDOR_ID,F_UDP_HEAD_PING,VERSION,original.getPayload());
_features = original.getFeatures();
_urn = original.getUrn();
_clientGUID = original.getClientGuid();
setGUID(new GUID(original.getGUID()));
}
private static byte [] derivePayload(URN urn, GUID clientGUID, int features) {
features = features & FEATURE_MASK;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream daos = new DataOutputStream(baos);
String urnStr = urn.httpStringValue();
GGEP ggep = null;
if (clientGUID != null) {
features |= GGEP_PING; // make sure we indicate we'll have ggep.
ggep = new GGEP(true);
ggep.put(GGEP_PUSH,clientGUID.bytes());
}
try {
daos.writeByte(features);
daos.writeBytes(urnStr);
if ( ggep != null)
ggep.write(daos);
}catch (IOException huh) {
ErrorService.error(huh);
}
return baos.toByteArray();
}
/**
*
* @return the URN carried in this head request.
*/
public URN getUrn() {
return _urn;
}
public boolean requestsRanges() {
return (_features & INTERVALS) == INTERVALS;
}
public boolean requestsAltlocs() {
return (_features & ALT_LOCS) == ALT_LOCS;
}
public boolean requestsPushLocs() {
return (_features & PUSH_ALTLOCS) == PUSH_ALTLOCS;
}
public boolean requestsFWTPushLocs() {
return (_features & FWT_PUSH_ALTLOCS) == FWT_PUSH_ALTLOCS;
}
public byte getFeatures() {
return _features;
}
public GUID getClientGuid() {
return _clientGUID;
}
}