// ------------------------------------------------
// File : servent.cpp
// Date: 4-apr-2002
// Author: giles
// Desc: 
//		Servents are the actual connections between clients. They do the handshaking,
//		transfering of data and processing of GnuPackets. Each servent has one socket allocated
//		to it on connect, it uses this to transfer all of its data.
//
// (c) 2002 peercast.org
// ------------------------------------------------
// 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.
// ------------------------------------------------


#include <stdlib.h>
#include "servent.h"
#include "sys.h"
#include "gnutella.h"
#include "xml.h"
#include "html.h"
#include "http.h"
#include "stats.h"
#include "servmgr.h"
#include "peercast.h"



// -----------------------------------
char *Servent::statusMsgs[]=
{
        "NONE",
		"CONNECTING",
        "PROTOCOL",
        "HANDSHAKE",
        "CONNECTED",
        "CLOSING",
		"LISTENING",
		"TIMEOUT",
		"REFUSED",
		"VERIFIED",
		"ERROR",
		"WAIT"
};

// -----------------------------------
char *Servent::typeMsgs[]=
{
		"NONE",
		"ALLOCATED",
        "OUT",
        "IN",
        "SERVER",
		"CHECK",
		"STREAM",
        "LOOKUP"
};
// -----------------------------------
bool	Servent::isPrivate() 
{
	Host h = getHost();
	return servMgr->isFiltered(ServFilter::F_PRIVATE,h);
}
// -----------------------------------
bool	Servent::isAllowed(int a) 
{
	Host h = getHost();

	if (!h.isValid())
		return false;

	if (servMgr->isFiltered(ServFilter::F_BAN,h))
		return false;

	if (!servMgr->isFiltered(ServFilter::F_NETWORK,h))
		return false;

	return (allow&a)!=0;
}

// -----------------------------------
void Servent::init()
{
	outPacketsPri.init(MAX_OUTPACKETS);
	outPacketsNorm.init(MAX_OUTPACKETS);

	seenIDs.init(MAX_HASH);
	permAlloc = false;
	sock = NULL;
	reset();
}
// -----------------------------------
void Servent::reset(bool doClose)
{

	if (doClose)
		close();


	flowControl = false;
	networkID.clear();

	agent.clear();
	sock = NULL;
	outputBitrate = 0;
	chanID.clear();
	chanIndex = -1;
	allow = ALLOW_ALL;
	currPos = 0;
	addMetadata = false;
	pack.func = 255;
	lastConnect = lastPing = lastPacket = 0;
	loginPassword[0] = 0;
	loginMount[0] = 0;
	bytesPerSecond = 0;
	priorityConnect = false;

	outPacketsNorm.reset();
	outPacketsPri.reset();

	seenIDs.clearAll();

	status = S_NONE;
	if (!permAlloc)
		type = T_NONE;
}
// -----------------------------------
Host Servent::getHost()
{
	Host h(0,0);

	if (sock)
		h = sock->host;

	return h;
}

// -----------------------------------
bool Servent::outputPacket(GnuPacket &p, bool pri)
{
	lock.on();

	bool r=false;
	if (pri)
		r = outPacketsPri.write(p);
	else
	{
		if (servMgr->useFlowControl)
		{
			int per = outPacketsNorm.percentFull();
			if (per > 50)
				flowControl = true;
			else if (per < 25)
				flowControl = false;
		}


		bool send=true;
		if (flowControl)
		{
			// if in flowcontrol, only allow packets with less of a hop count than already in queue
			if (p.hops >= outPacketsNorm.findMinHop())
				send = false;
		}

		if (send)
			r = outPacketsNorm.write(p);
	}

	lock.off();
	return r;
}

// -----------------------------------
bool Servent::initServer(Host &h)
{
	try
	{
		checkFree();

		status = S_WAIT;

		createSocket();

		sock->bind(h);

		thread.data = this;

		thread.func = serverProc;

		type = T_SERVER;

		if (!sys->startThread(&thread))
			throw StreamException("Can`t start thread");

	}catch(StreamException &e)
	{
		LOG_ERROR("Bad server: %s",e.msg);
		reset();
		return false;
	}

	return true;
}
// -----------------------------------
void Servent::checkFree()
{
	if (sock)
		throw StreamException("Socket already set");
	if (thread.active)
		throw StreamException("Thread already active");
}
// -----------------------------------
void Servent::initIncoming(ClientSocket *s, unsigned int a)
{

	try{

		checkFree();

		type = T_INCOMING;
		sock = s;
		allow = a;
		thread.data = this;
		thread.func = incomingProc;

		setStatus(S_PROTOCOL);

		if (!sys->startThread(&thread))
		{
			reset();
			LOG_ERROR("Unable to start incoming thread");
		}
	}catch(StreamException &e)
	{
		LOG_ERROR("Incoming error: %s",e.msg);
		reset();
	}
}

// -----------------------------------
void Servent::initOutgoing(Host &rh, TYPE ty)
{
	char ipStr[64];
	rh.toStr(ipStr);
	try 
	{
		checkFree();

	    createSocket();

		type = ty;

		sock->timeout = 10000;	// 10 seconds to connect 
		sock->open(rh);

		if (!isAllowed(ALLOW_SERVENT))
			throw StreamException("Servent not allowed");

		thread.data = this;
		thread.func = outgoingProc;

		LOG_NETWORK("Starting outgoing to %s",ipStr);

		if (!sys->startThread(&thread))
			throw StreamException("Can`t start thread");

	}catch(StreamException &e)
	{
		LOG_ERROR("Unable to open connection to %s - %s",ipStr,e.msg);
		reset();
	}
}
// -----------------------------------
void Servent::initGiv(Host &h, int idx, GnuID &id)
{
	char ipStr[64];
	h.toStr(ipStr);
	try 
	{
		checkFree();


		Channel *ch = chanMgr->findChannelByIndex(idx);
		if (!ch)
			throw StreamException("No channel");

		chanID = ch->info.id;
		chanIndex = idx;
		pushID	= id;

	    createSocket();

		sock->open(h);

		if (!isAllowed(ALLOW_DATA))
			throw StreamException("Servent not allowed");
		
		sock->connect();

		thread.data = this;
		thread.func = givProc;

		type = T_STREAM;

		if (!sys->startThread(&thread))
			throw StreamException("Can`t start thread");

	}catch(StreamException &e)
	{
		LOG_ERROR("Giv error: 0x%x to %s: %s",idx,ipStr,e.msg);
		reset();
	}
}
// -----------------------------------
void Servent::createSocket()
{
	if (sock)
		LOG_ERROR("Servent::createSocket attempt made while active");

	sock = sys->createSocket();
}
// -----------------------------------
void Servent::endThread()
{	
	reset(true);
	thread.unlock();
}
// -----------------------------------
void Servent::abort()
{
	thread.active = false;
	if (sock)
		sock->close();
}
// -----------------------------------
void Servent::close()
{	
	thread.active = false;
		
	setStatus(S_CLOSING);
	if (sock)
	{
		sock->close();
		delete sock;
		sock = NULL;
	}
}

// -----------------------------------
void Servent::setStatus(STATUS s)
{
	if (s != status)
	{
		status = s;

		if ((s == S_HANDSHAKE) || (s == S_CONNECTED) || (s == S_LISTENING))
			lastConnect = sys->getTime();
	}

}
// -----------------------------------
void Servent::handshakeOut()
{

	sock->timeout = 10000;

	if (servMgr->allowGnutella)
	    sock->writeLine(GNU_CONNECT);
	else
	    sock->writeLine(GNU_PEERCONN);

	char str[64];
    
	sock->writeLine("%s %s",HTTP_HS_AGENT,PCX_AGENT);

    sock->writeLine("%s %s",PCX_HS_SUBNET,"peercast");   // depreciated

	if (priorityConnect)
	    sock->writeLine("%s %d",PCX_HS_PRIORITY,1);
	


	if (networkID.isSet())
	{
		networkID.toStr(str);
		sock->writeLine("%s %s",PCX_HS_NETWORKID,str);
	}

	servMgr->sessionID.toStr(str);
	sock->writeLine("%s %s",PCX_HS_ID,str);

	
    sock->writeLine("%s %s",PCX_HS_OS,peercastApp->getClientTypeOS());
	
	sock->writeLine("");

	HTTP http(*sock);

	http.checkResponse(200);

	bool versionValid = false;

	GnuID clientID;
	clientID.clear();

    while (http.nextHeader())
    {
		LOG_DEBUG(http.cmdLine);

		char *arg = http.getArgStr();
		if (!arg)
			continue;

		if (http.isHeader(HTTP_HS_AGENT))
		{
			agent.set(arg);

			if (strnicmp(arg,"PeerCast/",9)==0)
				versionValid = (stricmp(arg+9,MIN_CONNECTVER)>=0) && (stricmp(arg+9,MAX_CONNECTVER)<=0);
		}else if (http.isHeader(PCX_HS_NETWORKID))
			clientID.fromStr(arg);


		if (type == T_LOOKUP)
		{
			if (http.isHeader("remote-ip"))
			{
				if (!servMgr->forceIP[0])
				{
					Host nh;
					nh.fromStrIP(arg,DEFAULT_PORT);
					if (nh.ip != servMgr->serverHost.ip)
					{
						LOG_DEBUG("Got new IP: %s",arg);
						servMgr->serverHost.ip = nh.ip;
					}
				}
			}else if (http.isHeader(PCX_HS_FLOWCTL))
			{
				servMgr->useFlowControl = atoi(arg)!=0;

			}else if (http.isHeader(PCX_HS_MSG))
			{
				if (strcmp(servMgr->rootMsg.cstr(),arg))
				{
					servMgr->rootMsg.set(arg);
					peercastApp->notifyMessage(ServMgr::NT_PEERCAST,arg);
				}

			}else if (http.isHeader(PCX_HS_DL))
			{
				if (servMgr->downloadURL[0]==0)
				{
					strcpy(servMgr->downloadURL,arg);
					peercastApp->notifyMessage(ServMgr::NT_UPGRADE,"There is a new version available, please click here to upgrade your client.");
				}
			}
			else if (http.isHeader(PCX_HS_MINBCTTL))
				chanMgr->minBroadcastTTL = atoi(arg);
			else if (http.isHeader(PCX_HS_MAXBCTTL))
				chanMgr->maxBroadcastTTL = atoi(arg);
			else if (http.isHeader(PCX_HS_RELAYBC))
				servMgr->relayBroadcast = atoi(arg);
		}

    }

	if (!clientID.isSame(networkID))
		throw HTTPException(HTTP_SC_UNAVAILABLE,503);

	if (!versionValid)
		throw HTTPException(HTTP_SC_UNAUTHORIZED,401);


    sock->writeLine(GNU_OK);
    sock->writeLine("");
}
// -----------------------------------
void Servent::handshakeIn()
{
	sock->timeout = 10000;


	int osType=0;

	HTTP http(*sock);


	bool versionValid = false;
	bool diffRootVer = false;

	GnuID clientID;
	clientID.clear();

    while (http.nextHeader())
    {
		LOG_DEBUG(http.cmdLine);

		char *arg = http.getArgStr();
		if (!arg)
			continue;

		if (http.isHeader(HTTP_HS_AGENT))
		{
			agent.set(arg);

			if (strnicmp(arg,"PeerCast/",9)==0)
			{
				versionValid = (stricmp(arg+9,MIN_CONNECTVER)>=0) && (stricmp(arg+9,MAX_CONNECTVER)<=0);
				diffRootVer = stricmp(arg+9,MIN_ROOTVER)<0;
			}
		}else if (http.isHeader(PCX_HS_NETWORKID))
		{
			clientID.fromStr(arg);

		}else if (http.isHeader(PCX_HS_PRIORITY))
		{
			priorityConnect = atoi(arg)!=0;

		}else if (http.isHeader(PCX_HS_ID))
		{
			GnuID id;
			id.fromStr(arg);
			if (id.isSame(servMgr->sessionID))
				throw StreamException("Servent loopback");

		}else if (http.isHeader(PCX_HS_OS))
		{
			if (stricmp(arg,PCX_OS_LINUXDYN)==0)
				osType = 1;
			else if (stricmp(arg,PCX_OS_LINUXSTA)==0)
				osType = 2;
			else if (stricmp(arg,PCX_OS_WIN32)==0)
				osType = 3;
			else if (stricmp(arg,PCX_OS_MACOSX)==0)
				osType = 4;
			else if (stricmp(arg,PCX_OS_WINAMP2)==0)
				osType = 5;
		}

    }

	if (!clientID.isSame(networkID))
		throw HTTPException(HTTP_SC_UNAVAILABLE,503);

	// if this is a priority connection and all incoming connections 
	// are full then kill an old connection to make room. Otherwise reject connection.
	if (!priorityConnect)
		if (servMgr->inFull())
			throw HTTPException(HTTP_SC_UNAVAILABLE,503);

	if (!versionValid)
		throw HTTPException(HTTP_SC_FORBIDDEN,403);


    sock->writeLine(GNU_OK);

    sock->writeLine("%s %s",HTTP_HS_AGENT,PCX_AGENT);
    sock->writeLine("%s %s",PCX_HS_SUBNET,"peercast"); // depreciated

	if (networkID.isSet())
	{
		char idStr[64];
		networkID.toStr(idStr);
		sock->writeLine("%s %s",PCX_HS_NETWORKID,idStr);
	}

	if (servMgr->isRoot)
	{
		sock->writeLine("%s %d",PCX_HS_FLOWCTL,servMgr->useFlowControl?1:0);
		sock->writeLine("%s %d",PCX_HS_MINBCTTL,chanMgr->minBroadcastTTL);
		sock->writeLine("%s %d",PCX_HS_MAXBCTTL,chanMgr->maxBroadcastTTL);
		sock->writeLine("%s %d",PCX_HS_RELAYBC,servMgr->relayBroadcast);
		//sock->writeLine("%s %d",PCX_HS_FULLHIT,2);


		if (diffRootVer)
		{
			sock->writeString(PCX_HS_DL);
			switch(osType)
			{
				case 1:
					sock->writeLine(PCX_DL_LINUXDYN);
					break;
				case 2:
					sock->writeLine(PCX_DL_LINUXSTA);
					break;
				case 3:
					sock->writeLine(PCX_DL_WIN32);
					break;
				case 4:
					sock->writeLine(PCX_DL_MACOSX);
					break;
				case 5:
					sock->writeLine(PCX_DL_WINAMP2);
					break;
				default:
					sock->writeLine(PCX_DL_URL);
					break;
			}
		}

		sock->writeLine("%s %s",PCX_HS_MSG,servMgr->rootMsg.cstr());
	}


	char hostIP[64];
	Host h = sock->host;
	h.IPtoStr(hostIP);
    sock->writeLine("Remote-IP: %s",hostIP);


    sock->writeLine("");


	while (http.nextHeader());

}

// -----------------------------------
void Servent::handshakeStream(Channel *ch, bool isRaw)
{
	sock->timeout = 10000;

	HTTP http(*sock);

	char idStr[64];
	ch->info.id.toStr(idStr);

	while (http.nextHeader())
	{
		char *arg = http.getArgStr();
		if (!arg)
			continue;

		if (http.isHeader(HTTP_HS_AGENT))
			agent.set(arg);

		if (ch->info.contentType == ChanInfo::T_MP3)
			if (http.isHeader("icy-metadata"))
				addMetadata = atoi(arg) > 0;

		LOG_DEBUG("Stream: %s",http.cmdLine);
	}

	if (addMetadata && isRaw)		// winamp mp3 metadata check
	{
	    sock->writeLine(ICY_OK);


		sock->writeLine("%s %s",HTTP_HS_SERVER,PCX_AGENT);
		sock->writeLine("icy-name:%s",ch->getName());
		sock->writeLine("icy-br:%d",ch->getBitrate());
		sock->writeLine("icy-genre:%s",ch->info.genre.cstr());
		sock->writeLine("icy-url:%s",ch->info.url.cstr());
		sock->writeLine("icy-metaint:%d",chanMgr->icyMetaInterval);
		sock->writeLine("%s %s",PCX_HS_CHANNELID,idStr);

	    sock->writeLine("%s %s",HTTP_HS_CONTENT,MIME_MP3);

	}else
	{

	    sock->writeLine(HTTP_SC_OK);

		if ((ch->info.contentType != ChanInfo::T_WMV) && (ch->info.contentType != ChanInfo::T_WMA))
		{
			sock->writeLine("%s %s",HTTP_HS_SERVER,PCX_AGENT);

		//	sock->writeLine("icy-metaint:0");
			sock->writeLine("Accept-Ranges: none");

			sock->writeLine("x-audiocast-name: %s",ch->getName());
			sock->writeLine("x-audiocast-bitrate: %d",ch->getBitrate());
			sock->writeLine("x-audiocast-genre: %s",ch->info.genre.cstr());
			sock->writeLine("x-audiocast-description: %s",ch->info.desc.cstr());
			sock->writeLine("x-audiocast-url: %s",ch->info.url.cstr());
			sock->writeLine("%s %s",PCX_HS_CHANNELID,idStr);
		}


		if (isRaw)
		{
			switch (ch->info.contentType)
			{
				case ChanInfo::T_OGG:
					sock->writeLine("%s %s",HTTP_HS_CONTENT,MIME_XOGG);
					break;
				case ChanInfo::T_MP3:
					sock->writeLine("%s %s",HTTP_HS_CONTENT,MIME_MP3);
					break;
				case ChanInfo::T_MOV:
					sock->writeLine("Connection: close");
					sock->writeLine("Content-Length: 10000000");
					sock->writeLine("%s %s",HTTP_HS_CONTENT,MIME_MOV);
					break;
				case ChanInfo::T_MPG:
					sock->writeLine("%s %s",HTTP_HS_CONTENT,MIME_MPG);
					break;
				case ChanInfo::T_NSV:
					sock->writeLine("%s %s",HTTP_HS_CONTENT,MIME_NSV);
					break;
				case ChanInfo::T_WMA:
				case ChanInfo::T_WMV:
				{
					sock->writeLine("Server: Rex/9.0.0.2980");
					sock->writeLine("Pragma: no-cache");
					sock->writeLine("Pragma: client-id=3587303426");
					sock->writeLine("%s %s",HTTP_HS_CONTENT,MIME_MMS);
					sock->writeLine("Pragma: features=\"broadcast,playlist\"");
					sock->writeLine("Cache-Control: no-cache");
					break;
				}
			}
		
		}else
		{
			sock->writeLine("%s %s",HTTP_HS_CONTENT,MIME_XPEERCAST);
		}
	}


	
    sock->writeLine("");

}

// -----------------------------------
void Servent::handshakeGiv(Channel *ch)
{
	sock->timeout = 10000;

	char idstr[64];

	pushID.toStr(idstr);
    sock->writeLine("GIV %d:%s/%s",chanIndex,idstr,ch->info.name.cstr());
	handshakeStream(ch,false);
}


// -----------------------------------
void Servent::process()
{
	try 
	{
	

		gnuStream.init(sock);
		setStatus(S_CONNECTED);


		sock->timeout = 10000;		// 10 seconds

		gnuStream.ping(2);

		if (type != T_LOOKUP)
			chanMgr->broadcastRelays(this,chanMgr->minBroadcastTTL,2);

		lastPacket = lastPing = sys->getTime();
		bool doneBigPing=false;

		const unsigned int	abortTimeoutSecs = 60;		// abort connection after 60 secs of no activitiy
		const unsigned int	packetTimeoutSecs = 30;		// ping connection after 30 secs of no activity

		unsigned int currBytes=0;
		unsigned int lastWait=0;

		unsigned int lastTotalIn=0,lastTotalOut=0;

		while (thread.active && sock->active())
		{

			if (sock->readPending())
			{
				lastPacket = sys->getTime();

				if (gnuStream.readPacket(pack))
				{
					unsigned int ver = pack.id.getVersion();


					char ipstr[64];
					sock->host.toStr(ipstr);

					GnuID routeID;
					GnuStream::R_TYPE ret = GnuStream::R_PROCESS;

					if ((ver < MIN_PACKETVER) || (ver >= MAX_PACKETVER))
						ret = GnuStream::R_BADVERSION;

					if (pack.func != GNU_FUNC_PONG)
						if (servMgr->seenPacket(pack))
							ret = GnuStream::R_DUPLICATE;

					seenIDs.addGnuID(pack.id);

					servMgr->addVersion(ver);


					if (ret == GnuStream::R_PROCESS)
					{
						GnuID routeID;
						ret = gnuStream.processPacket(pack,this,routeID);

						if (flowControl && (ret == GnuStream::R_BROADCAST))
							ret = GnuStream::R_DROP;

					}

					switch(ret)
					{
						case GnuStream::R_BROADCAST:
							if (servMgr->broadcast(pack,this))
								stats.add(Stats::NUMBROADCASTED);
							else
								stats.add(Stats::NUMDROPPED);
							break;
						case GnuStream::R_ROUTE:
							if (servMgr->route(pack,routeID,NULL))
								stats.add(Stats::NUMROUTED);
							else
								stats.add(Stats::NUMDROPPED);
							break;
						case GnuStream::R_ACCEPTED:
							stats.add(Stats::NUMACCEPTED);
							break;
						case GnuStream::R_DUPLICATE:
							stats.add(Stats::NUMDUP);
							break;
						case GnuStream::R_DEAD:
							stats.add(Stats::NUMDEAD);
							break;
						case GnuStream::R_DISCARD:
							stats.add(Stats::NUMDISCARDED);
							break;
						case GnuStream::R_BADVERSION:
							stats.add(Stats::NUMOLD);
							break;
						case GnuStream::R_DROP:
							stats.add(Stats::NUMDROPPED);
							break;
					}


					LOG_NETWORK("packet in: %s-%s, %d bytes, %d hops, %d ttl, v%05x from %s",GNU_FUNC_STR(pack.func),GnuStream::getRouteStr(ret),pack.len,pack.hops,pack.ttl,pack.id.getVersion(),ipstr);



				}else{
					LOG_ERROR("Bad packet");
				}
			}


			GnuPacket *p;

			if ((p=outPacketsPri.curr()))				// priority packet
			{
				gnuStream.sendPacket(*p);
				seenIDs.addGnuID(p->id);
				outPacketsPri.next();
			} else if ((p=outPacketsNorm.curr())) 		// or.. normal packet
			{
				gnuStream.sendPacket(*p);
				seenIDs.addGnuID(p->id);
				outPacketsNorm.next();
			}

			int lpt =  sys->getTime()-lastPacket;

			if (!doneBigPing)
			{
				if ((sys->getTime()-lastPing) > 15)
				{
					gnuStream.ping(7);
					lastPing = sys->getTime();
					doneBigPing = true;
				}
			}else{
				if (lpt > packetTimeoutSecs)
				{
					
					if ((sys->getTime()-lastPing) > packetTimeoutSecs)
					{
						gnuStream.ping(1);
						lastPing = sys->getTime();
					}

				}
			}
			if (lpt > abortTimeoutSecs)
				throw TimeoutException();


			unsigned int totIn = sock->totalBytesIn-lastTotalIn;
			unsigned int totOut = sock->totalBytesOut-lastTotalOut;

			unsigned int bytes = totIn+totOut;

			lastTotalIn = sock->totalBytesIn;
			lastTotalOut = sock->totalBytesOut;


			int delay = sys->idleSleepTime;
			if ((bytes) && (servMgr->serventBandwidth >= 8))
				delay = (bytes*1000)/(servMgr->serventBandwidth/8);	// set delay relative packetsize

			if (delay < (int)sys->idleSleepTime)
				delay = sys->idleSleepTime;

			//LOG("delay %d, in %d, out %d",delay,totIn,totOut);

			sys->sleep(delay);


#if 0
			// if this is a private servent (has networkID) then check to see
			// that the channel is still connected. Otherwise, closedown.
			if (networkID.isSet())
			{
				bool hasChan=true;
				Channel *ch = chanMgr->findChannelByID(networkID);
				if (ch)
				{
					if (ch->status == Channel::S_IDLE)
						throw StreamException("NetID idle");
				}
			}
#endif			

		}


	}catch(StreamException &e)
	{
		LOG_ERROR("Servent closed: %s",e.msg);
	}

}
// -----------------------------------
void Servent::processRelay()
{
	try 
	{
	
		sock->timeout = 10000;		// 10 seconds

		gnuStream.init(sock);
		setStatus(S_CONNECTED);

		gnuStream.ping(2);

		while (thread.active && sock->active())
		{
			if (gnuStream.readPacket(pack))
			{
				char ipstr[64];
				sock->host.toStr(ipstr);
				unsigned int ver = pack.id.getVersion();

				servMgr->addVersion(ver);
				
				LOG_NETWORK("packet in: %d v%05x from %s",pack.func,pack.getVersion(),ipstr);

				//if (pack.id.getVersion() < MIN_PACKETVER)
				//	break;

				if (pack.func == 0)		// if ping then pong back some hosts and close
				{
					
					Host hl[32];
					int cnt = servMgr->getNewestServents(hl,32,sock->host);	
					if (cnt)
					{
						int start = sys->rnd() % cnt;
						for(int i=0; i<8; i++)
						{
							GnuPacket pong;
							pack.hops = 1;
							pong.initPong(hl[start],false,pack);
							gnuStream.sendPacket(pong);

							char ipstr[64];
							hl[start].toStr(ipstr);

							//LOG_NETWORK("Pong %d: %s",start+1,ipstr);
							start = (start+1) % cnt;
						}
						char str[64];
						sock->host.toStr(str);
						LOG_NETWORK("Sent 8 pongs to %s",str);
					}else
					{
						LOG_NETWORK("No Pongs to send");
						//return;
					}
				}else if (pack.func == 1)		// pong?
				{
					MemoryStream pong(pack.data,pack.len);

					int ip,port;
					port = pong.readShort();
					ip = pong.readLong();
					ip = SWAP4(ip);

					LOG_NETWORK("pong: %d.%d.%d.%d:%d",ip>>24&0xff,ip>>16&0xff,ip>>8&0xff,ip&0xff,port);

					if ((ip) && (port))
					{
						Host h(ip,port);
						servMgr->addHost(h,ServHost::T_SERVENT,0,networkID);
					}
					return;
				}

				if (gnuStream.packetsIn > 5)	// die if we get too many packets
					return;
			}
		}


	}catch(StreamException &e)
	{
		LOG_ERROR("Relay: %s",e.msg);
	}



}

// -----------------------------------
int Servent::givProc(ThreadInfo *thread)
{
	thread->lock();
	Servent *sv = (Servent*)thread->data;
	try 
	{
		sv->processStream(true,false);
	}catch(StreamException &e)
	{
		LOG_ERROR("GIV: %s",e.msg);
	}

	sv->endThread();
	return 0;
}
// -----------------------------------
int Servent::outgoingProc(ThreadInfo *thread)
{
	thread->lock();

	Servent *sv = (Servent*)thread->data;
		
	try 
	{
		sv->setStatus(S_CONNECTING);
		sv->sock->timeout = 10000;		// 10 seconds handshake
		sv->sock->connect();
		sv->setStatus(S_HANDSHAKE);
		sv->handshakeOut();
		sv->process();

	}catch(TimeoutException &e)
	{
		LOG_ERROR("Outgoing Timeout: %s",e.msg);
		servMgr->deadHost(sv->sock->host,ServHost::T_SERVENT);
		sv->setStatus(S_TIMEOUT);
	}catch(StreamException &e)
	{
		servMgr->deadHost(sv->sock->host,ServHost::T_SERVENT);
		LOG_ERROR("Outgoing: %s",e.msg);
		sv->setStatus(S_REFUSED);

		if (sv->type == T_LOOKUP)
			sys->sleep(30000);
	}


	sv->endThread();
	return 0;
}
// -----------------------------------
int Servent::incomingProc(ThreadInfo *thread)
{
	thread->lock();

	Servent *sv = (Servent*)thread->data;
	
	try 
	{
		sv->handshakeIncoming();
	}catch(HTTPException &e)
	{
		try
		{
			sv->sock->writeLine(e.msg);
			if (e.code == 401)
				sv->sock->writeLine("WWW-Authenticate: Basic realm=\"PeerCast\"");
			sv->sock->writeLine("");
		}catch(StreamException &){}
		LOG_ERROR("Incoming HTTP error: %s",e.msg);
	}catch(StreamException &e)
	{
		LOG_ERROR("Incoming Connect error: %s",e.msg);
	}

	sv->endThread();
	return 0;
}
// -----------------------------------
void Servent::processServent()
{
	setStatus(S_HANDSHAKE);

	handshakeIn();

	if (!sock)
		throw StreamException("Servent has no socket");


	if (servMgr->isRoot && !servMgr->needConnections())
		processRelay();
	else
		process();

}

// -----------------------------------
void Servent::processStream(bool push, bool isRaw)
{
		
	Channel *ch = NULL;
	try 
	{
		setStatus(S_HANDSHAKE);


		ch = chanMgr->findChannelByID(chanID);
		if (!ch)
			throw StreamException("Not found");

		chanIndex = ch->index;

		if (push)
			handshakeGiv(ch);	
		else
			handshakeStream(ch,isRaw);

		servMgr->totalStreams++;


		Host host = sock->host;
		host.port = 0;	// force to 0 so we ignore the incoming port


		if (isRaw)
		{
			// we have agent string now, so check again.
			if (!canPreview())
				throw StreamException("Preview disallowed");

			servMgr->addHost(host,ServHost::T_STREAM,0,networkID);

			if ((addMetadata) && (chanMgr->icyMetaInterval))
				sendRawMetaChannel(ch,chanMgr->icyMetaInterval);
			else
				sendRawChannel(ch);
		}else
		{
			servMgr->addHost(host,ServHost::T_CHANNEL,0,networkID);
			sendChannel(ch);
		}

		setStatus(S_CLOSING);

	}catch(StreamException &e)
	{
		setStatus(S_ERROR);
		LOG_ERROR("Stream Error: %s",e.msg);
	}
}

// -----------------------------------------
#if 0
// debug
		FileStream file;
		file.openReadOnly("c://test.mp3");

		LOG_DEBUG("raw file read");
		char buf[4000];
		int cnt=0;
		while (!file.eof())
		{
			LOG_DEBUG("send %d",cnt++);
			file.read(buf,sizeof(buf));
			sock->write(buf,sizeof(buf));

		}
		file.close();
		LOG_DEBUG("raw file sent");

	return;
// debug
#endif
// -----------------------------------
bool Servent::waitForChannelHeader(Channel *ch)
{
	for(int i=0; i<60; i++)
	{
		if (ch->isPlaying() && ch->syncPos)
			return true;

		if (!thread.active || !sock->active())
			break;
		sys->sleep(1000);
	}
	return false;
}
// -----------------------------------
bool Servent::checkPreview(unsigned int connectTime)
{
	Host h = sock->host;
	h.port = 0;			// ignore incoming port number

	if (!servMgr->isFiltered(ServFilter::F_DIRECT,h))
		return false;

	if (isPrivate())	// always allow private clients
		return true;

	if (agent.contains("PeerCast"))		// allow connections from peercast clients (direct relays etc..)
		return true;

	if (servMgr->maxPreviewTime == 0)
		return true;

	if ((sys->getTime()-connectTime) <= servMgr->maxPreviewTime)
		return true;

	return false;
}
// -----------------------------------
bool Servent::canPreview()
{	
	// probably not needed, but just in case we bind to the actual IP address and not localhost
	if (sock->host.ip == servMgr->serverHost.ip)
		return true;

	Host h = getHost();
	h.port = 0;

	if (!servMgr->isFiltered(ServFilter::F_DIRECT,h))
		return false;

	if (isPrivate())	// always allow private clients
		return true;

	if (agent.contains("PeerCast"))		// allow connections from peercast clients (direct relays etc..)
		return true;

	if (servMgr->seenHost(sock->host,ServHost::T_STREAM,servMgr->maxPreviewWait))
		return false;

	return true;
}
// -----------------------------------
void Servent::sendRawChannel(Channel *ch)
{

	ch->numListeners++;
	outputBitrate = ch->getBitrate();

	try
	{

		sock->timeout = 10000;
		setStatus(S_CONNECTED);


		if (!waitForChannelHeader(ch))
			throw StreamException("Channel not ready");


		// pre-send HEAD data, and set current position to the packet after that
		if (ch->headMeta.len)
		{
			LOG_DEBUG("Send %d bytes header ",ch->headMeta.len);
			sock->write(ch->headMeta.data,ch->headMeta.len);
			currPos=ch->headMeta.startPos;
		}else
			currPos=0;

		LOG_DEBUG("Starting Raw stream at %d/%d-%d: %s",currPos,ch->chanData.firstPacket,ch->chanData.currPacket,ch->info.name.cstr());

		unsigned int mySync,chanSync;
		mySync = chanSync = 0;
		unsigned int syncTimeout=0;
		unsigned int connectTime=sys->getTime();

		ChanPacket pack;

		while ((thread.active) && sock->active())
		{
			if (!checkPreview(connectTime))
				throw StreamException("Preview time limit reached");

			if (!ch->isActive())
				throw StreamException("Channel closed");

			currPos = ch->chanData.readPacket(currPos,pack);


			//LOG_CHANNEL("Send POS: %d",currPos);

			if (pack.type == 'SYNC')
			{
				chanSync = *(unsigned int *)pack.data;
				//LOG_CHANNEL("Send SYNC: %d - %d",mySync,chanSync);
				int rs = mySync-chanSync;
				if (rs > ChanPacketBuffer::MAX_PACKETS)
					mySync = chanSync-1;

			}else if (pack.type == 'DATA')
			{
				// skip packets until we`re back in sync
				if (chanSync > mySync)
				{
					//LOG_CHANNEL("Send DATA: %d",pack.len);
					syncTimeout=0;
					mySync = chanSync;
					sock->write(pack.data,pack.len);
				}else
					LOG_CHANNEL("SYNC wait: %d - %d",mySync-chanSync,syncTimeout);
			}else if (pack.type == 'HEAD')
			{
				sock->write(pack.data,pack.len);
			}

			syncTimeout++;
			if (syncTimeout > 30)
				throw StreamException("Unable to sync");

		}
	}catch(StreamException &e)
	{
		LOG_ERROR("Stream channel: %s",e.msg);
	}
	if (ch->numListeners)
		ch->numListeners--;
}

// -----------------------------------
void Servent::sendRawMetaChannel(Channel *ch, int interval)
{
	ch->numListeners++;
	outputBitrate = ch->getBitrate();

	try
	{

		sock->timeout = 10000;
		setStatus(S_CONNECTED);

		LOG_DEBUG("Starting Raw Meta stream: %s (metaint: %d)",ch->info.name.cstr(),interval);

		if (!waitForChannelHeader(ch))
			throw StreamException("Channel not ready");


		if (ch->headMeta.len)
		{
			LOG_DEBUG("Send %d bytes header ",ch->headMeta.len);
			sock->write(ch->headMeta.data,ch->headMeta.len);
		}

		String lastTitle,lastURL;

		currPos=0;
		unsigned int mySync=0,chanSync=0;

		int		lastMsgTime=sys->getTime();
		bool	showMsg=true;

		ChanPacket pack;

		char buf[16384];
		int bufPos=0;

		if ((interval > sizeof(buf)) || (interval < 1))
			throw StreamException("Bad ICY Meta Interval value");

		unsigned int syncTimeout=0;
		unsigned int connectTime = sys->getTime();

		while ((thread.active) && sock->active())
		{
			if (!checkPreview(connectTime))
				throw StreamException("Preview time limit reached");

			if (!ch->isActive())
				throw StreamException("Channel closed");

			currPos = ch->chanData.readPacket(currPos,pack);
			MemoryStream mem(pack.data,pack.len);

			if (pack.type == 'SYNC')
			{
				chanSync = mem.readLong();
				int rs = mySync-chanSync;
				if (rs > ChanPacketBuffer::MAX_PACKETS)
					mySync = chanSync-1;
			}

			if (pack.type == 'DATA')
			{
				// skip packets until we`re back in sync
				if (chanSync > mySync)
				{
					syncTimeout = 0;
					mySync = chanSync;

					int len = pack.len;
					char *p = pack.data;
					while (len)
					{
						int rl = len;
						if ((bufPos+rl) > interval)
							rl = interval-bufPos;
						memcpy(&buf[bufPos],p,rl);
						bufPos+=rl;
						p+=rl;
						len-=rl;

						if (bufPos >= interval)
						{
							bufPos = 0;	
							sock->write(buf,interval);

							if (chanMgr->broadcastMsgInterval)
								if ((sys->getTime()-lastMsgTime) >= chanMgr->broadcastMsgInterval)
								{
									showMsg ^= true;
									lastMsgTime = sys->getTime();
								}

							String *metaTitle = &ch->info.track.title;
							if (!ch->info.comment.isEmpty() && (showMsg))
								metaTitle = &ch->info.comment;


							if (!metaTitle->isSame(lastTitle) || !ch->info.url.isSame(lastURL))
							{

								char tmp[1024];
								String title,url;

								title = *metaTitle;
								url = ch->info.url;

								title.convertTo(String::T_META);
								url.convertTo(String::T_META);

								sprintf(tmp,"StreamTitle='%s';StreamUrl='%s';\0",title.cstr(),url.cstr());
								int len = ((strlen(tmp) + 15+1) / 16);
								sock->writeChar(len);
								sock->write(tmp,len*16);

								lastTitle = *metaTitle;
								lastURL = ch->info.url;

								LOG_DEBUG("StreamTitle: %s, StreamURL: %s",lastTitle.cstr(),lastURL.cstr());

							}else
							{
								sock->writeChar(0);					
							}

						}
					}
				}else
				{
					LOG_DEBUG("SYNC wait: %d - %d",mySync-chanSync,syncTimeout);
				}
			}

			syncTimeout++;
			if (syncTimeout > 30)
				throw StreamException("Unable to sync");

		}
	}catch(StreamException &e)
	{
		LOG_ERROR("Stream channel: %s",e.msg);
	}
	if (ch->numListeners)
		ch->numListeners--;
}
// -----------------------------------
void Servent::sendChannel(Channel *ch)
{
	ch->numRelays++;
	outputBitrate = ch->getBitrate();

	try
	{


		sock->timeout = 10000;
		setStatus(S_CONNECTED);

		LOG_DEBUG("Starting PeerCast stream: %s",ch->info.name.cstr());


		currPos=0;

		sock->writeTag("PCST");

		ChanPacket pack;

		pack.init('HEAD',ch->headMeta.data,ch->headMeta.len);
		pack.write(*sock);

		pack.init('META',ch->insertMeta.data,ch->insertMeta.len);
		pack.write(*sock);
		

		while ((thread.active) && sock->active())
		{
			if (!ch->isActive())
				throw StreamException("Channel closed");

			unsigned int np = ch->chanData.readPacket(currPos,pack);
			if ((np-currPos) > 1)
				LOG_DEBUG("sendChannel skip: %d",np-currPos);
			currPos = np;

			pack.write(*sock);
		}

	}catch(StreamException &e)
	{
		LOG_ERROR("Stream channel: %s",e.msg);
	}

	if (ch->numRelays)
		ch->numRelays--;
}

// -----------------------------------
int Servent::serverProc(ThreadInfo *thread)
{
	thread->lock();

	Servent *sv = (Servent*)thread->data;

	try 
	{
		if (!sv->sock)
			throw StreamException("Server has no socket");

		sv->setStatus(S_LISTENING);
		//LOG4("Listening on port %d",sv->sock->host.port);


		char servIP[64];
		sv->sock->host.toStr(servIP);

		if (servMgr->isRoot)
			LOG_DEBUG("Root Server started: %s",servIP);
		else
			LOG_DEBUG("Server started: %s",servIP);
		

		while ((thread->active) && (sv->sock->active()))
		{
			ClientSocket *cs = sv->sock->accept();
			if (cs)
			{		
				// if global IP then we`re not firewalled
				if (cs->host.globalIP())
					servMgr->setFirewall(ServMgr::FW_OFF);

				Servent *ns = servMgr->allocServent();
				if (ns)
				{
					ns->networkID = servMgr->networkID;
					ns->initIncoming(cs,sv->allow);
				}else
					LOG_ERROR("Out of servents");
			}
		}
	}catch(StreamException &e)
	{
		LOG_ERROR("Server Error: %s:%d",e.msg,e.err);
	}

	
	LOG_DEBUG("Server stopped");

	sv->endThread();
	return 0;
}
 
