//-------------------------------------------------------------------------------------------------
// <copyright file="ServerExec.cpp" company="LiquidRobotics">
//  Copyright (c) Liquid Robotics Corporation, confidential and proprietary.  All rights reserved. 
// </copyright>
//
// <summary>
// Implementation for the program class for the ServerExec module
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------------------------

#include "ServerExec.h"
#include <cstdio>
#include <fstream>
#include <iostream>
#include <string>
#include "LqrSMACIdl.h"
#include "omniORB4/CORBA.h"
#include "LqrSMACObjectBroker.h"
#include "LqrSMACIObjectBroker-inline.h"
#include "LqrSMACObjectBroker-inline.h"
#include "LqrSMACByteArray.h"
#include "LqrSMACUptime.h"
#include "LqrSMACStdOutLogger.h"
#include "LqrSMACFindFile.h"
#include "LqrSMACPayloadDescription.h"
#include "LqrSMACPayloadDescriptionList.h"
#include "EventSettings.h"
#include "EventTransceiver.h"
#include "FCCMessage.h"
#include "FCCMessage_CommHeaderMessage.h"
#include "PayloadAddressableMessageService.h"
#include "SendCommandCompleteEvent.h"
#include <stdlib.h>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
#include "LqrSMACGetErrorDescription.h"
#include "LqrSMACSemaphore.h"
#include <errno.h>
#include <time.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

using namespace std;
using namespace boost;
using namespace boost::archive;
using namespace boost::serialization;
using namespace LiquidRobotics::EventLib;
using namespace LiquidRobotics::PayloadInterface;
using namespace LiquidRobotics::SMAC;
using namespace LiquidRobotics::SMACFramework;
using namespace CORBA;

ServerExecProgram::ServerExecProgram(int argc, char* argv[])
: progName(argv[0])
, orbInitialized(false)
, maxCommandSize(64*1024*1024L)
, commandCompleteSem(0)
{
	SetArgs(argc, argv);
}

int ServerExecProgram::Run()
{
	int retVal;

	try
	{
		// load settings from XML file
		LoadSettings();

		// compute the server name based on pid and up time
		{
			stringstream name;
			name << settings.GetServerBaseName() << "-" << getpid() << "-" << GetSystemUpTimeSeconds();

			serverName = name.str();
		}

		// initialize the CORBA ORB
		InitObjectBroker();

		// decide the logging level
		action = "parsing log type";
		Logger::LogType logType = Logger::LT_NONE;
		StringArray::iterator it = args.begin();
		bool atEnd = (it == args.end());

		while(!atEnd)
		{
			StringArray::iterator thisItem = it++;
			atEnd = (it == args.end());

			if (boost::istarts_with(*thisItem, "LogType="))
			{
				Logger::FromString(thisItem->substr(8), logType);
				args.erase(thisItem);
			}
		}

		// create the event transceiver
		action = "initializing";
		StdOutLogger dummyLogger(settings.GetServerBaseName().c_str(), 1, logType);
		eventTransPtr.reset( new EventTransceiver(settings.GetEventTransceiverSettings(), *brokerPtr, dummyLogger) );

		// create a tie class that will forward CORBA call to this instance
		PortableServer::ServantBase_var theServer = 0;
		theServer = new ::POA_LiquidRobotics::SMAC::IEventReceiver_tie<EventTransceiver>(*eventTransPtr);

		// activate the instance (not providing name since there's no need to call back with name)
		Object_var serverRef = brokerPtr->ActivateInstance(theServer.in());

		// configure EventTransceiver to create the appropriate types based on incoming events
		const EventSettings& cmdCmplt = settings.GetCommandCompleteEventSettings();
		eventTransPtr->RegisterEventClassFactory(cmdCmplt.GetType(),
							new EventTransceiver::TEventClassFactory<SendCommandCompleteEvent>());

		// connect a handler to the command complete event
		EventTransceiver::InformSignalType& sig0 = eventTransPtr->SignalForEventName(cmdCmplt.GetName());

		connectionToCommandCompleteEvent.reset(
				new ScopedConnection(
						sig0.connect(boost::bind(&ServerExecProgram::CommandCompleteHandler, this, _1, _2)
						)));

		// complete initialization of EventTransceiver
		serverPtr = serverRef.in();
		eventTransPtr->Initialize(serverRef.in());

		// begin initialization of payload manager client if there is enough information
		if (!settings.GetTransmitRemoteCommandSettings().Empty() && settings.GetSemaphoreFileName().length())
		{
			try
			{
				pmClient.reset(new PayloadManagerClient(settings.GetPMSettings(), *brokerPtr, *eventTransPtr));
			}
			catch(...)
			{
			}
		}

		// start another thread to complete the work
		boost::thread thr(boost::bind(&ServerExecProgram::Run2, this));

		// listen here for requests
		brokerPtr->ListenForRequests();

		// this exit code is from the background routine
		retVal = queuedExitCode;
	}
	catch(CORBA::Exception& ex)
	{
		cerr << "Caught CORBA exception " << DecodeCORBAException(ex) << " while " << action << endl;

		if (!orbInitialized)
		{
			cerr << endl;
			ShowUsage(cerr);
		}

		retVal = 4;
	}
	catch(std::exception& ex)
	{
		cerr << "Caught exception " << typeid(ex).name() << " '" << ex.what() << "' while " << action << endl;

		if (!orbInitialized)
		{
			cerr << endl;
			ShowUsage(cerr);
		}

		retVal = 5;
	}

	return retVal;
}

string ServerExecProgram::Collect(FILE* fp)
{
	stringstream stm;
	char c;
	size_t counter = 0;

	while(c = fgetc(fp), !feof(fp) && !ferror(fp))
	{
		if (++counter > maxCommandSize)
			throw runtime_error("Input data stream exceeds maximum command content size");

		stm << c;
	}

	return stm.str();
}

void ServerExecProgram::CommandCompleteHandler(EventTransceiver& sender, const IEventBase& receivedEvent)
{
	const EventSettings& cmdCmplt = settings.GetCommandCompleteEventSettings();
	const SendCommandCompleteEvent& evt =
		EventTransceiver::UpcastEventRef<SendCommandCompleteEvent>(receivedEvent, cmdCmplt,
				"ServerExecProgram::CommandCompleteHandler");

	if (eventTag.length() && !eventTag.compare(evt.TransactionID()))
	{
		if (evt.HasResultFile())
		{
			string fileName = evt.ResultFile();

			CatFile(fileName, cout);

			if (evt.ResultFileOwnedByReceiver())
				unlink(fileName.c_str());
		}
		else
			DecodeResult(evt.Result().in(), cout);

		if (evt.Complete())
			commandCompleteSem.Post();
	}
}

void ServerExecProgram::CatFile(const string& fileName, ostream& stm)
{
	try
	{
		fstream fs(fileName.c_str(), ios_base::in | ios_base::binary);
		ByteArray buffer;

		buffer.resize(2048);

		bool reading = true;

		while(reading)
		{
			fs.read((char*) &buffer.front(), buffer.size());
			reading = fs.gcount() && fs.good();

			if (fs.gcount())
				stm.write((const char*) &buffer.front(), fs.gcount());
		}
	}
	catch(std::exception& ex)
	{
		stm << "Caught " << typeid(ex).name() << " '" << ex.what() << "' while reading the content file " << fileName << endl;
	}
}

void ServerExecProgram::DecodeResult(const Any& result, ostream& stm)
{
	EventPropertyValue epv(result, *brokerPtr);
	ByteArray data;

	epv.ToBinary(data);
	cout.write((const char*) &data.front(), data.size());
}

int ServerExecProgram::ExecuteCommand(const string& serverName, const string& commandContent, double timeoutVal, bool timeoutSpec, bool enableAsynchronousResponse)
{
	action = "validating/translating server name";

	unsigned remoteAddr;
	bool isRemote = ValidateRemote(serverName, remoteAddr);
	int result;

	action.clear();

	if (isRemote)
		result = ExecuteRemoteCommand(remoteAddr, commandContent, timeoutVal);
	else if (timeoutSpec)
		throw invalid_argument("Local command executions cannot specify timeout");
	else
		result = ExecuteLocalCommand(serverName, commandContent, enableAsynchronousResponse);

	return result;
}

int ServerExecProgram::ExecuteLocalCommand(const string& serverName, const string& commandContent, bool enableAsynchronousResponse)
{
	action = "validating/translating server name";
	string SN2 = ValidateTranslateServerName(serverName);

	action = "inserting command content into CORBA variable";

	EventPropertyValue epv(*brokerPtr);
	ByteArray content2;
	const ByteArray::byte* pContent = (const ByteArray::byte*) commandContent.data();

	content2.assign(pContent, pContent+commandContent.length());

	if (CanPassAsString(content2))
		epv.SetString(commandContent);
	else
		epv.SetByteArray(content2);

	CORBA::Any cmd;
	epv.CopyOut(cmd);

	{
		stringstream actionMsg;
		actionMsg << "getting commandable interface for " << SN2;
		action = actionMsg.str();
	}

	ICommandable_var commandableV;
	::CORBA::Any_var result;
	commandableV = brokerPtr->GetNamedInstance<ICommandable_var,ICommandable>(SN2.c_str());

	{
		stringstream actionMsg;
		actionMsg << "executing command on " << SN2;
		action = actionMsg.str();
	}

	bool asynchCompletion = false;

	if (enableAsynchronousResponse)
	{
		const string& me = this->serverName;
		eventTag = me;
		eventTag += "-1";

		try
		{
			ICommandableInvoker<ICommandable_var> invoker(commandableV);
			invoker(cmd, me.c_str(), eventTag.c_str(), result);
		}
		catch(WillCompleteAsynchronously& exc)
		{
			asynchCompletion = true;
			commandCompleteSem.Wait();
		}
	}
	else
	{
		ICommandableInvoker<ICommandable_var> invoker(commandableV);
		invoker(cmd, SN2.c_str(), "", result);
	}

	if (!asynchCompletion)
		try
		{
			DecodeResult(result.in(), cout);
		}
		catch(...)
		{
			return 6;
		}

	return 0;
}

int ServerExecProgram::ExecuteRemoteCommand(int remoteAddr, const string& commandContent, double timeoutVal)
{
	using namespace LiquidRobotics::PayloadInterface::FCCMessage;
	typedef LiquidRobotics::PayloadInterface::FCCMessage::ByteArray ByteArray;

	if (!pmClient)
		throw runtime_error("PayloadManager client could not be instantiated");

	int fd = -1;
	bool registered = false;

	try
	{
		string fileName = settings.GetSemaphoreFileName();
		fd = open(fileName.c_str(), O_RDWR);

		if (fd == -1)
			fd = open(fileName.c_str(), O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);

		if (fd == -1)
		{
			string errMsg = GetErrorDescription(errno);
			stringstream msg;
			msg << "Failed to open semaphore file '" << settings.GetSemaphoreFileName() << "': " << errMsg;
			string m = msg.str();
			throw runtime_error(m);
		}

		int flockStatus = flock(fd, LOCK_EX);

		if (flockStatus == -1)
		{
			string errMsg = GetErrorDescription(errno);
			stringstream msg;
			msg << "Exclusive lock failed for semaphore file '" << settings.GetSemaphoreFileName() << "': " << errMsg;
			string m = msg.str();
			throw runtime_error(m);
		}

		pmClient->Register(serverPtr);
		registered = true;

		const LiquidRobotics::SMACFramework::PayloadDescription& pyldDesc = settings.GetPMSettings().GetPayloadDescription();
		PayloadAddressableMessageService msgService(pyldDesc, settings.GetAddressableMessageServiceSettings(), *brokerPtr, *eventTransPtr);
		ByteArray messageData((const ByteArray::byte*) commandContent.data(), commandContent.length());

		CommHeaderMessage< CmdLayout<CMD_FMT_1E>,false> l2msg(messageData.size());
		l2msg.SetType(RCV_CMD);
		l2msg.SetPacketID(100);
		l2msg->cmdFormat.fmtID = FMT_RCV_CONSOLE_INPUT;
		l2msg->cmdFormat.command = CMD_WRITE_CNSL_CMD_BINARY;
		l2msg->params.dest = remoteAddr;
		l2msg->params.function = 0;
		l2msg->params.dataLength = messageData.size();
		memcpy(l2msg->params.consoleData, &messageData.at(0), messageData.size());

		ByteArray::Ptr content;
		content.reset(new ByteArray);
		l2msg.ToArray(*content, true);

		LRI_COMMAND_REQUEST_ACK_WRAPPER commandReq;
		commandReq.commandFormat = FLOAT_MESSAGE_FORMAT_ICD_1;
		content->insert(content->begin(), (const ByteArray::byte*) &commandReq, sizeof(commandReq));

		msgService.Transmit(FLOAT_TARGET_COMMAND_INTERFACE, content, true);
		sleep(30);
	}
	catch(...)
	{
		if (registered)
			try			{ pmClient->Unregister(serverPtr); }
			catch(...)	{ }

		if (fd != -1)
			close(fd);

		throw;
	}


	if (registered)
		try			{ pmClient->Unregister(serverPtr); }
		catch(...)	{ }

	if (fd != -1)
		close(fd);

	return 0;
}

double ServerExecProgram::GetCurrentTime()
{
	double result;
	timeval tv;
	int status = gettimeofday(&tv, NULL);
	int errSave = errno;

	if (status)
	{
		stringstream msg;
		msg << "gettimeofday returned error #" << errSave << " '" << GetErrorDescription(errSave) << "'";
		throw runtime_error(msg.str());
	}

	result = tv.tv_usec;
	result /= 1000000;
	result += tv.tv_sec;
	return result;
}

void ServerExecProgram::InitObjectBroker()
{
	action = "initializing object broker";
	brokerPtr.reset(new ObjectBroker(true));

	StringArray temp;
	temp.assign(args.begin(), args.end());

	vector<char*> tempRaw;
	tempRaw.reserve(temp.size()+1);

	BOOST_FOREACH(const string& item, temp)
		tempRaw.push_back(const_cast<char*>(item.c_str()));

	tempRaw.push_back(NULL);

	int argc = tempRaw.size()-1;
	char** argv = &tempRaw.at(0);

	brokerPtr->Initialize(argc, argv);
	orbInitialized = true;

	SetArgs(argc, argv);
	action.clear();
}

void ServerExecProgram::LoadSettings()
{
	const int argCount = args.size();
	string xmlFile = "ServerExec-config.xml";
	const string configFileOption = "--config-file";

	for (int idx = 1; idx < argCount; ++idx)
		if (boost::iequals(args[idx], configFileOption))
		{
			if (++idx >= argCount)
			{
				stringstream msg;
				msg << "No filename appeared after " << configFileOption;
				throw runtime_error(msg.str());
			}
			else
			{
				xmlFile = args[idx];

   				StringArray::iterator it = args.begin()+idx-1;
				args.erase(it, it+2);
				break;
			}
		}

	string xmlFile2 = FindFileWithExecutable(xmlFile, progName);
	settings.Load(xmlFile2);
}

int ServerExecProgram::Ping(const string& serverName)
{
	action = "validating/translating server name";
	string SN2 = ValidateTranslateServerName(serverName);

	{
		stringstream actionMsg;
		actionMsg << "getting pingable instance for " << SN2;
		action = actionMsg.str();
	}

	IPingable_var pingV;
	pingV = brokerPtr->GetNamedInstance<IPingable_var,IPingable>(SN2.c_str());

	{
		stringstream actionMsg;
		actionMsg << "executing ping on " << SN2;
		action = actionMsg.str();
	}

	const double startTime = GetCurrentTime();
	::CORBA::Double timeAtObject = pingV->Ping();
	const double endTime = GetCurrentTime();

	cout.precision(15);
	cout << timeAtObject << " (time delta=";
	cout.precision(15);

	if (timeAtObject)
	{
		cout << (timeAtObject-startTime);
		cout << "+";
		cout.precision(15);
		cout << (endTime-timeAtObject);
		cout << "=";
		cout.precision(15);
	}

	cout << (endTime-startTime) << ")" << endl;
	action.clear();
	return 0;
}

int ServerExecProgram::PingWait(const string& serverName, double timeoutVal)
{
	string SN2 = ValidateTranslateServerName(serverName);

	const double startTime = GetCurrentTime();
	double elapsedTime;
	bool success = false;
	bool timeout;

	do
	{
		try
		{
			IPingable_var pingV;
			pingV = brokerPtr->GetNamedInstance<IPingable_var,IPingable>(SN2.c_str());

			pingV->Ping();
			success = true;
		}
		catch(...)
		{
		}

		elapsedTime = GetCurrentTime()-startTime;
		timeout = (elapsedTime >= timeoutVal);

		// wait for 200ms between attempts so that the CPU is not monopolized
		if (!success && !timeout)
			usleep(200000);

	} while(!success && !timeout);

	if (success)
	{
		cout << SN2;
		if (SN2.compare(serverName))
			cout << " (" << serverName << ")";

		cout << " is up" << endl;
	}
	else
		cout << "Timeout on ping request to " << SN2 << endl;

	return success ? 0 : 1;
}

void ServerExecProgram::QueueShutdown(int exitCode)
{
	if (orbInitialized && !brokerPtr->IsShutDown() && !brokerPtr->IsShutdownQueued())
	{
		queuedExitCode = exitCode;
		brokerPtr->QueueShutdown();
	}
}

void ServerExecProgram::Run2()
{
	int retVal = 0;

	try
	{
		// handle list invocation
		if ((args.size() == 2) && boost::iequals(args[1], "--list"))
			retVal = ListServers();

		// handle ping invocation
		else if ((args.size() == 3) && boost::iequals(args[1], "--ping"))
			retVal = Ping(args[2]);

		// handle ping/wait invocation
		else if ((args.size() == 4) && boost::iequals(args[1], "--pingwait"))
		{
			action = "converting timeout parameter";
			double timeoutVal = lexical_cast<double>(args[3]);
			action.clear();
			retVal = PingWait(args[2], timeoutVal);
		}

		// handle command invocation
		else
		{
			// check for timeout setting
			//StringArray::iterator it = args.begin();
			const string timeoutEq = "--timeout=";
			double timeoutVal = 60;
			bool timeoutSpec = false;

			if ((args.size() >= 2) && boost::istarts_with(args[1], timeoutEq))
			{
				timeoutSpec = true;
				timeoutVal = lexical_cast<double>(args[1].substr(timeoutEq.length()));
				args.erase(args.begin()+1);
			}

			// too few arguments
			if (args.size() < 2)
			{
				cerr << "No server name was specified" << endl << endl;
				ShowUsage(cerr);
				retVal = 1;
			}

			// only server name passed in -- collect command from stdin
			else if (args.size() == 2)
				retVal = ExecuteCommand(args[1], Collect(stdin), timeoutVal, timeoutSpec);

			// server name and command passed in
			else if (args.size() == 3)
				retVal = ExecuteCommand(args[1], args[2], timeoutVal, timeoutSpec);

			// too many args
			else
			{
				cerr << "Too many options were specified" << endl << endl;
				ShowUsage(cerr);
				retVal = 2;
			}
		}
	}
	catch(CORBA::Exception& ex)
	{
		cerr << "Caught CORBA exception " << DecodeCORBAException(ex) << " while " << action << endl;

		if (!orbInitialized)
		{
			cerr << endl;
			ShowUsage(cerr);
		}

		retVal = 4;
	}
	catch(std::exception& ex)
	{
		cerr << "Caught exception " << typeid(ex).name() << " '" << ex.what() << "' while " << action << endl;

		if (!orbInitialized)
		{
			cerr << endl;
			ShowUsage(cerr);
		}

		retVal = 5;
	}

	QueueShutdown(retVal);
}

void ServerExecProgram::SetArgs(int argc, char* argv[])
{
	args.resize(0);
	args.reserve(argc);

	for (int idx = 0; idx < argc; ++idx)
		args.push_back(argv[idx]);
}

void ServerExecProgram::ShowUsage(ostream& stm)
{
	string orbparamlist = "...ORB-parameter-list...";

	stm << "Usage: (1) " << progName << " " << orbparamlist << " --address=nnn|[--timeout=ttt] --remote=nnn|server-name [input-command]" << endl
		<< "       (2) " << progName << " " << orbparamlist << " --list" << endl
		<< "       (3) " << progName << " " << orbparamlist << " --ping server-name  or  --ping --address=nnn" << endl
		<< "       (4) " << progName << " " << orbparamlist << " --pingwait server-name max-time  or  --pingwait --address=nnn max-time" << endl
		<< endl
		<< "The typical ORB parameter list is:  -ORBInitRef NameService=corbaname::localhost" << endl;
}

int ServerExecProgram::ListServers()
{
	IObjectBroker::StringPairVectorSet servers;
	brokerPtr->GetServers(servers);

	bool first = true;

	for (IObjectBroker::StringPairVectorSet::const_iterator ci = servers.begin(); ci != servers.end(); ++ci)
	{
		if (first)
			first = false;
		else
			cout << endl;

		bool first2 = true;

		for (IObjectBroker::StringPairVector::const_iterator ci2 = ci->begin(); ci2 != ci->end(); ++ci2)
		{
			if (first2)
				first2 = false;
			else
				cout << " ";

			cout << ci2->first << "/" << ci2->second;
		}
	}

	return 0;
}

bool ServerExecProgram::ValidateRemote(const string& serverName, unsigned& remoteAddr)
{
	const string remoteEq = "--remote=";
	const bool isRemote = boost::istarts_with(serverName, remoteEq);

	if (isRemote)
	{
		string tail = serverName.substr(remoteEq.length());
		trim(tail);
		remoteAddr = ConvertAddress(tail);
	}

	return isRemote;
}

string ServerExecProgram::ValidateTranslateServerName(const string& serverName)
{
	typedef LiquidRobotics::SMACFramework::PayloadDescriptionList PyldDescList;

	string result;

	IObjectBroker::StringPairVectorSet servers;
	brokerPtr->GetServers(servers);

	IObjectBroker::StringPairVector srch(1);
	srch[0].first = serverName;

	if (servers.find(srch) == servers.end())
	{
		const string addressEq = "--address=";
		const string hexPrefix = "0x";
		const string payloadManager = "PayloadManager";

		if (boost::istarts_with(serverName, addressEq))
		{
			string tail = serverName.substr(addressEq.length());
			boost::trim(tail);

			unsigned n = ConvertAddress(tail);

			srch[0].first = payloadManager;

			if (servers.find(srch) == servers.end())
			{
				stringstream msg;
				msg << "Payload manager service (" << payloadManager << ") is not advertised, so address lookups cannot be performed";
				string s = msg.str();
				throw runtime_error(s);
			}

			IPayloadManager_var pm;
			try
			{
				brokerPtr->GetNamedInstance<IPayloadManager_var, IPayloadManager>(payloadManager.c_str());
			}
			catch(...)
			{
				stringstream msg;
				msg << "Payload manager service (" << payloadManager << ") could not be accessed, so address lookups cannot be performed";
				string s = msg.str();
				throw runtime_error(s);
			}

			PayloadDescriptionList_var corbaList = pm->payloadDescriptions();
			PyldDescList::Ptr pyldDescList;
			pyldDescList.reset(new PyldDescList(corbaList.in()));

			bool found = false;

			for (PyldDescList::const_iterator ci = pyldDescList->begin(); !found && (ci != pyldDescList->end()); ++ci)
			{
				unsigned candidateAddress = (*ci)->GetBoardID();

				candidateAddress <<= 8;
				candidateAddress += (*ci)->GetTaskID();

				if (candidateAddress == n)
				{
					result = (*ci)->GetReadableName();
					found = true;
				}
			}

			if (!found)
			{
				stringstream msg;
				msg << "No match for " << serverName << " (" << n << ") in services registered with payload manager";
				string s = msg.str();
				throw invalid_argument(s);
			}

			srch[0].first = result;

			if (servers.find(srch) == servers.end())
			{
				stringstream msg;
				msg << serverName << " equates to " << result << ", but no such service is advertised";
				string s = msg.str();
				throw invalid_argument(s);
			}
		}
		else
		{
			stringstream msg;
			msg << serverName << " is not an advertised service name";
			string s = msg.str();
			throw invalid_argument(s);
		}
	}
	else
		result = serverName;

	return result;
}

unsigned ServerExecProgram::ConvertAddress(const string& inputStr)
{
	const string hexPrefix = "0x";
	int radix = 10;
	int offset = 0;

	if (boost::istarts_with(inputStr, hexPrefix))
	{
		radix = 16;
		offset = hexPrefix.length();
	}

	char* p = NULL;
	unsigned n = strtoul(inputStr.c_str()+offset, &p, radix);

	if (p && *p)
	{
		stringstream msg;
		msg << "Invalid address conversion for " << inputStr;
		string s = msg.str();
		throw invalid_argument(s);
	}

	return n;
}

bool ServerExecProgram::CanPassAsString(const ByteArray& data) const
{
	bool canPassAsString = true;

	for (ByteArray::const_iterator ci = data.begin(); canPassAsString && (ci != data.end()); ++ci)
		canPassAsString = !!*ci;

	return canPassAsString;
}
