/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2007 Phex Development Group
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 *  --- SVN Information ---
 *  $Id: PongMsg.java 3859 2007-07-01 20:15:19Z gregork $
 */
package phex.msg;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import phex.common.IntObj;
import phex.common.address.AddressUtils;
import phex.common.address.DefaultDestAddress;
import phex.common.address.DestAddress;
import phex.common.address.IpAddress;
import phex.common.address.MalformedDestAddressException;
import phex.connection.NetworkManager;
import phex.host.CaughtHost;
import phex.host.CaughtHostsContainer;
import phex.host.Host;
import phex.host.HostManager;
import phex.host.NetworkHostsContainer;
import phex.io.buffer.ByteBuffer;
import phex.net.repres.PresentationManager;
import phex.security.AccessType;
import phex.security.PhexSecurityManager;
import phex.share.ShareManager;
import phex.share.SharedFilesService;
import phex.statistic.StatisticProvider;
import phex.statistic.StatisticsManager;
import phex.udp.hostcache.UdpHostCache;
import phex.udp.hostcache.UdpHostCacheManager;
import phex.utils.HexConverter;
import phex.utils.IOUtil;
import phex.utils.NLogger;
import phex.utils.NLoggerNames;
import phex.utils.VersionUtils;

/**
 * <p>A pong response.</p>
 *
 * <p>This encapsulates a Gnutella message that informs this servent of the
 * vital statistics of a Gnutella host. Pongs should  only ever be received
 * in response to pings.</p>
 *
 * <p>This implementation handles GGEP extension blocks.</p>
 */
public class PongMsg extends Message
{
	private static final int MIN_PONG_DATA_LEN = 14;
	
    /**
     * Vendor code GGEP extension in GUESS format
     */
    private static final byte[] GGEP_VENDOR_CODE = new byte[5];
    static
    {
        // add vendor code 'PHEX'
        GGEP_VENDOR_CODE[ 0 ] = (byte) 0x50;
        GGEP_VENDOR_CODE[ 1 ] = (byte) 0x48;
        GGEP_VENDOR_CODE[ 2 ] = (byte) 0x45;
        GGEP_VENDOR_CODE[ 3 ] = (byte) 0x58;
        GGEP_VENDOR_CODE[4] = IOUtil.serializeGUESSVersionFormat(
            VersionUtils.getMajorVersionNumber(),
            VersionUtils.getMinorVersionNumber() );
    }
    
    /**
     * <p>The un-parsed body of the message.</p>
     */
    private byte[] body;

    private int port;
    private byte[] ip;
    private long fileCount;
    private long fileSizeInKB;
    
    private String vendor;
    private int vendorVersionMajor = -1;
    private int vendorVersionMinor = -1;
    private int avgDailyUptime;
    private boolean isUltrapeer;
    private GGEPBlock ggepBlock;
    
    /**
     * holds the information that can appear in udp pongs
     */
    private Set<DestAddress> ippDestAddresses;
    private Set<UdpHostCache> udpHostCaches;
    private UdpHostCache udpHostCache;

    public PongMsg( MsgHeader aHeader, byte[] payload )
    {
        super( aHeader );
        getHeader().setPayloadType( MsgHeader.PONG_PAYLOAD );

        body = payload;
        getHeader().setDataLength( body.length );
        
        avgDailyUptime = -1;

        // parse the body
        parseBody();
    }

    /**
     * <p>Create a new MsgInitResponse.</p>
     *
     * <p>The header will be modified so that its function property becomes
     * MsgHeader.sInitResponse. The header argument is owned by this object.</p>
     *
     * @param header  the MsgHeader to associate with the new message
     */
    private PongMsg( MsgHeader header, byte[] ip, int port, int fileCount,
        int fileSizeInKB, boolean isUltrapeer, GGEPBlock ggepBlock )
    {
        super( header );
        getHeader().setPayloadType( MsgHeader.PONG_PAYLOAD );

        if( ip.length != 4 )
        {
            throw new IllegalArgumentException(
                "Can't accept ip that is not 4 bytes in length: " +
                ip.length );
        }
        this.ip = ip;
        this.port = port;
        this.fileCount = fileCount;
        this.isUltrapeer = isUltrapeer;
        this.ggepBlock = ggepBlock;
                
        if ( isUltrapeer )
        {
            this.fileSizeInKB = createUltrapeerMarking( fileSizeInKB );
        }
        else
        {
            this.fileSizeInKB = fileSizeInKB;
        }
        buildBody();
        getHeader().setDataLength( body.length );
    }

    /**
     * Get the port that the remote host will listen on.
     *
     * @return  the current port number as a short
     */
    public int getPort()
    {
        return port;
    }

    /**
     * <p>Get the four byte image of the ip address of the Gnutella servent.</p>
     *
     * <p><em>Important:</em> Do not modify the return value. It is owned by
     * this object.</p>
     *
     * @return the four byte image of this servent's ip address
     */
    public byte[] getIP()
    {
        return ip;
    }

    /**
     * Get the number of files served from this servent.
     *
     * @return  a zero or positive integer giving the number of files served
     */
    public long getFileCount()
    {
        return fileCount;
    }

    /**
     * Get the number of bytes served by this servent.
     *
     * @return  the number of bytes served
     */
    public long getFileSizeInKB()
    {
        return fileSizeInKB;
    }
    
    /**
     * @return the vendor
     */
    public String getVendor()
    {
        return vendor;
    }

    /**
     * @return the vendorVersionMajor
     */
    public int getVendorVersionMajor()
    {
        return vendorVersionMajor;
    }

    /**
     * @return the vendorVersionMinor
     */
    public int getVendorVersionMinor()
    {
        return vendorVersionMinor;
    }

    /**
     * @return the avgDailyUptime
     */
    public int getDailyUptime()
    {
        return avgDailyUptime;
    }
    
    public boolean hasFreeLeafSlots()
    {
        if ( ggepBlock == null ||
            !ggepBlock.isExtensionAvailable( GGEPBlock.ULTRAPEER_ID ) )
        {
            return false;
        }
        byte[] data = ggepBlock.getExtensionData( GGEPBlock.ULTRAPEER_ID );
        if ( data != null )
        {
            if( data.length >= 3 )
            {
                if( data[1] > 0 )
                {
                    return true;
                }
            }
        }
        return false;
    }
    
    public boolean hasFreeUPSlots()
    {
        if ( ggepBlock == null ||
            !ggepBlock.isExtensionAvailable( GGEPBlock.ULTRAPEER_ID ) )
        {
            return false;
        }
        byte[] data = ggepBlock.getExtensionData( GGEPBlock.ULTRAPEER_ID );
        if ( data != null )
        {
            if( data.length >= 3 )
            {
                if( data[2] > 0 )
                {
                    return true;
                }
            }
        }
        return false;
    }
    
    public Set<DestAddress> getIPPDestAddresses()
    {
        return ippDestAddresses;
    }
    
    public Set<UdpHostCache> getUdpHostCaches()
    {
        return udpHostCaches;
    }
    
    public UdpHostCache getUdpHostCache()
    {
        return udpHostCache;
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public ByteBuffer createMessageBuffer()
    {
        return ByteBuffer.wrap( body );
    }
    
    public byte[] getbytes()
    {
        byte[] data = new byte[ MsgHeader.DATA_LENGTH + body.length ];
        byte[] hdr = getHeader().getBytes();
        System.arraycopy( hdr, 0, data, 0, MsgHeader.DATA_LENGTH );
        System.arraycopy( body, 0, data, MsgHeader.DATA_LENGTH , body.length );
        return data;
    }

    public String getDebugString()
    {
        return "Pong[ IP=" + AddressUtils.ip2string( ip ) +
            ", Port=" + port +
            ", FileCount=" + fileCount +
            ", FileSize=" + fileSizeInKB +
            ", AvgUptime=" + avgDailyUptime +
            ", HEX=[" + HexConverter.toHexString( body ) +
            "] ]";
    }

    private void buildBody()
    {
        byte[] ggepExtension;
        int extensionLength;
        if ( ggepBlock != null )
        {
            // we only build from the first GGEP block our body
            ggepExtension = ggepBlock.getBytes();
            extensionLength = ggepExtension.length;            
        }
        else
        {
            ggepExtension = null;
            extensionLength = 0;
        }
        
        body = new byte[ 14 + extensionLength ];
        IOUtil.serializeShortLE( (short)port, body, 0 );
        System.arraycopy( ip, 0, body, 2, 4 );
        IOUtil.serializeIntLE( (int)fileCount, body, 6);
        IOUtil.serializeIntLE( (int)fileSizeInKB, body, 10);
        if ( ggepExtension != null )
        {
            System.arraycopy( ggepExtension, 0, body, 14, extensionLength );
        }   
    }

    private void parseBody()
    {
        port = IOUtil.unsignedShort2Int(IOUtil.deserializeShortLE( body, 0 ));

        ip = new byte[4];
        ip[0] = body[2];
        ip[1] = body[3];
        ip[2] = body[4];
        ip[3] = body[5];

        fileCount = IOUtil.unsignedInt2Long( IOUtil.deserializeIntLE( body, 6 ) );
        fileSizeInKB = IOUtil.unsignedInt2Long( IOUtil.deserializeIntLE( body, 10 ) );
        
        // parse possible GGEP data
        if ( body.length <= 14 )
        {
            return;
        }
        
        parseGGEPBlocks();
    }
    
    private void parseGGEPBlocks()
    {
        PhexSecurityManager securityMgr = PhexSecurityManager.getInstance();

        ggepBlock = GGEPBlock.mergeGGEPBlocks( GGEPBlock.parseGGEPBlocks( body, 14 ) );
        
        if ( ggepBlock.isExtensionAvailable( GGEPBlock.VENDOR_CODE_ID ) )
        {
            byte[] data = ggepBlock.getExtensionData( GGEPBlock.VENDOR_CODE_ID );
            if ( data.length >= 4 )
            {
                vendor = new String( data, 0, 4 ).intern();
                if ( data.length > 4 )
                {
                    vendorVersionMajor = data[4] >> 4;
                    vendorVersionMinor = data[4] & 0x0F;
                }
            }
        }
        
        if ( ggepBlock.isExtensionAvailable( GGEPBlock.AVARAGE_DAILY_UPTIME ) )
        {
            byte[] data = ggepBlock.getExtensionData( GGEPBlock.AVARAGE_DAILY_UPTIME );
            if ( data != null && data.length >= 1 && data.length <= 4)
            {
                try
                {
                    avgDailyUptime = IOUtil.deserializeIntLE( data, 0, data.length );
                }
                catch ( IllegalArgumentException exp )
                {
                    // 5 bytes invalid average uptime infomation are most likley comming from Bearshare
                    NLogger.warn( PongMsg.class, "Invalid average uptime GGEP extension data: " +
                        HexConverter.toHexString( data ) );
                    avgDailyUptime = -1;
                }
            }
            else
            {
                NLogger.warn( PongMsg.class, "Invalid average uptime GGEP extension data: " +
                    HexConverter.toHexString( data ) );
                avgDailyUptime = -1;
            }
        }
        // checks for the UP ggep extension
        // if its present and the host has free slots its added to the respective free slots container 
        // in NetworkHostContainer ....as of now we ignore the actual count of the slots available
        
        
        if ( ggepBlock.isExtensionAvailable( GGEPBlock.UDP_HOST_CACHE_IPP ) )
        {
            byte[] data = ggepBlock.getExtensionData( GGEPBlock.UDP_HOST_CACHE_IPP );
            if ( data != null )
            {        
                ippDestAddresses = unpackIpPortData( data, securityMgr );
            }
        }
        
        // try to get any packed udp host cache list which may be present in udp pongs
        if( ggepBlock.isExtensionAvailable( GGEPBlock.UDP_HOST_CACHE_PHC))
        {
            byte[] data = ggepBlock.getExtensionData( GGEPBlock.UDP_HOST_CACHE_PHC );
            if ( data != null )
            {
                String packedData = new String( data );
                udpHostCaches = parsePackedHostCache( packedData, securityMgr );
            }
        }
        
        //check if the sending host is a udp host cache n add to
        //the functional udp host cache container
        if( ggepBlock.isExtensionAvailable( GGEPBlock.UDP_HOST_CACHE_UDPHC))
        {
            byte[] data = ggepBlock.getExtensionData( GGEPBlock.UDP_HOST_CACHE_UDPHC );
            try
            {
                DestAddress address;
                if ( data != null && data.length > 0)
                {
                	address = new DefaultDestAddress( new String( data ), port );
                }
                else
                {
                    address = new DefaultDestAddress( ip, port );
                }
                //add to the container
                AccessType access = securityMgr.controlHostAddressAccess(
                    address );
                if ( access == AccessType.ACCESS_GRANTED )
                {
                    udpHostCache = new UdpHostCache( address );
                }
            }
            catch( IllegalArgumentException e)
            {
                NLogger.warn( PongMsg.class, "INVALID Udp Host Cache found " +
                		"and ignored " + e);                
            }
        }
    }

    /**
     * gets the list of udp host cache addresses from the 
     * packed host cache ggep extension
     * @param packedHostCaches
     */
    private Set<UdpHostCache> parsePackedHostCache( String packedHostCaches, PhexSecurityManager securityMgr )
    {
        PresentationManager netPresMgr = PresentationManager.getInstance();
        String[] hostCaches = packedHostCaches.split( "\n" );
        Set<UdpHostCache> packedUdpHostCaches = new HashSet<UdpHostCache>( hostCaches.length );
        
        for( int i=0; i < hostCaches.length; i++ )
        {
            //find the position of the first key/value pair if any
            int pos = hostCaches[i].indexOf( "&" );
            try
            {
                DestAddress address;
                //no key/value pair found
                if( pos == -1)
                {
                    address = netPresMgr.createHostAddress(hostCaches[i], DefaultDestAddress.DEFAULT_PORT);
                }
                else 
                {
                    //key/value pair found, but just ignore as of now
                    String temp = hostCaches[i].substring( 0, pos );
                    address = netPresMgr.createHostAddress(temp, DefaultDestAddress.DEFAULT_PORT);
                }
                AccessType access = securityMgr.controlHostAddressAccess( address );
                if ( access == AccessType.ACCESS_GRANTED )
                {
                    UdpHostCache cache = new UdpHostCache( address );
                    packedUdpHostCaches.add( cache );
                }
            }
            catch ( MalformedDestAddressException e ) 
            {
                // just ignore and continue with next string
                NLogger.warn( NLoggerNames.UDP_HOST_CACHE, " Ignored " +
                		"One Host Cache address in a packed host cache list  "
                        + e );
                continue;
            }
        }
        return packedUdpHostCaches;
    }
    
    private static Set<DestAddress> unpackIpPortData( byte[] data, PhexSecurityManager securityMgr )
    {
        Set<DestAddress> ipPortPairs = null;
        try
        {
            final int FIELD_SIZE = 6;
            if (data.length % FIELD_SIZE != 0)
            {
                throw new InvalidGGEPBlockException("invalid IPPORT EXTENSION DATA IN PONG");
            }
            int size = data.length/FIELD_SIZE;
            ipPortPairs = new HashSet<DestAddress>();
            int index;
            for (int i=0; i<size; i++) 
            {
                index = i*FIELD_SIZE;
                byte[] ip = new byte[4];
                System.arraycopy(data, index, ip, 0, 4);
                int port = IOUtil.unsignedShort2Int(
                    IOUtil.deserializeShortLE( data, index + 4 ) );
                
                AccessType access = securityMgr.controlHostIPAccess( ip );
                if ( access == AccessType.ACCESS_GRANTED )
                {
                    DestAddress current = new DefaultDestAddress( ip, port );
                    ipPortPairs.add( current );
                }
            }
        }
        catch ( InvalidGGEPBlockException exp )
        {
            //ignore and continue parsing...
            NLogger.warn( PongMsg.class, exp );
        }
        return ipPortPairs;
    }


    /**
     * packs ip port data into a data array
     * @param ipPortCollection
     * @return ip port byte array 
     */    
    private static byte[] packIpPortData( Collection<CaughtHost> ipPortCollection )
    {
        final int FIELD_SIZE = 6;
        byte[] data = new byte[ipPortCollection.size() * FIELD_SIZE];
        int offset = 0;
        
        for( CaughtHost host : ipPortCollection ) 
        {
            DestAddress address = host.getHostAddress();
            byte[] addr = address.getIpAddress().getHostIP();
            int port = address.getPort();
            System.arraycopy(addr, 0, data, offset, 4);
            offset += 4;
            IOUtil.serializeShortLE( (short)port, data, offset);
            offset += 2;
        }
        return data;    
    }    
    
    /**
     * Returns true if this pong is marking a ultrapeer. This is the case when
     * fileSizeInKB is a power of two but at least 8.
     * @return true if this pong is marking a ultrapeer.
     */
    public boolean isUltrapeerMarked()
    {
        if ( fileSizeInKB < 8 )
        {
            return false;
        }
        return ( fileSizeInKB & (fileSizeInKB - 1 ) ) == 0;
    }
    
    /**
     * creates a udp pong message from a given byte array
     * @throws InvalidMessageException
     * @author Madhu
     *
     */
    public static PongMsg createUdpPongMsg( byte[] bytesMsg, Host fromHost ) 
    	throws InvalidMessageException
    {
        MsgHeader msgHdr = MsgHeader.createMsgHeader( bytesMsg, 0 );
        return createUdpPongMsg( msgHdr, bytesMsg, MsgHeader.DATA_LENGTH, fromHost );
    }
    
    public static PongMsg createUdpPongMsg( MsgHeader msgHdr, byte[] data, int offset, Host fromHost ) 
		throws InvalidMessageException 
	{
        msgHdr.setFromHost( fromHost );
        
        if( msgHdr.getDataLength() < MIN_PONG_DATA_LEN )
        {
        	 throw new InvalidMessageException( " Could not create Msg Body while trying to" +
            		" create udp pong Msg"
                    );
        }
        
        byte[] body = MessageProcessor.createBody( msgHdr, data, offset );
        if ( body == null )
        {
            throw new InvalidMessageException( " Could not create Msg Body while trying to" +
            		" create udp pong Msg"
                    );
        }
        
        return new PongMsg( msgHdr, body );  
	}
    
    
    /**
     * create a pong response message for a given ping
     * @param ping
     * @return the new PongMsg
     */
    public static PongMsg createUdpPongMsg( PingMsg ping )
    {
        StatisticsManager statMgr = StatisticsManager.getInstance();
        NetworkManager networkMgr = NetworkManager.getInstance();
        ShareManager shareMgr = ShareManager.getInstance();
        HostManager hostMgr = HostManager.getInstance();
        
        SharedFilesService sharedFilesService = shareMgr.getSharedFilesService();
        int fileCount = sharedFilesService.getFileCount();
        int fileSize = sharedFilesService.getTotalFileSizeInKb();
        
        StatisticProvider uptimeProvider = statMgr.getStatisticProvider(
            StatisticsManager.DAILY_UPTIME_PROVIDER );
        int avgDailyUptime = ((IntObj)uptimeProvider.getValue()).intValue();
        boolean isUltrapeer = hostMgr.isUltrapeer();
        GGEPBlock ggepBlock = createMyGGEPBlock(avgDailyUptime, isUltrapeer);
        
        byte[] scpByte = ping.getScpByte();
        if ( scpByte != null )
        {
            Collection<CaughtHost> ipPortPairs = null;
            CaughtHostsContainer hostContainer = hostMgr.getCaughtHostsContainer();
            if ( scpByte.length > 0 && (scpByte[0] & PingMsg.UDP_SCP_MASK) == PingMsg.UDP_SCP_ULTRAPEER )
            {
                ipPortPairs = hostContainer.getFreeUltrapeerSlotHosts();
            }
            else
            {
                ipPortPairs = hostContainer.getFreeLeafSlotHosts();
            }
            addUdpPongGGEPExt( ipPortPairs, ggepBlock );
        }
        
        
        DestAddress localAddress = networkMgr.getLocalAddress();
        IpAddress ipAddress = localAddress.getIpAddress();
        if( ipAddress == null )
        {
            throw new IllegalArgumentException( "Can't accept null ip." );
        }
        byte[] pongIp = ipAddress.getHostIP();        
        short pongPort = (short)localAddress.getPort();
        
        // Construct pingResponse msg.  Copy the original ping's GUID.
        MsgHeader newHeader = new MsgHeader( ping.getHeader().getMsgID(),
                MsgHeader.PONG_PAYLOAD, (byte)1, (byte)0, 0 );
        PongMsg udpPong = new PongMsg( newHeader, pongIp, pongPort, 
            fileCount, fileSize, isUltrapeer, ggepBlock );
        NLogger.info( PongMsg.class, "Created udp pong " +
            " in response to ping: " + udpPong );
        return udpPong;
    }
    
    
    public static PongMsg createMyOutgoingPong( GUID msgId, byte ttl )
    {
        StatisticsManager statMgr = StatisticsManager.getInstance();
        NetworkManager networkMgr = NetworkManager.getInstance();
        ShareManager shareMgr = ShareManager.getInstance();
        HostManager hostMgr = HostManager.getInstance();
        
        SharedFilesService sharedFilesService = shareMgr.getSharedFilesService();
        int fileCount = sharedFilesService.getFileCount();
        int fileSize = sharedFilesService.getTotalFileSizeInKb();
        
        StatisticProvider uptimeProvider = statMgr.getStatisticProvider(
                StatisticsManager.DAILY_UPTIME_PROVIDER );
        int avgDailyUptime = ((IntObj)uptimeProvider.getValue()).intValue();
        boolean isUltrapeer = hostMgr.isUltrapeer();
        GGEPBlock ggepBlock = createMyGGEPBlock( avgDailyUptime, isUltrapeer );
        
        DestAddress localAddress = networkMgr.getLocalAddress();
        IpAddress localIp = localAddress.getIpAddress();
        byte[] pongIp;
        if ( localIp == null )
        {
            pongIp = IpAddress.UNSET_IP;
            // in case we have a unset ip address we need to use the Phex.EXTDEST
            // GGEP extension to specify our pong destination.
            addPhexExtendedDestinationGGEP( localAddress, ggepBlock );
        }
        else
        {
            pongIp = localIp.getHostIP();
        }
        
        MsgHeader header = new MsgHeader( msgId, MsgHeader.PONG_PAYLOAD,
            ttl, (byte)0, 0 );
        PongMsg pong = new PongMsg( header, pongIp, (short)localAddress.getPort(), 
            fileCount, fileSize, isUltrapeer, ggepBlock );
        return pong;
    }
    
    public static PongMsg createOtherLeafsOutgoingPong( GUID msgId, byte ttl, 
        byte hops, DestAddress address )
    {
        MsgHeader header = new MsgHeader( msgId, MsgHeader.PONG_PAYLOAD,
            ttl, hops, 0 );
        GGEPBlock ggepBlock = null;
        
        IpAddress ip = address.getIpAddress();
        byte[] pongIp;
        if ( ip == null )
        {
            pongIp = IpAddress.UNSET_IP;
            // in case we have a unset ip address we need to use the Phex.EXTDEST
            // GGEP extension to specify our pong destination.
            ggepBlock = new GGEPBlock( false );
            addPhexExtendedDestinationGGEP( address, ggepBlock );
        }
        else
        {
            pongIp = ip.getHostIP();
        }
        PongMsg pong = new PongMsg( header, pongIp, (short)address.getPort(), 
            0, 0, false, ggepBlock );
        return pong;
    }
    
    public static PongMsg createFromCachePong( GUID newGuid, byte newTTL, PongMsg pongMsg )
    {
        MsgHeader header = new MsgHeader( newGuid, MsgHeader.PONG_PAYLOAD,
            newTTL, (byte)0, 0 );
        PongMsg pong = new PongMsg( header, pongMsg.body );
        return pong;
    }
    
    private static GGEPBlock createMyGGEPBlock( int avgDailyUptime,
        boolean isUltrapeer )
    {
        GGEPBlock ggepBlock = new GGEPBlock( false );
        // add daily avg. uptime.
        if ( avgDailyUptime > 0 )
        {
            ggepBlock.addExtension( GGEPBlock.AVARAGE_DAILY_UPTIME, avgDailyUptime );
        }
        
        // add UP GGEP extension.
        if ( isUltrapeer )
        {
            byte[] upExtension = new byte[3];
            upExtension[0] = IOUtil.serializeGUESSVersionFormat(
                VersionUtils.getUltrapeerMajorVersionNumber(),
                VersionUtils.getUltrapeerMinorVersionNumber() ); 
                
            NetworkHostsContainer networkHostsContainer = HostManager.getInstance().getNetworkHostsContainer();
            upExtension[1] = (byte) networkHostsContainer.getOpenLeafSlotsCount();
            upExtension[2] = (byte) networkHostsContainer.getOpenUltrapeerSlotsCount();
            
            ggepBlock.addExtension( GGEPBlock.ULTRAPEER_ID, upExtension );
        }
        
        // add vendor info
        ggepBlock.addExtension( GGEPBlock.VENDOR_CODE_ID, GGEP_VENDOR_CODE );
        return ggepBlock;
    }
    
    private static void addUdpPongGGEPExt( Collection<CaughtHost> ipPortPairs,
        GGEPBlock ggepBlock )
    {
        // add ip port info if asked for
        if( ipPortPairs != null )
        {
            byte[] ipPortData = packIpPortData( ipPortPairs );
            if( ipPortData.length >= 6 )
            {
                ggepBlock.addExtension( GGEPBlock.UDP_HOST_CACHE_IPP, ipPortData );
            }
        }
        
        UdpHostCacheManager uhcMgr = UdpHostCacheManager.getInstance();
        if( uhcMgr.isUdpHostCache() )
        {
            NetworkManager netMgr = NetworkManager.getInstance();
            DestAddress uhcAddress = netMgr.getLocalAddress();
            byte[] data;
            
            // check if we have dns name
            if( uhcAddress.isIpHostName() )
            {
                data = new byte[0];
            }
            else
            {
                data = uhcAddress.getHostName().getBytes();
            }
            // now add the ggep extension udphc
            ggepBlock.addExtension( GGEPBlock.UDP_HOST_CACHE_UDPHC, data );
            NLogger.debug( PongMsg.class, "UDP HOST CACHE extension added to outgoing pongs");
        }
        
        // if we want Packed Host Caches the data should be added in compressed form
        String packedCacheString = uhcMgr.getUdpHostCacheContainer().createPackedHostCaches();
        if( packedCacheString.length() > 0 )
        {
            byte[] data = IOUtil.deflate( packedCacheString.getBytes() );
            ggepBlock.addExtension( GGEPBlock.UDP_HOST_CACHE_PHC, data );
            NLogger.debug( PongMsg.class, " PACKED HOST CACHE extension added to outgoing pongs ");
        }
    }
    
    private static void addPhexExtendedDestinationGGEP( DestAddress address,
        GGEPBlock ggepBlock )
    {
        // TODO1 this is totally experimental and needs to be optimised
        // to use correct byte encoding! It can be used to transfer an destination
        // address info in case there is no IP address used for communication.
        // Like the case in I2P
        ggepBlock.addExtension( GGEPBlock.PHEX_EXTENDED_DESTINATION, 
            address.getHostName().getBytes() );
    }
    
    
    /**
     * Sets the ultrapeer kbytes field for ultrapeers.
     * This is done by returning the nearest power of two of the kbytes field.
     * A kbytes value of 1536 would return 1024.
     *                   1535              512.
     */
    private static int createUltrapeerMarking( int kbytes )
    {
        if ( kbytes < 12 )
        {
            return 8;
        }
        // first get the bit count of the value and substract 1...
        int bitCount = IOUtil.determineBitCount( kbytes );
        // calculate the power of two...
        int power = (int)Math.pow( 2, bitCount );
        // now determine the border value of the exponent...
        int minBorder = power - (power / 4);
        if ( kbytes < minBorder )
        {
            power = (int)Math.pow( 2, bitCount-1 );
        }
        return power;
    }
}