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(); } }