/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  Please
 * see the License for the specific language governing rights and
 * limitations under the License.
 *
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*
	File:		StreamingLoadTool.cpp

	Contains:       Tool that simulates streaming movie load
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>

#ifndef kVersionString
#include "revision.h"
#endif
#ifndef __Win32__
#include <unistd.h>
#include <netdb.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#endif

#include "ClientSession.h"
#include "FilePrefsSource.h"
#include "OSMemory.h"
#include "SocketUtils.h"
#include "StringFormatter.h"
#include "Socket.h"
#include "OS.h"
#include "Task.h"
#include "TimeoutTask.h"
#ifndef __MacOSX__
#include "getopt.h"
#include "revision.h"
#endif

#define STREAMINGLOADTOOL_DEBUG 0

//
// Static data
static UInt32	sConnectionsThatErrored = 0;
static UInt32	sFailedConnections = 0;
static UInt32	sSuccessfulConnections = 0;
static FILE*	sLog = NULL;

static ClientSession** sClientSessionArray = NULL;
static UInt32 sNumClients = 1;
static Bool16 sGotSigInt = false;
static Bool16 sQuitNow = false;
static SInt64 sSigIntTime = 0;
static Bool16 sVerbose = false;

int main(int argc, char *argv[]);

//
// Helper functions
char* 	GetClientTypeDescription(ClientSession::ClientType inClientType);
void	DoDNSLookup(FilePrefsSource* inFileParser, UInt32* inURLIndexes, UInt32* ioIPAddrs, UInt32 inNumURLs);
void 	RecordClientInfoBeforeDeath(ClientSession* inSession);
char*	GetDeathReasonDescription(UInt32 inDeathReason);
char*	GetPayloadDescription(QTSS_RTPPayloadType inPayload);
void	CheckForStreamingLoadToolDotMov(UInt32* inIPAddrArray, UInt32 inNumURLs, UInt16 inPort);
UInt32 CalcStartTime(Bool16 inRandomThumb, UInt32 inMovieLength);
extern char* optarg;



#ifndef __Win32__
void sigcatcher(int sig, int /*sinfo*/, struct sigcontext* /*sctxt*/);

void sigcatcher(int sig, int /*sinfo*/, struct sigcontext* /*sctxt*/)
{
	//printf("sigcatcher =%d\n", sig);

	if ((sig == SIGINT) || (sig == SIGTERM))
	{       // TheTime check and sQuitNow flag tests are needed test with Linux and OSX. 
		if (sGotSigInt && (OS::Milliseconds() > sSigIntTime + 500) )
		{	if (!sQuitNow)// print only once
				printf("Force quitting\n");
			sQuitNow = true;
		}
		else
		{
			sSigIntTime = OS::Milliseconds();
			sGotSigInt = true;
		}
	}
}
#endif

int main(int argc, char *argv[])
{
#ifndef __Win32__
	struct sigaction act;
	
#if defined(sun) || defined(i386) || defined (__MacOSX__) || defined(__powerpc__) || defined (__osf__) || defined (__sgi_cc__) || defined (__hpux__)
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    act.sa_handler = (void(*)(int))&sigcatcher;
#elif defined(__sgi__) 
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	act.sa_handler = (void(*)(...))&sigcatcher;
#else
    act.sa_mask = 0;
    act.sa_flags = 0;
    act.sa_handler = (void(*)(...))&sigcatcher;
#endif
	(void)::sigaction(SIGPIPE, &act, NULL);
	(void)::sigaction(SIGINT, &act, NULL);
	(void)::sigaction(SIGTERM, &act, NULL);

#if __MacOSX__ || __solaris__
	//grow our pool of file descriptors to the max!
	struct rlimit rl;
               
	rl.rlim_cur = Socket::kMaxNumSockets;
	rl.rlim_max = Socket::kMaxNumSockets;

	setrlimit (RLIMIT_NOFILE, &rl);
#endif
#else
	//
	// Start Win32 DLLs
	WORD wsVersion = MAKEWORD(1, 1);
	WSADATA wsData;
	(void)::WSAStartup(wsVersion, &wsData);
#endif

#ifdef __Win32__
	char* configFilePath = "streamingloadtool.cfg";
#else
	char* configFilePath = "streamingloadtool.conf";
#endif
	Bool16 dropPost = false;
	ClientSession::ClientType theClientType = ClientSession::kRTSPUDPClientType;
	UInt16 thePort = 554;
	UInt32 theMovieLength = 60;
	Bool16 runForever = false;
	UInt32 theHTTPCookie = 1;
	Bool16 shouldLog = false;
	char* logPath = "streamingloadtool.log";
	UInt32 proxyIP = 0;
	Bool16 appendJunk = false;
	UInt32 theReadInterval = 50;
	UInt32 sockRcvBuf = 32768;
	Float32 lateTolerance = 0;
	char* rtpMetaInfo = NULL;
	Float32 speed = 1;
	Bool16 verbose = false;
	char* packetPlayHeader = NULL;
    UInt32 overbufferwindowInK = 0;
    Bool16 randomThumb = false;
	//
	// Set up our User Agent string for the RTSP client
	char theUserAgentStr[128];
	::sprintf(theUserAgentStr, "StreamingLoadTool-%s",kVersionString);
	RTSPClient::SetUserAgentStr(theUserAgentStr);
	
	UInt32	theURLIndexArraySize = 8;
	UInt32* theURLIndexArray = NEW UInt32[theURLIndexArraySize];
	UInt32 theNumURLs = 0;
    char* controlID = NULL;
	char			ch = 0;
	//
	// Read our command line options
	while( (ch = getopt(argc, argv, "vf:c:i:d")) != -1 )
	{
		switch( ch )
		{
			case 'v':
				printf("StreamingLoadTool %s, built on %s, %s.\n", kVersionString, __DATE__  , __TIME__);
				printf("Usage: StreamingLoadTool [-i urlid] [-f path] [-c #]\n");
				printf("-f: Config file to use. Defaults to streamingloadtool.conf\n");
				printf("-c: HTTP cookie to use. Overrides what is in config file\n");
				printf("-d: Debug messages\n");
				printf("-i: RTSP stream URL id (i.e. trackID or streamID etc.). Default is -i trackID\n");
				exit(0);
				break;
			case 'f':
				configFilePath = optarg;
				break;
		    case 'i':
		        controlID = optarg;
		        break;
			case 'c':
				theHTTPCookie = atoi(optarg);
				break;
			case 'd':
				verbose = true;
				sVerbose = true;
				break;
		}
	}
	
	//
	// Get settings from the file
	FilePrefsSource theFileParser(true);
	if (theFileParser.InitFromConfigFile(configFilePath))
	{
		printf("Couldn't find StreamingLoadTool config file at: %s\n", configFilePath);
		::exit(-1);
	}
	
	
	for (UInt32 x = 0; true; x++)
	{
		char* theValue = theFileParser.GetValueAtIndex(x);
		char* theKey = theFileParser.GetKeyAtIndex(x);

		if (theKey == NULL)
			break;
		
		if (theValue != NULL)
		{

			if (::strcmp("clienttype", theKey) == 0)
			{
				if (::strcmp("http", theValue) == 0)
					theClientType = ClientSession::kRTSPHTTPClientType;
				else if (::strcmp("reliableudp", theValue) == 0)
					theClientType = ClientSession::kRTSPReliableUDPClientType;
				else if (::strcmp("tcp", theValue) == 0)
					theClientType = ClientSession::kRTSPTCPClientType;
				else
					theClientType = ClientSession::kRTSPUDPClientType;
			}
			else if (::strcmp("player", theKey) == 0)
			{
				if (theValue[0] != 0)
	            {	
	                ::strncpy(theUserAgentStr, theValue, sizeof(theUserAgentStr));
                    theUserAgentStr[sizeof(theUserAgentStr) -1] = 0;
                    RTSPClient::SetUserAgentStr(theUserAgentStr);
                }
			}
			else if (::strcmp("droppost", theKey) == 0)
			{
				if (::strcmp("yes", theValue) == 0)
					dropPost = true;
			}
			else if (::strcmp("concurrentclients", theKey) == 0)
			{
				::sscanf(theValue, "%lu", &sNumClients);
			}
			else if (::strcmp("port", theKey) == 0)
			{
				UInt32 tempPort = 0;
				::sscanf(theValue, "%lu", &tempPort);
				thePort = (UInt16)tempPort;
			}
			else if (::strcmp("movielength", theKey) == 0)
			{
				::sscanf(theValue, "%lu", &theMovieLength);
			}
			else if (::strcmp("runforever", theKey) == 0)
			{
				if (::strcmp("yes", theValue) == 0)
					runForever = true;
			}
			else if (::strcmp("shouldlog", theKey) == 0)
			{
				if (::strcmp("yes", theValue) == 0)
					shouldLog = true;
			}
			else if (::strcmp("appendjunk", theKey) == 0)
			{
				if (::strcmp("yes", theValue) == 0)
					appendJunk = true;
			}
			else if (::strcmp("logpath", theKey) == 0)
			{
				logPath = theValue;
			}
			else if (::strcmp("proxyip", theKey) == 0)
			{
				proxyIP = SocketUtils::ConvertStringToAddr(theValue);
			}
			else if (::strcmp("clientwindow", theKey) == 0)
			{
				::sscanf(theValue, "%lu", &sockRcvBuf);
			}
			else if (::strcmp("httpcookie", theKey) == 0)
			{
				if (theHTTPCookie == 1)
				{
					// Ignore if set by command line
					::sscanf(theValue, "%lu", &theHTTPCookie);
					theHTTPCookie *= 1000000;
				}
			}
			else if (::strcmp("readinterval", theKey) == 0)
			{
				::sscanf(theValue, "%lu", &theReadInterval);
			}
			else if (::strcmp("latetolerance", theKey) == 0)
			{
				::sscanf(theValue, "%f", &lateTolerance);
			}
			else if (::strcmp("rtpmetainfo", theKey) == 0)
			{
				if (::strlen(theValue) > 0)
					rtpMetaInfo = theValue;				
			}
			else if (::strcmp("speed", theKey) == 0)
			{
				::sscanf(theValue, "%f", &speed);
			}
			else if (::strcmp("packetplayheader", theKey) == 0)
			{
				if (::strlen(theValue) > 0)
					packetPlayHeader = theValue;
			}
            else if (::strcmp("overbufferwindowsize", theKey) == 0)
            {
                ::sscanf(theValue, "%lu", &overbufferwindowInK);
            }
			else if (::strcmp("randomthumb", theKey) == 0)
			{
				if (::strcmp("yes", theValue) == 0)
					randomThumb = true;
			}
			else if (::strcmp("url", theKey) == 0)
			{
				if (theNumURLs == theURLIndexArraySize)
				{
					UInt32* tempArray = NEW UInt32[theURLIndexArraySize * 2];
					::memcpy(tempArray, theURLIndexArray, sizeof(UInt32) * theURLIndexArraySize);
					theURLIndexArraySize *= 2;
					delete [] theURLIndexArray;
					theURLIndexArray = tempArray;
				}
				theURLIndexArray[theNumURLs] = x;
				theNumURLs++;
			}
			else
				printf("Found bad directive in StreamingLoadTool config file: %s\n", theKey);
		}
	}
	
	if (theNumURLs == 0)
	{
		printf("Didn't find any URLs in StreamingLoadTool config file.\n");
		exit(-1);
	}
	
	//
	// Figure out what type of clients we are going to run
	if ((theClientType == ClientSession::kRTSPHTTPClientType) && dropPost)
		theClientType = ClientSession::kRTSPHTTPDropPostClientType;
		
	// Do IP lookups on all the URLs
	UInt32* theIPAddrArray = NEW UInt32[theNumURLs + 1];
	
	if (proxyIP == 0)
	{	::DoDNSLookup(&theFileParser, theURLIndexArray, theIPAddrArray, theNumURLs);
	    ::CheckForStreamingLoadToolDotMov(theIPAddrArray, theNumURLs, thePort); // do this on the target not the proxy
	}
	else
	{
	    //
	    // Check for "StreamingLoadTool.mov" on the target machines
		::DoDNSLookup(&theFileParser, theURLIndexArray, theIPAddrArray, theNumURLs);
	    ::CheckForStreamingLoadToolDotMov(theIPAddrArray, theNumURLs, thePort); // do this on the target not the proxy

		//
		// If user specified a proxy to connect through, use that IP address,
		// not the IP address in the URL.
		for (UInt32 ipAddrCounter = 0; ipAddrCounter < theNumURLs; ipAddrCounter++)
			theIPAddrArray[ipAddrCounter] = proxyIP;
	}
	
	//
	// Check for "StreamingLoadTool.mov" on the target machines
    //	::CheckForStreamingLoadToolDotMov(theIPAddrArray, theNumURLs, thePort); // checking for movies on wrong ip if proxy
	
	// Open the log if we are logging
	if (shouldLog)
		sLog = ::fopen(logPath, "w");
	
	//
	// Final setup before running the show
	OS::Initialize();
	OSThread::Initialize();
	OSMemory::SetMemoryError(ENOMEM);
	Socket::Initialize();
	SocketUtils::Initialize();

#if !MACOSXEVENTQUEUE
	::select_startevents();//initialize the select() implementation of the event queue
#endif
	
	TaskThreadPool::AddThreads(OS::GetNumProcessors());
	TimeoutTask::Initialize();
	Socket::StartThread();

	//
	// Create the array of sessions
	if ((sNumClients > theNumURLs) && (!runForever))
		sNumClients = theNumURLs;
		
	sClientSessionArray = NEW ClientSession*[sNumClients];
	
	UInt32 theCurURLIndex = 0;
	for (UInt32 clientCount = 0; clientCount < sNumClients; clientCount++, theCurURLIndex++)
	{
		if (theCurURLIndex == theNumURLs)
			theCurURLIndex = 0;
			
		if (theIPAddrArray[theCurURLIndex] == 0)
			continue;  //skip over non-spammable IP addresses
			
		sClientSessionArray[clientCount] = NEW ClientSession( theIPAddrArray[theCurURLIndex],
																thePort,					
																theFileParser.GetValueAtIndex(theURLIndexArray[theCurURLIndex]),
																theClientType,		// Client type
																theMovieLength,		// Movie length
																CalcStartTime(randomThumb, theMovieLength),		// Movie start time
																5,					// RTCP interval
																0,					// Options interval
																theHTTPCookie++,	// HTTP cookie
																appendJunk,
																theReadInterval,	// Interval between data reads	
																sockRcvBuf,			// socket recv buffer size
																lateTolerance,		// late tolerance
																rtpMetaInfo,
																speed,
																verbose,
																packetPlayHeader,
																overbufferwindowInK);	
																
	     if (sClientSessionArray[clientCount])
	        sClientSessionArray[clientCount]->GetClient()->SetControlID(controlID);

#if STREAMINGLOADTOOL_DEBUG
		printf("Creating a ClientSession for URL: %s\n", theFileParser.GetValueAtIndex(theURLIndexArray[theCurURLIndex]));
#endif
	}
	
	//
	// Now, all we have to do is loop, destroying and re-creating ClientSession objects when they die.
	// Occassionally, lets print out status to show what is currently going on
	Bool16 isStillActive = false;
	UInt32 loopCount = 0;
	do
	{
		if (sGotSigInt)
			break;

		OSThread::Sleep(1000);
		
		isStillActive = false;
		for (UInt32 y = 0; y < sNumClients; y++)
		{
			if (sClientSessionArray == NULL)
				continue; //skip over NULL client sessions
		
			if (sClientSessionArray[y] != NULL && sClientSessionArray[y]->IsDone())
			{
				// If we are looping forever, when we run out of URLs, go back to the beginning of the list
				if ((runForever) && (theCurURLIndex == theNumURLs))
						theCurURLIndex = 0;
						
				if (theCurURLIndex < theNumURLs)
				{
					isStillActive = true;
					// If there is a new URL to fetch, kill this client and restart
					::RecordClientInfoBeforeDeath(sClientSessionArray[y]);
					sClientSessionArray[y]->Signal(Task::kKillEvent); // Tell it to destroy itself
					
					sClientSessionArray[y] = NEW ClientSession(	theIPAddrArray[theCurURLIndex],	// IP addr
                                                                                        thePort,			// IP port
                                                                                        theFileParser.GetValueAtIndex(theURLIndexArray[theCurURLIndex]),
                                                                                        theClientType,		// Client type
                                                                                        theMovieLength,		// Movie length
                                                                                        CalcStartTime(randomThumb, theMovieLength),		// Movie start time
                                                                                        5,					// RTCP interval
                                                                                        0,					// Options interval
                                                                                        theHTTPCookie++,	// HTTP cookie
                                                                                        appendJunk,
                                                                                        theReadInterval,	// Interval between data reads	
                                                                                        sockRcvBuf,			// socket recv buffer size	
                                                                                        lateTolerance,		// late tolerance
                                                                                        rtpMetaInfo,
                                                                                        speed,
                                                                                        verbose,
                                                                                        packetPlayHeader,
                                                                                        overbufferwindowInK);	

#if STREAMINGLOADTOOL_DEBUG
					printf("Creating a ClientSession for URL: %s\n", theFileParser.GetValueAtIndex(theURLIndexArray[theCurURLIndex]));
#endif
					theCurURLIndex++;
				}
			}
			else
					isStillActive = true;
		}
		
		if ((loopCount & 7) == 0) // Fancy way of mod'ing by 8, so the following will execute every 8 seconds
		{
			if ((loopCount & 127) == 0) // Fancy way of dividing by 127,
			{
				// Occassionally give the user lots of info
				printf("StreamingLoadTool test in progress.\n");
				printf("Config file: %s. Client type: %s. Num clients: %lu.\n",
								configFilePath, GetClientTypeDescription(theClientType), sNumClients);
				printf("Movie length: %lu. Run forever: %d. HTTP cookie: %lu. Port: %d\n",
								theMovieLength, runForever, theHTTPCookie, thePort);
				if (shouldLog)
					printf("Writing StreamingLoadTool log at: %s\n", logPath);
				printf("Active Playing Attempts Success Errors Failed\n");
			}
			
			printf("%5lu %6lu %8lu %6lu %6lu %6lu\n",
				ClientSession:: GetActiveConnections (),
				ClientSession:: GetPlayingConnections (),
				ClientSession:: GetConnectionAttempts (),
				sSuccessfulConnections,
				sConnectionsThatErrored,
				sFailedConnections);
		}
		loopCount++;
		
	} while (isStillActive == true);

	if (sGotSigInt)
	{
		//
		// If someone hits Ctrl-C, force all the clients to wrap it up
		printf("Sending TEARDOWNs.\n");
		
		//
		// Tell all the clients to wrap it up.
		for (UInt32 clientCount = 0; clientCount < sNumClients; clientCount++)
		{
			if (sClientSessionArray == NULL)
				continue; //skip over NULL client sessions
			
			if (sClientSessionArray[clientCount] != NULL)	
				sClientSessionArray[clientCount]->Signal(ClientSession::kTeardownEvent);
		}
		
		//
		// Wait for the clients to complete
		Bool16 isDone = false;
		while (!isDone && !sQuitNow)
		{
			OSThread::Sleep(1000);
			isDone = true;
			for (UInt32 cc2 = 0; cc2 < sNumClients; cc2++)
			{
				if (sClientSessionArray == NULL)
					continue; //skip over NULL client sessions
			
				if (sClientSessionArray[cc2] == NULL)	
					continue;

				if (!sClientSessionArray[cc2]->IsDone())
					isDone = false;
			}
		}	
	}
	
	//
	// We're done... now go through and delete the last sessions
	for (UInt32 z = 0; z < sNumClients; z++)
	{
		if (sClientSessionArray == NULL)
			continue; //skip over NULL client sessions
				
		if (sClientSessionArray[z] == NULL)	
			continue;
	
		::RecordClientInfoBeforeDeath(sClientSessionArray[z]);
	}
	
	if (sLog != NULL)
		::fclose(sLog);
		
	printf("StreamingLoadTool test complete. Total number of connections: %lu.\n", ClientSession:: GetConnectionAttempts ());
}

UInt32 CalcStartTime(Bool16 inRandomThumb, UInt32 inMovieLength)
{
	UInt32 theMovieLength = inMovieLength;
	if (theMovieLength > 1)
		theMovieLength--;
		
	if (inRandomThumb)
		return ::rand() % theMovieLength;
	else
		return 0;
}

void CheckForStreamingLoadToolPermission(UInt32* inIPAddrArray, UInt32 inNumURLs, UInt16 inPort)
{
	//Eventually check for the existance of a specially formatted sdp file (assuming the server blindly returns sdps)
}

void CheckForStreamingLoadToolDotMov(UInt32* ioIPAddrArray, UInt32 inNumURLs, UInt16 inPort)
{
	printf("Checking for 'streamingloadtool.mov' on the target servers\n");

	UInt32* uniqueIPAddrs = NEW UInt32[inNumURLs + 1];
	::memset(uniqueIPAddrs, 0, sizeof(UInt32) * (inNumURLs + 1));
	

	for (UInt32 count = 0; count < inNumURLs; count++)
	{
		UInt32 badAddr = 0;
		Bool16 dup = false;
		for (UInt32 x = 0; uniqueIPAddrs[x] != 0; x++)
		{
			if (uniqueIPAddrs[x] == ioIPAddrArray[count])
			{
				dup = true;
				break;
			}
		}
		
		if (dup)
			continue;
		
		//
		// For tracking dups.
		uniqueIPAddrs[count] = ioIPAddrArray[count];

		//
		// Make an RTSP client. We'll send a DESCRIBE to the server to check for this sucker
		
		TCPClientSocket* theSocket = NEW TCPClientSocket(0); // blocking socket.
		RTSPClient* theClient = NEW RTSPClient(theSocket, sVerbose);
 		//
		// Format the URL: rtsp://xx.xx.xx.xx/streamingloadtool.mov
		char theAddrBuf[20];
		StrPtrLen theAddrBufPtr(theAddrBuf, 20);
		struct in_addr theAddr;
		theAddr.s_addr = htonl(ioIPAddrArray[count]);
		
		SocketUtils::ConvertAddrToString(theAddr, &theAddrBufPtr);
		
		char theURLBuf[100];
		StringFormatter theFormatter(theURLBuf, 100);
		
		theFormatter.Put("rtsp://");
		theFormatter.Put(theAddrBufPtr);
		theFormatter.Put("/streamingloadtool.mov");
		theFormatter.PutTerminator();
		
		StrPtrLen theURL(theFormatter.GetBufPtr(), theFormatter.GetCurrentOffset());
		
		//
		// tell the client this is the URL to use
		theSocket->Set(ioIPAddrArray[count], inPort);
		theClient->Set(theURL);
		
		//
		// Send the DESCRIBE! Whoo hoo!
		OS_Error theErr = theClient->SendDescribe();
		
		if (theErr != OS_NoErr)
		{
			printf("##WARNING: Couldn't contact %s.\n\n", theAddrBufPtr.Ptr);
			badAddr = ioIPAddrArray[count];
			//::exit(-1);
		}
		else if (theClient->GetStatus() != 200)
		{
			printf("##WARNING: %s doesn't have %s.\n\n",theAddrBufPtr.Ptr, theURL.Ptr);
			badAddr = ioIPAddrArray[count];
			//::exit(-1);
		}
		
		//if the address was unreachable or did not give permission to be spammed, then clear it from our IPAddress list.
		if (badAddr != 0)
		{
			for (UInt32 x = 0; x < inNumURLs; x++)
			{
				if (badAddr == ioIPAddrArray[count])
					ioIPAddrArray[count] = 0;
			}
		}
		
		theClient->SendTeardown();
		delete theClient;
		delete theSocket;
	}

	int addrCount = 0;
	for (UInt32 x = 0; x < inNumURLs; x++)
	{
		if ( 0 != ioIPAddrArray[x])
			addrCount++ ;
	}
	if (addrCount == 0)
	{	printf("No valid destinations\n");
		exit (-1);
	}
	delete [] uniqueIPAddrs;
	printf("Done checking for 'streamingloadtool.mov' on all servers\n");	
}


void DoDNSLookup(FilePrefsSource* inFileParser, UInt32* inURLIndexes, UInt32* ioIPAddrs, UInt32 inNumURLs)
{
	char theDNSName[128];
	
	for (UInt32 x = 0; x < inNumURLs; x++)
	{
		// First extract the DNS name from this URL as a c-string
		StrPtrLen theURL(inFileParser->GetValueAtIndex(inURLIndexes[x]));
		StringParser theURLParser(&theURL);
		StrPtrLen theDNSNamePtr;
		
		theURLParser.ConsumeLength(NULL, 7); // skip over rtsp://
		theURLParser.ConsumeUntil(&theDNSNamePtr, '/'); // grab the DNS name
		StringParser theDNSParser(&theDNSNamePtr);
		theDNSParser.ConsumeUntil(&theDNSNamePtr, ':'); // strip off the port number if any
		
		theDNSName[0] = 0;
		::memcpy(theDNSName, theDNSNamePtr.Ptr, theDNSNamePtr.Len);
		theDNSName[theDNSNamePtr.Len] = 0;
		
		Assert(theDNSNamePtr.Len < 128);
		
		ioIPAddrs[x] = 0;
		
		// Now pass that DNS name into gethostbyname.
		struct hostent* theHostent = ::gethostbyname(theDNSName);
		
		if (theHostent != NULL)
			ioIPAddrs[x] = ntohl(*(UInt32*)(theHostent->h_addr_list[0]));
		else
			ioIPAddrs[x] = SocketUtils::ConvertStringToAddr(theDNSName);
		
		if (ioIPAddrs[x] == 0)
		{
			printf("Couldn't look up host name: %s.\n", theDNSName);
			//exit(-1);
		}
	}
}

char* 	GetClientTypeDescription(ClientSession::ClientType inClientType)
{
	static char* kUDPString = "RTSP/UDP client";
	static char* kTCPString = "RTSP/TCP client";
	static char* kHTTPString = "RTSP/HTTP client";
	static char* kHTTPDropPostString = "RTSP/HTTP drop post client";
	static char* kReliableUDPString = "RTSP/ReliableUDP client";
	
	switch (inClientType)
	{
		case ClientSession::kRTSPUDPClientType:
			return kUDPString;
		case ClientSession::kRTSPTCPClientType:
			return kTCPString;
		case ClientSession::kRTSPHTTPClientType:
			return kHTTPString;
		case ClientSession::kRTSPHTTPDropPostClientType:
			return kHTTPDropPostString;
		case ClientSession::kRTSPReliableUDPClientType:
			return kReliableUDPString;
	}
	Assert(0);
	return NULL;
}

char*	GetDeathReasonDescription(UInt32 inDeathReason)
{
	static char* kDiedNormallyString = "Completed normally";
	static char* kTeardownFailedString = "Couldn't complete TEARDOWN";
	static char* kRequestFailedString = "Failed RTSP request";
	static char* kBadSDPString = "misformatted SDP";
	static char* kSessionTimedoutString = "Couldn't connect to server";
	static char* kConnectionFailedString = "Server refused connection";
	static char* kDiedWhilePlayingString = "Disconnected while playing";

	switch (inDeathReason)
	{
		case ClientSession::kDiedNormally:
			return kDiedNormallyString;
		case ClientSession::kTeardownFailed:
			return kTeardownFailedString;
		case ClientSession::kRequestFailed:
			return kRequestFailedString;
		case ClientSession::kBadSDP:
			return kBadSDPString;
		case ClientSession::kSessionTimedout:
			return kSessionTimedoutString;
		case ClientSession::kConnectionFailed:
			return kConnectionFailedString;
		case ClientSession::kDiedWhilePlaying:
			return kDiedWhilePlayingString;
	}
	Assert(0);
	return NULL;
}

char*	GetPayloadDescription(QTSS_RTPPayloadType inPayload)
{
	static char*	kSound = "Sound";
	static char*	kVideo = "Video";
	static char*	kUnknown = "Unknown";

	switch (inPayload)
	{
		case qtssVideoPayloadType:
			return kVideo;
		case qtssAudioPayloadType:
			return kSound;
		default:
			return kUnknown;
	}
	return NULL;
}

void RecordClientInfoBeforeDeath(ClientSession* inSession)
{
	if (inSession->GetReasonForDying() == ClientSession::kRequestFailed)
		sConnectionsThatErrored++;
	else if (inSession->GetReasonForDying() != ClientSession::kDiedNormally)
		sFailedConnections++;
	else
		sSuccessfulConnections++;
				
	if (sLog != NULL)
	{
		UInt32 theReason = inSession->GetReasonForDying();
		in_addr theAddr;
		theAddr.s_addr = htonl(inSession->GetSocket()->GetHostAddr());
		char* theAddrStr = ::inet_ntoa(theAddr);
		
		//
		// Write a log entry for this client
		::fprintf(sLog, "Client complete. IP addr = %s, URL = %s. Connection status: %s. ",
						theAddrStr,
						inSession->GetClient()->GetURL()->Ptr,
						::GetDeathReasonDescription(theReason));
						
		if (theReason == ClientSession::kRequestFailed)
			::fprintf(sLog, "Failed request status: %lu", inSession->GetRequestStatus());
		
		::fprintf(sLog, "\n");
		
		//
		// If this was a successful connection, log statistics for this connection
		if ((theReason == ClientSession::kDiedNormally) || (theReason == ClientSession::kTeardownFailed))
		{
			UInt32 bytesReceived = 0;
			for (UInt32 trackCount = 0; trackCount < inSession->GetSDPInfo()->GetNumStreams(); trackCount++)
			{
				::fprintf(sLog, "Track type: %s. Total packets received: %lu. Total packets lost: %lu. Total out of order packets: %lu. Total duplicate packets: %lu. Total ACKs sent: %lu.\n",
					::GetPayloadDescription(inSession->GetTrackType(trackCount)), 
					inSession->GetNumPacketsReceived(trackCount),
					inSession->GetNumPacketsLost(trackCount),
					inSession->GetNumPacketsOutOfOrder(trackCount),
					inSession->GetNumDuplicates(trackCount),
					inSession->GetNumAcks(trackCount));
					
				bytesReceived += inSession->GetNumBytesReceived(trackCount);
			}
			UInt32 duration = (UInt32)(inSession->GetTotalPlayTimeInMsec() / 1000);
			Float32 bitRate = (((Float32)bytesReceived) / ((Float32)duration) * 8) / 1024;
			::fprintf(sLog, "Play duration in sec: %lu. Total stream bit rate in Kbits / sec: %f.\n", duration, bitRate);
		}
		
		::fprintf(sLog, "\n");		
	}
}

