package com.limegroup.gnutella.messages;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

import org.limewire.io.IpPort;
import org.limewire.io.IpPortImpl;
import org.limewire.security.QueryKey;
import org.limewire.util.PrivilegedAccessor;

import junit.framework.Test;

import com.limegroup.gnutella.ConnectionManager;
import com.limegroup.gnutella.Endpoint;
import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.RouterService;

@SuppressWarnings( { "unchecked", "cast" } )
public class PingReplyTest extends com.limegroup.gnutella.util.LimeTestCase {
    
    /**
     * A non blank IP
     */
    private static final byte[] IP = new byte[] { 1, 1, 1, 1 };
    
    public PingReplyTest(String name) {
        super(name);
    }

    public static Test suite() {
        return buildTestSuite(PingReplyTest.class);
    }

    public static void main(String[] args) {
        junit.textui.TestRunner.run(suite());
    }
   
    /**
     * Tests the methods for getting the leaf and ultrapeer slots from the 
     * pong.
     * 
     * @throws Exception if an error occurs
     */
    public void testHasFreeSlots() throws Exception {
        
        byte[] guid = GUID.makeGuid();
        byte[] ip = {1,1,1,1};
        PingReply pr = PingReply.create(guid, (byte)3, 6346, ip, 
            (long)10, (long)10, true, 100, true);    

        //All values are determined based on connection status, and because
        // we haven't set up connections yet, we don't have free anything.
        assertTrue("slots unexpectedly empty", !pr.hasFreeSlots());
        assertEquals("unexpected number leaf slots", 0, pr.getNumLeafSlots());
        assertTrue("slots unexpectedly not empty", !pr.hasFreeLeafSlots());
        assertTrue("slots unexpectedly empty", !pr.hasFreeUltrapeerSlots());
        assertEquals("slots unexpectedly empty", 0, pr.getNumUltrapeerSlots());
        
        // Switch ConnectionManager to report different values for free leaf
        // and ultrapeer slots.
        ConnectionManager manager = new TestConnectionManager(7, 10);
        PrivilegedAccessor.setValue(RouterService.class, "manager", manager);
        
        pr = PingReply.create(guid, (byte)3, 6346, ip, 
            (long)10, (long)10, true, 100, true);    
            
        assertTrue("slots unexpectedly full", pr.hasFreeSlots());

        assertTrue("slots unexpectedly full", pr.hasFreeLeafSlots());
        
        assertTrue("slots unexpectedly full", pr.hasFreeUltrapeerSlots());
        
        // Should now have leaf slots
        assertEquals("unexpected number leaf slots", 
                    manager.getNumFreeLimeWireLeafSlots(), 
                    pr.getNumLeafSlots());
        
        assertEquals("unexpected number ultrapeer slots", 
                    manager.getNumFreeLimeWireNonLeafSlots(), 
                    pr.getNumUltrapeerSlots());
    }
   
    
    /**
     * Tests the method for creating a new pong with a changed GUID out
     * of an existing pong
     */
    public void testMutateGUID() throws Exception {
        byte[] guid = new GUID().bytes();
        byte ttl = 4;
        int port = 6444;
        byte[] ip = {1, 1, 1, 1};
        long files = 500L;
        long kbytes = 75580L;
        boolean isUltrapeer = false;
        int dailyUptime = 10;
        boolean isGUESSCapable = false;
        
        PingReply pr = 
            PingReply.create(guid, ttl, port, ip, files, kbytes, 
                             isUltrapeer, dailyUptime, isGUESSCapable);
        
        PingReply testPR = pr.mutateGUID(new GUID().bytes());
        
        assertNotEquals(pr.getGUID(), testPR.getGUID());
        assertEquals(pr.getTTL(), testPR.getTTL());
        assertEquals(pr.getPort(), testPR.getPort());
        assertEquals(pr.getInetAddress(), testPR.getInetAddress());
        assertEquals(pr.getFiles(), testPR.getFiles());
        assertEquals(pr.getKbytes(), testPR.getKbytes());
        assertEquals(pr.isUltrapeer(), testPR.isUltrapeer());
        assertEquals(pr.getDailyUptime(), testPR.getDailyUptime());
        assertEquals(pr.supportsUnicast(), testPR.supportsUnicast());
    }


    /**
     * Tests the method for creating a pong from the network.
     */
    public void testCreatePongFromNetwork() throws Exception {
        byte[] guid = new GUID().bytes();
        byte[] payload = new byte[2];

        // make sure we reject invalid payload sizes
        try {
            PingReply.createFromNetwork(guid, (byte)4, (byte)3,
                                            payload);
            fail("should have not accepted payload size");
        } catch(BadPacketException e) {
            // expected because the payload size is invalid
        }

        // make sure we reject null guids
        payload = new byte[PingReply.STANDARD_PAYLOAD_SIZE];
        addIP(payload);
        try {
            PingReply.createFromNetwork(null, (byte)4, (byte)3,
                                            payload);
            fail("should have not accepted null guid");
        } catch(NullPointerException e) {
            // expected because the payload size is invalid
        }        

        // make sure we reject null payloads
        try {
            PingReply.createFromNetwork(guid, (byte)4, (byte)3,
                                            null);
            fail("should have not accepted null payload");
        } catch(NullPointerException e) {
            // expected because the payload size is invalid
        }        

        // make sure we reject bad ggep
        GGEP ggep = new GGEP(true);
        payload = new byte[3];
        // set 'LIM'  -- incorrect value to make sure it fails
        System.arraycopy("LIM".getBytes(),
                         0, payload, 0,
                         2);
         // add it
        ggep.put(GGEP.GGEP_HEADER_VENDOR_INFO, payload);  
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ggep.write(baos);

        byte[] extensions = baos.toByteArray();
        payload = 
            new byte[PingReply.STANDARD_PAYLOAD_SIZE+extensions.length];
        addIP(payload);            

        System.arraycopy(extensions, 0, 
                         payload, PingReply.STANDARD_PAYLOAD_SIZE, 
                         extensions.length);
        try {
            PingReply.createFromNetwork(guid, (byte)4, (byte)3,
                                            payload);
            fail("should have not accepted bad GGEP in payload");
        } catch(BadPacketException e) {
            // expected because the payload size is invalid
        }                

        // test one that should go through fine
        payload = new byte[PingReply.STANDARD_PAYLOAD_SIZE];
        payload[0] = 1;
        addIP(payload);        

        // this one should go through
        PingReply.createFromNetwork(guid, (byte)4, (byte)3,
                                        payload);
    }


    public void testNewPong() {
        long u4=0x00000000FFFFFFFFl;
        int u2=0x0000FFFF;
        byte[] ip={(byte)0xFE, (byte)0x00, (byte)0x00, (byte)0x1};
        PingReply pr = PingReply.create(new byte[16], (byte)0,
                                        u2, ip, u4, u4);

        assertEquals(u2, pr.getPort());
        assertEquals(u4, pr.getFiles());
        long kbytes=pr.getKbytes();
        assertEquals(Long.toHexString(kbytes), u4, kbytes);
        String ip2=pr.getAddress();
        assertEquals("254.0.0.1", ip2);
        assertTrue(! pr.isUltrapeer());
    }      
      
    //TODO: check construction from raw bytes

    public void testPongMarking() {
        PingReply pr = 
            PingReply.createExternal(new byte[16], (byte)2, 6346, IP,
                                     false);

        
        assertTrue(! pr.isUltrapeer());        
        // all pongs should have a GGEP extension now....
        assertTrue("pong should have GGEP ext", pr.hasGGEPExtension());

        pr = PingReply.createExternal(new byte[16], (byte)2, 6346, IP,
                                      true);
        assertTrue(pr.isUltrapeer());
        // all pongs should have a GGEP extension now....
        assertTrue("pong should have GGEP ext", pr.hasGGEPExtension());

        pr = PingReply.create(new byte[16], (byte)2, 6346, IP,
                              5, 2348, false, 0, false);        
        assertTrue(! pr.isUltrapeer());
        assertEquals(2348, pr.getKbytes());
        // all pongs should have a GGEP extension now....
        assertTrue("pong should have GGEP ext", pr.hasGGEPExtension());

        pr = PingReply.create(new byte[16], (byte)2, 6346, IP,
                              5, 2348, true, 0, true);
        assertTrue(pr.isUltrapeer());
        // all pongs should have a GGEP extension now....
        assertTrue("pong should have GGEP ext", pr.hasGGEPExtension());

        pr = PingReply.create(new byte[16], (byte)2, 6346, IP,
                              5, 345882, false, 0, false);
        assertTrue(! pr.isUltrapeer());
        // all pongs should have a GGEP extension now....
        assertTrue("pong should have GGEP ext", pr.hasGGEPExtension());

        pr = PingReply.create(new byte[16], (byte)2, 6346, IP,
                              5, 345882, true, -1, true);
        assertTrue(pr.isUltrapeer());
        // after added unicast support, all Ultrapeer Pongs have GGEP extension
        assertTrue("pong should have GGEP ext", pr.hasGGEPExtension());

        
        assertEquals("pong should not have a daily uptime", -1,
                     pr.getDailyUptime());        
    }
      
    public void testPowerOf2() {
        assertTrue(! PingReply.isPowerOf2(-1));
        assertTrue(! PingReply.isPowerOf2(0));
        assertTrue(PingReply.isPowerOf2(1));
        assertTrue(PingReply.isPowerOf2(2));
        assertTrue(! PingReply.isPowerOf2(3));
        assertTrue(PingReply.isPowerOf2(4));
        assertTrue(PingReply.isPowerOf2(16));
        assertTrue(! PingReply.isPowerOf2(18));
        assertTrue(PingReply.isPowerOf2(64));
        assertTrue(! PingReply.isPowerOf2(71));
    }

    public void testNonGGEPBigPong() throws Exception  {
        //Will this pass big pongs--even if the contents are not GGEP?
        byte[] payload = new byte[14+2];
        //add the port
        payload[0] = 0x0F;
        payload[1] = 0x00;//port 
      
        payload[2] = 0x10;
        payload[3] = 0x10;
        payload[4] = 0x10;
        payload[5] = 0x10;//ip = 16.16.16.16
      
        payload[6] = 0x0F;//
        payload[7] = 0x00;//
        payload[8] = 0x00;//
        payload[9] = 0x00;//15 files shared
      
        payload[10] = 0x0F;//
        payload[11] = 0x00;//
        payload[12] = 0x00;//
        payload[13] = 0x00;//15 KB
        //OK Now for the big pong part
        payload[14] = (byte) 65;
        payload[15] = (byte) 66;
        PingReply pr=null;
        pr = PingReply.createFromNetwork(new byte[16], (byte)2, (byte)4, payload);
        assertTrue(! pr.hasGGEPExtension());

        assertEquals("pong should not have a daily uptime", -1,
                     pr.getDailyUptime());  
        
        //Start testing
        assertEquals("wrong port", 15, pr.getPort());
        String ip = pr.getAddress();
        assertEquals("wrong IP", "16.16.16.16", ip);
        assertEquals("wrong files", 15, pr.getFiles());
        assertEquals("Wrong share size", 15, pr.getKbytes());
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        pr.write(stream);
        byte[] op = stream.toByteArray();
        byte[] big = new byte[2];
        big[0] = op[op.length-2];
        big[1] = op[op.length-1];
        String out = new String(big);
        assertEquals("Big part of pong lost", "AB", out);
        //come this far means its OK
    }


    public void testBasicGGEP() throws Exception {
        // create a pong
        PingReply pr = 
            PingReply.createExternal(new byte[16], (byte)3, 6349, IP, false);
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        pr.write(baos);

        byte[] bytes=baos.toByteArray(); 

        //Decode and check contents.
        Message m=MessageFactory.read(new ByteArrayInputStream(bytes));
        PingReply pong=(PingReply)m;
        assertTrue(m instanceof PingReply);
        assertTrue(pong.getPort()==6349);
        assertTrue("pong should have GGEP ext", pr.hasGGEPExtension());
        assertTrue(pong.supportsUnicast()==false);
        assertTrue(pong.getVendor().equals("LIME"));
        assertTrue("Major Version = " + pong.getVendorMajorVersion(), 
                   pong.getVendorMajorVersion()==2);
        assertTrue("Minor Version = " + pong.getVendorMinorVersion(), 
                   pong.getVendorMinorVersion()==7);
    }


    /** Test the raw bytes of an encoded GGEP'ed pong.  Then checks that
     *  these can be decoded.  Note that this will need to be changed if
     *  more extensions are added. */
    public void testGGEPEncodeDecode() throws Exception {
        //Create pong

        PingReply pr = PingReply.create(new byte[16], (byte)3, 6349, IP,
                                   0l, 0l, true, 523, true);        

        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        try {
            pr.write(baos);
        } catch (IOException e) {
            assertTrue("Couldn't write stream.", false);
        }

        //Encode and check raw bytes.
        //UDP is the last extension, so it is DUPTIME and then UDP.  should take
        //this into account....
        byte[] bytes=baos.toByteArray(); 
        int idLength=GGEP.GGEP_HEADER_DAILY_AVERAGE_UPTIME.length();
        int udpLength=GGEP.GGEP_HEADER_UNICAST_SUPPORT.length();
        int vcLength=GGEP.GGEP_HEADER_VENDOR_INFO.length();
        int upLength=GGEP.GGEP_HEADER_UP_SUPPORT.length();
        int dhtLength = GGEP.GGEP_HEADER_DHT_SUPPORT.length();
        int ggepLength=1   //magic number
                      +1   //"DUPTIME" extension flags
                      +idLength //ID
                      +1   //data length
                      +2   //data bytes
                      +1   //"UDP" extension flags
                      +udpLength // ID
                      +1   //data length
                      +1   //data bytes
                      +1   //"UP" extension flags
                      +upLength // ID
                      +1   // data length
                      +3  // data bytes
                      +1   //"VC" extension flags
                      +vcLength // ID
                      +1   // data length
                      +5   // data bytes
                      +1  //"DHT" extension flags
                      +dhtLength
                      +1   // data length
                      +3;  // data bytes 
        assertTrue("Length: "+bytes.length, bytes.length==(23+14+ggepLength));
        int offset=23+14;                              //GGEP offset
        assertTrue(bytes[offset]==(byte)0xc3);         //GGEP magic number
        assertTrue("Got: "+(0xFF&bytes[offset+1]), 
                   bytes[offset+1]==(byte)(0x00 | dhtLength)); //extension flags
        assertTrue(bytes[offset+2]==(byte)'D');
        assertTrue(bytes[offset+3]==(byte)'H');
        assertTrue(bytes[offset+4]==(byte)'T');
        assertTrue(bytes[offset+2+dhtLength+5]==(byte)'D');
        assertTrue(bytes[offset+2+dhtLength+6]==(byte)'U');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4]==(byte)'G');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+5]==(byte)'U');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+6]==(byte)'E');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+udpLength+3]==(byte)'U');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+udpLength+4]==(byte)'P');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+udpLength+3+upLength+5]==(byte)'V');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+udpLength+3+upLength+6]==(byte)'C');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+udpLength+3+upLength+8]==(byte)'L');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+udpLength+3+upLength+9]==(byte)'I');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+udpLength+3+upLength+10]==(byte)'M');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+udpLength+3+upLength+11]==(byte)'E');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+udpLength+3+upLength+12]==39);
        //...etc.
        assertTrue(bytes[bytes.length-2-(3+udpLength)-(5+upLength)-(7+vcLength)]==(byte)0x0B); //little byte of 523
        assertTrue(bytes[bytes.length-1-(3+udpLength)-(5+upLength)-(7+vcLength)]==(byte)0x02); //big byte of 523


        //Decode and check contents.
        Message m=MessageFactory.read(new ByteArrayInputStream(bytes));
        PingReply pong=(PingReply)m;
        assertTrue(m instanceof PingReply);
        assertTrue(pong.getPort()==6349);
        assertTrue("pong should have GGEP ext", pr.hasGGEPExtension());
        assertTrue(pong.getDailyUptime()==523);
        assertTrue(pong.supportsUnicast()==true);
        assertTrue(pong.getVendor().equals("LIME"));
        assertTrue("Major Version = " + pong.getVendorMajorVersion(), 
                   pong.getVendorMajorVersion()==2);
        assertTrue("Minor Version = " + pong.getVendorMinorVersion(), 
                   pong.getVendorMinorVersion()==7);

    }

    /** Test the raw bytes of an encoded GGEP'ed pong.  Then checks that
     *  these can be decoded.  Note that this will need to be changed if
     *  more extensions are added. */
    public void testGGEPEncodeDecodeNoGUESS() throws Exception {
        //Create pong

        PingReply pr=PingReply.create(new byte[16], (byte)3, 6349, IP,
                                      0l, 0l, true, 523, false);        
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        try {
            pr.write(baos);
        } catch (IOException e) {
            assertTrue("Couldn't write stream.", false);
        }

        //Encode and check raw bytes.
        //UDP is the last extension, so it is DUPTIME and then UDP.  should take
        //this into account....
        byte[] bytes=baos.toByteArray(); 
        int idLength=GGEP.GGEP_HEADER_DAILY_AVERAGE_UPTIME.length();
        int vcLength=GGEP.GGEP_HEADER_VENDOR_INFO.length();
        int upLength=GGEP.GGEP_HEADER_UP_SUPPORT.length();
        int dhtLength = GGEP.GGEP_HEADER_DHT_SUPPORT.length();
        int ggepLength=1   //magic number
                      +1   //"DUPTIME" extension flags
                      +idLength //ID
                      +1   //data length
                      +2   //data bytes
                      +1   //"UP" extension flags
                      +upLength // ID
                      +1   // data length
                      +3  // data bytes
                      +1   //"VC" extension flags
                      +vcLength // ID
                      +1   // data length
                      +5   // data bytes
                      +1   //"DHT" extension flags
                      +dhtLength
                      +1   // data length
                      +3;  // data bytes 
        assertTrue("Length: "+bytes.length, bytes.length==(23+14+ggepLength));
        int offset=23+14;                              //GGEP offset
        assertTrue(bytes[offset]==(byte)0xc3);         //GGEP magic number
        assertTrue("Got: "+(0xFF&bytes[offset+1]), 
                bytes[offset+1]==(byte)(0x00 | dhtLength)); //extension flags
        assertTrue(bytes[offset+2]==(byte)'D');
        assertTrue(bytes[offset+3]==(byte)'H');
        assertTrue(bytes[offset+4]==(byte)'T');
        assertTrue(bytes[offset+2+dhtLength+5]==(byte)'D');
        assertTrue(bytes[offset+2+dhtLength+6]==(byte)'U');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4]==(byte)'U');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+5]==(byte)'P');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+upLength+5]==(byte)'V');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+upLength+6]==(byte)'C');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+upLength+8]==(byte)'L');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+upLength+9]==(byte)'I');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+upLength+10]==(byte)'M');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+upLength+11]==(byte)'E');
        assertTrue(bytes[offset+2+dhtLength+5+idLength+4+upLength+12]==39);
        //...etc.
        assertTrue(bytes[bytes.length-2-(5+upLength)-(7+vcLength)]==(byte)0x0B); //little byte of 523
        assertTrue(bytes[bytes.length-1-(5+upLength)-(7+vcLength)]==(byte)0x02); //big byte of 523


        //Decode and check contents.
        Message m=MessageFactory.read(new ByteArrayInputStream(bytes));
        PingReply pong=(PingReply)m;
        assertTrue(m instanceof PingReply);
        assertTrue(pong.getPort()==6349);
        assertTrue("pong should have GGEP ext", pr.hasGGEPExtension());
        assertTrue(pong.getDailyUptime()==523);
        assertTrue(pong.supportsUnicast()==false);
        assertTrue(pong.getVendor().equals("LIME"));
        assertTrue("Major Version = " + pong.getVendorMajorVersion(), 
                   pong.getVendorMajorVersion()==2);
        assertTrue("Minor Version = " + pong.getVendorMinorVersion(), 
                   pong.getVendorMinorVersion()==7);
    }

    public void testStripGGEP2() throws Exception {
        byte[] guid=GUID.makeGuid();
        byte[] ip={(byte)18, (byte)239, (byte)3, (byte)144};
        PingReply pr1 = PingReply.create(guid, (byte)3, 6349, ip,
                                    13l, 14l, false, 4321, false); 
        PingReply pr2=(PingReply)pr1.stripExtendedPayload();
        assertTrue(Arrays.equals(pr1.getGUID(), pr2.getGUID()));
        assertEquals(pr1.getHops(), pr2.getHops());
        assertEquals(pr1.getTTL(), pr2.getTTL());
        assertEquals(pr1.getFiles(), pr2.getFiles());
        assertEquals(pr1.getKbytes(), pr2.getKbytes());
        assertEquals(pr1.getPort(), pr2.getPort());
        assertEquals(pr1.getInetAddress(), pr2.getInetAddress());

        assertTrue(! pr2.hasGGEPExtension());
        assertEquals("pong should not have a daily uptime", -1,
                     pr2.getDailyUptime());

        ByteArrayOutputStream out=new ByteArrayOutputStream();
        pr2.write(out);
        assertTrue(out.toByteArray().length==(23+14));

        //Check no aliasing
        pr1.hop();
        assertTrue(pr1.getHops()!=pr2.getHops());
        assertTrue(pr1.getTTL()!=pr2.getTTL());
    }

    public void testPongTooSmall() throws Exception {
        byte[] bytes=new byte[23+25];  //one byte too small
        bytes[16]=Message.F_PING_REPLY;
        bytes[17]=(byte)3;     //hops
        bytes[18]=(byte)3;     //ttl
        bytes[19]=(byte)13;    //payload length
        ByteArrayInputStream in=new ByteArrayInputStream(bytes);
        try {
            MessageFactory.read(in);
            fail("No exception thrown");
        } catch (BadPacketException pass) { 
            //Pass!
        }
    }


    public void testQueryKeyPong() throws Exception {
        byte[] randBytes = new byte[8];
        (new Random()).nextBytes(randBytes);
        QueryKey qk = null;
        GUID guid = new GUID(GUID.makeGuid());
        byte[] ip={(byte)18, (byte)239, (byte)3, (byte)144};
        qk = QueryKey.getQueryKey(randBytes, true);
        PingReply pr = 
            PingReply.createQueryKeyReply(guid.bytes(), (byte) 1, 6346, ip,
                                          2, 2, true, qk);
        assertTrue(pr.getQueryKey().equals(qk));
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        pr.write(baos);
        ByteArrayInputStream bais = 
             new ByteArrayInputStream(baos.toByteArray());
        PingReply prStreamed = (PingReply) MessageFactory.read(bais);
        assertTrue(prStreamed.getQueryKey().equals(qk));
            
    }
    
    public void testIpRequestPong() throws Exception {
        RouterService.getAcceptor().setAddress(InetAddress.getLocalHost());
        
        // a pong carrying an ip:port
        Endpoint e = new Endpoint("1.2.3.4",5);
        PingReply p = PingReply.create(GUID.makeGuid(),(byte)1,e);
        
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        p.write(baos);
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        PingReply fromNet = (PingReply)MessageFactory.read(bais);
        
        assertEquals("1.2.3.4",fromNet.getMyInetAddress().getHostAddress());
        assertEquals(5,fromNet.getMyPort());
        
        // a pong carrying invalid port
        e = new Endpoint("1.2.3.4",5) {
            public int getPort() {
                return 0;
            }
        };
        
        p = PingReply.create(GUID.makeGuid(),(byte)1,e);
        baos = new ByteArrayOutputStream();
        p.write(baos);
        bais = new ByteArrayInputStream(baos.toByteArray());
        fromNet = (PingReply)MessageFactory.read(bais);
        
        assertNull(fromNet.getMyInetAddress());
        assertEquals(0,fromNet.getMyPort());
        
        //a pong carrying private ip
        e = new Endpoint("192.168.0.1",20);
        p = PingReply.create(GUID.makeGuid(),(byte)1,e);
        baos = new ByteArrayOutputStream();
        p.write(baos);
        bais = new ByteArrayInputStream(baos.toByteArray());
        fromNet = (PingReply)MessageFactory.read(bais);
        
        assertNull(fromNet.getMyInetAddress());
        assertEquals(0,fromNet.getMyPort());
        
        // a pong not carrying ip:port
        p = PingReply.create(GUID.makeGuid(),(byte)1);
        baos = new ByteArrayOutputStream();
        p.write(baos);
        bais = new ByteArrayInputStream(baos.toByteArray());
        fromNet = (PingReply)MessageFactory.read(bais);
        
        assertNull(fromNet.getMyInetAddress());
        assertEquals(0,fromNet.getMyPort());
    }
    
    
    public void testUDPHostCacheExtension() throws Exception {
        GGEP ggep = new GGEP(true);
        ggep.put(GGEP.GGEP_HEADER_UDP_HOST_CACHE);
        PingReply pr = PingReply.create(GUID.makeGuid(), (byte)1, 1,
                    new byte[] { 1, 1, 1, 1 },
                    (long)0, (long)0, false, ggep);
        assertTrue(pr.isUDPHostCache());
        assertEquals("1.1.1.1", pr.getUDPCacheAddress());
        
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        pr.write(out);
        byte[] b = out.toByteArray();
        
        PingReply read = (PingReply)MessageFactory.read(new ByteArrayInputStream(b));
        assertTrue(read.isUDPHostCache());
        assertEquals("1.1.1.1", read.getUDPCacheAddress());
        
        ggep = new GGEP(true);
        ggep.put(GGEP.GGEP_HEADER_UDP_HOST_CACHE, "www.nowhere.org");
        pr = PingReply.create(GUID.makeGuid(), (byte)1, 1,
                    new byte[] { 1, 1, 1, 1 },
                    (long)0, (long)0, false, ggep);
        assertTrue(pr.isUDPHostCache());
        assertEquals("www.nowhere.org", pr.getUDPCacheAddress());

        out = new ByteArrayOutputStream();
        pr.write(out);
        b = out.toByteArray();
        
        read = (PingReply)MessageFactory.read(new ByteArrayInputStream(b));
        assertTrue(read.isUDPHostCache());
        assertEquals("www.nowhere.org", read.getUDPCacheAddress());
    }
    
    public void testPackedIPsInPong() throws Exception {
        GGEP ggep = new GGEP(true);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write(new byte[] { 1, 1, 1, 1, 1, 0 } );
        out.write(new byte[] { 1, 2, 3, 4, 2, 0 } );
        out.write(new byte[] { 3, 4, 2, 3, 3, 0 } );
        out.write(new byte[] { (byte)0xFE, 0, 0, 3, 4, 0 } );
        ggep.put(GGEP.GGEP_HEADER_PACKED_IPPORTS, out.toByteArray());
        PingReply pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);

        List l = pr.getPackedIPPorts();
        assertEquals(4, l.size());
        IpPort ipp = (IpPort)l.get(0);
        assertEquals("1.1.1.1", ipp.getAddress());
        assertEquals(1, ipp.getPort());
        ipp = (IpPort)l.get(1);
        assertEquals("1.2.3.4", ipp.getAddress());
        assertEquals(2, ipp.getPort());
        ipp = (IpPort)l.get(2);
        assertEquals("3.4.2.3", ipp.getAddress());
        assertEquals(3, ipp.getPort());
        ipp = (IpPort)l.get(3);
        assertEquals("254.0.0.3", ipp.getAddress());
        assertEquals(4, ipp.getPort());
        
        // Try with invalid list of IPs (invalid by not being multiple of 6)
        ggep = new GGEP(true);
        out = new ByteArrayOutputStream();
        out.write(new byte[] { 1, 1, 1, 1, 1, 0 } );
        out.write(new byte[] { 1, 2, 3, 4, 2, 0 } );
        out.write(new byte[] { 3, 4, 2, 3, /* no port */ } );
        ggep.put(GGEP.GGEP_HEADER_PACKED_IPPORTS, out.toByteArray());
        pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);
        l = pr.getPackedIPPorts();
        assertTrue(l.isEmpty());
        
        // Try with invalid IPs (invalid by invalid IP addr)
        ggep = new GGEP(true);
        out = new ByteArrayOutputStream();
        out.write(new byte[] { 0, 0, 0, 0, 1, 0 } );
        out.write(new byte[] { 1, 2, 3, 4, 2, 0 } );
        out.write(new byte[] { 3, 4, 2, 3, 3, 0 } );
        ggep.put(GGEP.GGEP_HEADER_PACKED_IPPORTS, out.toByteArray());
        pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);
        l = pr.getPackedIPPorts();
        assertTrue(l.isEmpty());
        
        // Try with invalid IPs (invalid by invalid port)
        ggep = new GGEP(true);
        out = new ByteArrayOutputStream();
        out.write(new byte[] { 1, 1, 1, 1, 0, 0 } );
        out.write(new byte[] { 1, 2, 3, 4, 2, 0 } );
        out.write(new byte[] { 3, 4, 2, 3, 3, 0 } );
        ggep.put(GGEP.GGEP_HEADER_PACKED_IPPORTS, out.toByteArray());
        pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);
        l = pr.getPackedIPPorts();
        assertTrue(l.isEmpty());
        
        // Make sure the extension works with other GGEP flags (like UDP Host Cache)
        ggep = new GGEP(true);
        out = new ByteArrayOutputStream();
        out.write(new byte[] { 1, 1, 1, 1, 1, 0 } );
        out.write(new byte[] { 1, 2, 3, 4, 2, 0 } );
        out.write(new byte[] { 3, 4, 2, 3, 3, 0 } );
        out.write(new byte[] { (byte)0xFE, 0, 0, 3, 4, 0 } );
        ggep.put(GGEP.GGEP_HEADER_PACKED_IPPORTS, out.toByteArray());
        ggep.put(GGEP.GGEP_HEADER_UDP_HOST_CACHE);
        pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);
        assertTrue(pr.isUDPHostCache());
        l = pr.getPackedIPPorts();
        assertEquals(4, l.size());
        ipp = (IpPort)l.get(0);
        assertEquals("1.1.1.1", ipp.getAddress());
        assertEquals(1, ipp.getPort());
        ipp = (IpPort)l.get(1);
        assertEquals("1.2.3.4", ipp.getAddress());
        assertEquals(2, ipp.getPort());
        ipp = (IpPort)l.get(2);
        assertEquals("3.4.2.3", ipp.getAddress());
        assertEquals(3, ipp.getPort());
        ipp = (IpPort)l.get(3);
        assertEquals("254.0.0.3", ipp.getAddress());
        assertEquals(4, ipp.getPort());        
        
        // and make sure we can read from network data.
        out = new ByteArrayOutputStream();
        pr.write(out);
        
        pr = (PingReply)MessageFactory.read(new ByteArrayInputStream(out.toByteArray()));
        assertTrue(pr.isUDPHostCache());
        l = pr.getPackedIPPorts();
        assertEquals(4, l.size());
        ipp = (IpPort)l.get(0);
        assertEquals("1.1.1.1", ipp.getAddress());
        assertEquals(1, ipp.getPort());
        ipp = (IpPort)l.get(1);
        assertEquals("1.2.3.4", ipp.getAddress());
        assertEquals(2, ipp.getPort());
        ipp = (IpPort)l.get(2);
        assertEquals("3.4.2.3", ipp.getAddress());
        assertEquals(3, ipp.getPort());
        ipp = (IpPort)l.get(3);
        assertEquals("254.0.0.3", ipp.getAddress());
        assertEquals(4, ipp.getPort());
        
        // Try with one of the constructors.
        l = new LinkedList(l);
        l.add(new Endpoint("1.5.3.5", 5));
        pr = PingReply.create(GUID.makeGuid(), (byte)1, l, null);
        l = pr.getPackedIPPorts();
        assertFalse(pr.isUDPHostCache());
        l = pr.getPackedIPPorts();
        assertEquals(5, l.size());
        ipp = (IpPort)l.get(0);
        assertEquals("1.1.1.1", ipp.getAddress());
        assertEquals(1, ipp.getPort());
        ipp = (IpPort)l.get(1);
        assertEquals("1.2.3.4", ipp.getAddress());
        assertEquals(2, ipp.getPort());
        ipp = (IpPort)l.get(2);
        assertEquals("3.4.2.3", ipp.getAddress());
        assertEquals(3, ipp.getPort());
        ipp = (IpPort)l.get(3);
        assertEquals("254.0.0.3", ipp.getAddress());
        assertEquals(4, ipp.getPort());        
        ipp = (IpPort)l.get(4);
        assertEquals("1.5.3.5", ipp.getAddress());
        assertEquals(5, ipp.getPort());
    }
    
    public void testPackedHostCachesInPong() throws Exception {
        // test with compression.
        GGEP ggep = new GGEP(true);
        List addrs = new LinkedList();
        addrs.add("1.2.3.4:81");
        addrs.add("www.limewire.com:6379");
        addrs.add("www.eff.org");
        addrs.add("www.test.org:1&something=somethingelse&nothing=this");
        ggep.putCompressed(GGEP.GGEP_HEADER_PACKED_HOSTCACHES, toBytes(addrs));
        PingReply pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);

        Set s = new TreeSet(IpPort.COMPARATOR);
        s.addAll(pr.getPackedUDPHostCaches());
        assertEquals(4, s.size());
        IpPort ipp = new IpPortImpl("1.2.3.4", 81);
        assertContains(s, ipp);
        s.remove(ipp);
        ipp = new IpPortImpl("www.limewire.com", 6379);
        assertContains(s, ipp);
        s.remove(ipp);
        ipp = new IpPortImpl("www.eff.org", 6346);
        assertContains(s, ipp);
        s.remove(ipp);
        ipp = new IpPortImpl("www.test.org", 1);
        assertContains(s, ipp);
        s.remove(ipp);
        assertEquals(0, s.size());
        
        // test without compression
        ggep = new GGEP(true);
        addrs.clear();
        addrs.add("1.2.3.4:81");
        addrs.add("www.limewire.com:6379");
        addrs.add("www.eff.org");
        addrs.add("www.test.org:1&something=somethingelse&nothing=this");
        ggep.put(GGEP.GGEP_HEADER_PACKED_HOSTCACHES, toBytes(addrs));
        pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);
        s.clear();
        s.addAll(pr.getPackedUDPHostCaches());
        assertEquals(4, s.size());
        ipp = new IpPortImpl("1.2.3.4", 81);
        assertContains(s, ipp);
        s.remove(ipp);
        ipp = new IpPortImpl("www.limewire.com", 6379);
        assertContains(s, ipp);
        s.remove(ipp);
        ipp = new IpPortImpl("www.eff.org", 6346);
        assertContains(s, ipp);
        s.remove(ipp);
        ipp = new IpPortImpl("www.test.org", 1);
        assertContains(s, ipp);
        s.remove(ipp);
        assertEquals(0, s.size());        
        
        ggep = new GGEP(true);
        addrs.clear();
        addrs.add("1.2.3.4:");
        addrs.add("3.4.2.3");
        addrs.add("5.4.3.2:1:1");
        addrs.add("13.13.1.1:notanumber");
        ggep.putCompressed(GGEP.GGEP_HEADER_PACKED_HOSTCACHES, toBytes(addrs));
        pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);
        s.addAll(pr.getPackedUDPHostCaches());
        assertEquals(1, s.size());
        ipp = new IpPortImpl("3.4.2.3", 6346);
        assertContains(s, ipp);
        s.remove(ipp);
        assertEquals(0, s.size());
        
        ggep = new GGEP(true);
        ggep.put(GGEP.GGEP_HEADER_PACKED_HOSTCACHES, new byte[0]);
        pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);
        assertEquals(0, pr.getPackedUDPHostCaches().size());
        
        ggep = new GGEP(true);
        ggep.putCompressed(GGEP.GGEP_HEADER_PACKED_HOSTCACHES, new byte[] { 1, 1, 1, 1 } );
        pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);
        assertEquals(0, pr.getPackedUDPHostCaches().size());
        
        ggep = new GGEP(true);
        ggep.put(GGEP.GGEP_HEADER_PACKED_HOSTCACHES, new byte[] { 1, 1, 1, 1 } );
        pr = PingReply.create(
            GUID.makeGuid(), (byte)1, 1, new byte[] { 1, 1, 1, 1 },
            0, 0, false, ggep);
        assertEquals(0, pr.getPackedUDPHostCaches().size());        
    }
    
    private final byte[] toBytes(List l) throws Exception {
        StringBuffer sb = new StringBuffer();
        for(Iterator i = l.iterator(); i.hasNext(); ) {
            sb.append(i.next().toString());
            if(i.hasNext())
                sb.append("\n");
        }
        return sb.toString().getBytes();
    }
    
    private final void addIP(byte[] payload) {
        // fill up the ip so its not blank.
        payload[2] = 1;
        payload[3] = 1;
        payload[4] = 1;
        payload[5] = 1;
    }

    /**
     * Utility class that overrides ConnectionManager methods for getting the
     * number of free leaf and ultrapeer slots.
     */
    private static class TestConnectionManager extends ConnectionManager {
        private final int NUM_FREE_NON_LEAF_SLOTS;
        private final int NUM_FREE_LEAF_SLOTS;

        TestConnectionManager(int numFreeNonLeafSlots, int numFreeLeafSlots) {
            super();
            NUM_FREE_NON_LEAF_SLOTS = numFreeNonLeafSlots;
            NUM_FREE_LEAF_SLOTS = numFreeLeafSlots;
        }
        
        public int getNumFreeNonLeafSlots() {
            return NUM_FREE_NON_LEAF_SLOTS;
        }
        
        public int getNumFreeLeafSlots() {
            return NUM_FREE_LEAF_SLOTS;
        }
    }
    // TODO: build a test to test multiple GGEP blocks in the payload!!  the
    // implementation does not cover this it seems, so it should fail ;)

}


