package com.limegroup.gnutella.messages.vendor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import com.limegroup.gnutella.ByteOrder;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.statistics.SentMessageStatHandler;
import com.limegroup.gnutella.util.NetworkUtils;
/** In Vendor Message parlance, the "message type" of this VMP is "GTKG/7".
* Used to ask a host you connect to do a UDP ConnectBack.
*
* VERSIONING INFO:
* -------------------------
* Version 2 of this message will fold the connect back guid into the guid
* of the message. In order to transition, we should follow a 3 step process:
* 1) allow this class to accept version 2 format
* 2) after 1) has been released for a while, start using version 2
* 3) some time after 2), stop accepting 1) (optional)
*/
public final class UDPConnectBackVendorMessage extends VendorMessage {
public static final int VERSION = 2;
/** The payload has a 16-bit unsigned value - the port - at which one should
* connect back.
*/
private final int _port;
/** The GUID that should be used for connect back.
*/
private final GUID _guid;
/** The encoding of the port and the guid.
*/
/** The network constructor. */
UDPConnectBackVendorMessage(byte[] guid, byte ttl, byte hops, int version,
byte[] payload) throws BadPacketException {
super(guid, ttl, hops, F_GTKG_VENDOR_ID, F_UDP_CONNECT_BACK,
version, payload);
try {
payload = getPayload();
ByteArrayInputStream bais;
switch(getVersion()) {
case 1:
if( payload.length != 18 )
throw new BadPacketException("invalid version1 payload");
bais = new ByteArrayInputStream(payload);
_port = ByteOrder.ushort2int(ByteOrder.leb2short(bais));
byte[] guidBytes = new byte[16];
int bytesRead = bais.read(guidBytes, 0, guidBytes.length);
_guid = new GUID(guidBytes);
break;
case 2:
if( payload.length != 2 )
throw new BadPacketException("invalid version2 payload");
bais = new ByteArrayInputStream(payload);
_port = ByteOrder.ushort2int(ByteOrder.leb2short(bais));
_guid = new GUID(super.getGUID());
break;
default:
throw new BadPacketException("Unsupported Version");
}
if( !NetworkUtils.isValidPort(_port) )
throw new BadPacketException("invalid connectback port.");
}
catch (IOException ioe) {
throw new BadPacketException("Couldn't read from a ByteStream!!!");
}
}
/**
* Constructs a new UDPConnectBackVendorMessage to be sent out.
* @param port The port you want people to connect back to. If you give a
* bad port I don't check so check yourself!
* @param guid The guid you want people to connect back with. Serves as
* a flag that the connect back is 'unsolicited'.
*/
public UDPConnectBackVendorMessage(int port, GUID guid) {
super(F_GTKG_VENDOR_ID, F_UDP_CONNECT_BACK, 1,
derivePayload(port, guid));
_port = port;
_guid = guid;
}
public int getConnectBackPort() {
return _port;
}
public GUID getConnectBackGUID() {
return _guid;
}
/**
* Constructs the payload given the desired port & guid.
*/
private static byte[] derivePayload(int port, GUID guid) {
try {
// do it during construction....
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteOrder.short2leb((short)port,baos); // write port
baos.write(guid.bytes());
return baos.toByteArray();
} catch (IOException ioe) {
ErrorService.error(ioe);
return null;
}
}
/** Overridden purely for stats handling.
*/
protected void writePayload(OutputStream out) throws IOException {
super.writePayload(out);
SentMessageStatHandler.TCP_UDP_CONNECTBACK.addMessage(this);
}
/** Overridden purely for stats handling.
*/
public void recordDrop() {
super.recordDrop();
}
}