/*
 * libretroshare/src/zeroconf: p3zeroconf.cc
 *
 * ZeroConf interface for RetroShare.
 *
 * Copyright 2011-2011 by Robert Fernie.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License Version 2 as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 *
 * Please report all bugs and problems to "retroshare@lunamutt.com".
 *
 */


#include "zeroconf/p3zeroconf.h"
#include <openssl/sha.h>
#include <iostream>

#include "pqi/authgpg.h"

/* TODO
 *
 * - Get Local Port when it changes.... (need second interface?)
 * - Get GpgId, SslId for all peers.
 * - Able to install new sslIds for friends.
 * - Hold data for all peers.
 * - parse TxtRecord.
 *
 */

//#define DEBUG_ZEROCONF	1

#define ZC_MAX_QUERY_TIME	30
#define ZC_MAX_RESOLVE_TIME	30

p3ZeroConf::p3ZeroConf(std::string gpgid, std::string sslid, pqiConnectCb *cb, p3NetMgr *nm, p3PeerMgr *pm)
	:pqiNetAssistConnect(sslid, cb), mNetMgr(nm), mPeerMgr(pm), mZcMtx("p3ZeroConf")
{
	mRegistered = false;
	mTextOkay = false;
	mPortOkay = false;

	mOwnGpgId = gpgid;
	mOwnSslId = sslid;

#ifdef DEBUG_ZEROCONF
	std::cerr << "p3ZeroConf::p3ZeroConf()" << std::endl;
	std::cerr << "Using Id: " << sslid;
	std::cerr << std::endl;
#endif

	mRegisterStatus = ZC_SERVICE_STOPPED;
	mRegisterStatusTS = time(NULL);
	mBrowseStatus = ZC_SERVICE_STOPPED;
	mBrowseStatusTS = time(NULL);
	mResolveStatus = ZC_SERVICE_STOPPED;
	mResolveStatusTS = time(NULL);
	mQueryStatus = ZC_SERVICE_STOPPED;
	mQueryStatusTS = time(NULL);

	createTxtRecord();
}

p3ZeroConf::~p3ZeroConf()
{

}


void    p3ZeroConf::start()
{
#ifdef DEBUG_ZEROCONF
	std::cerr << "p3ZeroConf::start()";
	std::cerr << std::endl;
#endif

}

	/* pqiNetAssist - external interface functions */
void    p3ZeroConf::enable(bool on)
{
#ifdef DEBUG_ZEROCONF
	std::cerr << "p3ZeroConf::enable(" << on << ")";
	std::cerr << std::endl;
#endif

}
	
void    p3ZeroConf::shutdown() /* blocking call */
{
}


void	p3ZeroConf::restart()
{
}

bool    p3ZeroConf::getEnabled()
{
	return true;
}

bool    p3ZeroConf::getActive()
{
	return true;
}

bool    p3ZeroConf::getNetworkStats(uint32_t &netsize, uint32_t &localnetsize)
{
	return false;  // Cannot provide Network Stats.
}

void 	p3ZeroConf::createTxtRecord()
{
	RsStackMutex stack(mZcMtx); /****** STACK LOCK MUTEX *******/

	std::string gpgid = "gpgid=" + mOwnGpgId;
	std::string sslid = "sslid=" + mOwnSslId;

	mTextRecord += (char) gpgid.length();
	mTextRecord += gpgid;
	mTextRecord += (char) sslid.length();
	mTextRecord += sslid;

	std::cerr << "p3ZeroConf::createTxtRecord() Record: " << std::endl;
	std::cerr << "------------------------------------" << std::endl;
	std::cerr << mTextRecord;
	std::cerr << std::endl;
	std::cerr << "------------------------------------" << std::endl;

	mTextOkay = true;
}

int procPeerTxtRecord(int txtLen, const unsigned char *txtRecord, std::string &peerGpgId, std::string &peerSslId)
{
	int txtRemaining = txtLen;
	int idx = 0;
	bool setGpg = false;
	bool setSsl = false;

	std::cerr << "procPeerTxtRecord() processing";
	std::cerr << std::endl;

	while(txtRemaining > 0)
	{
		uint8_t len = txtRecord[idx];
		idx++;
		txtRemaining -= 1;

		std::string record((const char *) txtRecord, idx, len);

		std::cerr << "procPeerTxtRecord() record: " << record;
		std::cerr << std::endl;

		if (0 == strncmp(record.c_str(), "gpgid=", 6))
		{
			peerGpgId = record.substr(6, -1);
			setGpg = true;

			std::cerr << "procPeerTxtRecord() found peerGpgId: " << peerGpgId;
			std::cerr << std::endl;
		}
		else if (0 == strncmp(record.c_str(), "sslid=", 6))
		{
			peerSslId = record.substr(6, -1);
			setSsl = true;

			std::cerr << "procPeerTxtRecord() found peerSslId: " << peerSslId;
			std::cerr << std::endl;
		}
		else
		{
			std::cerr << "Unknown Record";
		}
		idx += len;
		txtRemaining -= len;
	}
	return (setGpg && setSsl);
}


        /*** OVERLOADED from pqiNetListener ***/
bool 	p3ZeroConf::resetListener(struct sockaddr_in &local)
{
	RsStackMutex stack(mZcMtx); /****** STACK LOCK MUTEX *******/

	mLocalPort = ntohs(local.sin_port);
	mPortOkay  = true;

	if (mRegisterStatus == ZC_SERVICE_ACTIVE)
	{
		locked_stopRegister();
	}
	return true;
}


/*****************************************************************************
 *********  pqiNetAssistConnect - external interface functions ***************
 *****************************************************************************/

/**** DUMMY FOR THE MOMENT *****/

bool 	p3ZeroConf::findPeer(std::string pid)
{
#ifdef DEBUG_ZEROCONF
	std::cerr << "p3ZeroConf::findPeer(" << pid << ")";
	std::cerr << std::endl;
#endif

	return true;
}

bool 	p3ZeroConf::dropPeer(std::string pid)
{
#ifdef DEBUG_ZEROCONF
	std::cerr << "p3ZeroConf::dropPeer(" << pid << ")";
	std::cerr << std::endl;
#endif

	return true;
}


	/* extract current peer status */
bool 	p3ZeroConf::getPeerStatus(std::string id, 
				struct sockaddr_in &/*laddr*/, struct sockaddr_in &/*raddr*/,
				uint32_t &/*type*/, uint32_t &/*mode*/)
{
	/* remove unused parameter warnings */
	(void) id;

#ifdef DEBUG_ZEROCONF
	std::cerr << "p3ZeroConf::getPeerStatus(" << id << ")";
	std::cerr << std::endl;
#endif

	return false;
}

#if 0
bool 	p3ZeroConf::getExternalInterface(struct sockaddr_in &/*raddr*/,
					uint32_t &/*mode*/)
{

#ifdef DEBUG_ZEROCONF
	std::cerr << "p3ZeroConf::getExternalInterface()";
	std::cerr << std::endl;
#endif


	return false;
}

#endif

bool 	p3ZeroConf::setAttachMode(bool on)
{

#ifdef DEBUG_ZEROCONF
	std::cerr << "p3ZeroConf::setAttachMode(" << on << ")";
	std::cerr << std::endl;
#endif

	return true;
}

int 	p3ZeroConf::addBadPeer(const struct sockaddr_in &addr, uint32_t reason, uint32_t flags, uint32_t age)
{
	return 1;
}

int 	p3ZeroConf::addKnownPeer(const std::string &pid, const struct sockaddr_in &addr, uint32_t flags)
{
	return 1;
}

void 	p3ZeroConf::ConnectionFeedback(std::string pid, int state)
{
	return;
}



/***********************************************************************************/
/*
 *
 *
 * ZeroConf Interface for Retroshare.
 * This will use the pqiNetAssistConnect Interface (like the DHT).
 * 
 * we will advertise ourselves using GPGID & SSLID on the zeroconf interface.
 *
 * Will search local network for any peers.
 * If they are our friends... callback to p3NetMgr to initiate connection.
 *
 * 
 * This should be very easy to implement under OSX.... so we start with that.
 * For Windows, we will use Apple's libraries... so that should be similar too.
 * Linux - won't bother with zeroconf for the moment - If someone feels like implementing that.. ok.
 *
 * Most important is that Windows and OSX can interact.
 *
 * We keep a simple system here....
 * 1) Register.
 * 2) Enable Browsing.
 * 3) Resolve Browse results one at a time.
 * 4) Callback Resolve results.
 *
 *
 */


int p3ZeroConf::tick()
{
	{
		RsStackMutex stack(mZcMtx); /****** STACK LOCK MUTEX *******/

		locked_startRegister();
		locked_startBrowse();
	}


	checkServiceFDs(); // will cause callbacks - if data is ready.

	checkResolveAction();
	checkLocationResults();
	checkQueryAction();
	checkQueryResults();
	
	return 0;
}

void p3ZeroConf::checkServiceFDs()
{
	RsStackMutex stack(mZcMtx); /****** STACK LOCK MUTEX *******/

	//std::cerr << "p3ZeroConf::checkServiceFDs()";
	//std::cerr << std::endl;
	
	if (mRegisterStatus == ZC_SERVICE_ACTIVE)
	{
		locked_checkFD(mRegisterRef);
	}
	
	if (mBrowseStatus == ZC_SERVICE_ACTIVE)
	{
		locked_checkFD(mBrowseRef);
	}
	
	if (mResolveStatus == ZC_SERVICE_ACTIVE)
	{
		locked_checkFD(mResolveRef);

		time_t age = time(NULL) - mResolveStatusTS;
		if (age > ZC_MAX_RESOLVE_TIME)
		{
			std::cerr << "p3ZeroConf::checkServiceFDs() Killing very old Resolve request";
			std::cerr << std::endl;
			locked_stopResolve();
		
		}	
	}

	
	if (mQueryStatus == ZC_SERVICE_ACTIVE)
	{
		locked_checkFD(mQueryRef);

		time_t age = time(NULL) - mQueryStatusTS;
		if (age > ZC_MAX_QUERY_TIME)
		{
			std::cerr << "p3ZeroConf::checkServiceFDs() Killing very old Query request";
			std::cerr << std::endl;
			locked_stopQueryIp();
		}	
	}
}


void p3ZeroConf::locked_checkFD(DNSServiceRef ref)
{
	//std::cerr << "p3ZeroConf::locked_checkFD() Start";
	//std::cerr << std::endl;

	int sockfd = DNSServiceRefSockFD(ref);

	fd_set ReadFDs, WriteFDs, ExceptFDs;
	FD_ZERO(&ReadFDs);
	FD_ZERO(&WriteFDs);
	FD_ZERO(&ExceptFDs);
	
	FD_SET(sockfd, &ReadFDs);

	struct timeval timeout;
	timeout.tv_sec = 0;
	timeout.tv_usec = 0;
	
	if (select(sockfd + 1, &ReadFDs, &WriteFDs, &ExceptFDs, &timeout) < 0)
	{
		std::cerr << "p3zeroconf::checkFD() Select ERROR";
		std::cerr << std::endl;
		return;
	}
	
	if (FD_ISSET(sockfd, &ReadFDs))
	{
		DNSServiceErrorType err = DNSServiceProcessResult(ref);
		switch(err)
		{
			case 0:
				/* ok */
				break;
			default:
				std::cerr << "DNSServiceProcessResult() ERROR: ";
				std::cerr << displayDNSServiceError(err);
				std::cerr << std::endl;
				break;
		}
	}

	//std::cerr << "p3ZeroConf::locked_checkFD() End";
	//std::cerr << std::endl;
	return;

}



int p3ZeroConf::checkResolveAction()
{
	RsStackMutex stack(mZcMtx); /****** STACK LOCK MUTEX *******/

	if (mResolveStatus == ZC_SERVICE_ACTIVE)
	{
		/* Do nothing - if we are already resolving */

		return 0;
	}

	/* iterate through the Browse Results... look for unresolved one - and do it! */

	/* check the new results Queue first */
	if (mBrowseResults.size() > 0)
	{
		std::cerr << "p3ZeroConf::checkResolveAction() Getting Item from BrowseResults";
		std::cerr << std::endl;

		zcBrowseResult br = mBrowseResults.front();
		mBrowseResults.pop_front();

		locked_startResolve(br.interfaceIndex, br.serviceName, br.regtype, br.replyDomain);
	}
	return 1;
}


int p3ZeroConf::checkLocationResults()
{
	zcLocationResult lr;

	{
		RsStackMutex stack(mZcMtx); /****** STACK LOCK MUTEX *******/

		/* check the results Queue  */
		if (mLocationResults.size() == 0)
		{
			return 0;
		}

		std::cerr << "p3ZeroConf::checkLocationResults() Getting Item from LocationResults";
		std::cerr << std::endl;

		lr = mLocationResults.front();
		mLocationResults.pop_front();

	}

	/* now callback to mPeerMgr to add new location... This will eventually get back here
	 * via some magic!
	 */

	std::cerr << "p3ZeroConf::checkLocationResults() adding new location.";
	std::cerr << std::endl;
	std::cerr << "gpgid = " << lr.gpgId;
	std::cerr << std::endl;
	std::cerr << "sslid = " << lr.sslId;
	std::cerr << std::endl;

	time_t now = time(NULL);
	mPeerMgr->addFriend(lr.sslId, lr.gpgId, RS_NET_MODE_UDP, RS_VIS_STATE_STD, now);
	return 1;
}


int p3ZeroConf::checkQueryAction()
{
	RsStackMutex stack(mZcMtx); /****** STACK LOCK MUTEX *******/

	if (mQueryStatus == ZC_SERVICE_ACTIVE)
	{
		/* Do nothing - if we are already resolving */

		return 0;
	}

	/* check the new results Queue first */
	if (mResolveResults.size() > 0)
	{
		std::cerr << "p3ZeroConf::checkQueryAction() Getting Item from ResolveResults";
		std::cerr << std::endl;

		zcResolveResult rr = mResolveResults.front();
		mResolveResults.pop_front();

		locked_startQueryIp(rr.interfaceIndex, rr.hosttarget, rr.gpgId, rr.sslId);
	}
	return 1;
}


int p3ZeroConf::checkQueryResults()
{
	zcQueryResult qr;
	{
		RsStackMutex stack(mZcMtx); /****** STACK LOCK MUTEX *******/

		/* check the results Queue  */
		if (mQueryResults.size() == 0)
		{
			return 0;
		}

		std::cerr << "p3ZeroConf::checkQueryResults() Getting Item from QueryResults";
		std::cerr << std::endl;

		qr = mQueryResults.front();
		mQueryResults.pop_front();

	}

	/* now callback to mLinkMgr to say we can connect */

	std::cerr << "p3ZeroConf::checkQueryResults() linkMgr->peerConnectRequest() to start link";
	std::cerr << std::endl;
	std::cerr << "to gpgid = " << qr.gpgId;
	std::cerr << std::endl;
	std::cerr << "sslid = " << qr.sslId;
	std::cerr << std::endl;

	time_t now = time(NULL);
	uint32_t flags = RS_CB_FLAG_MODE_TCP;
	uint32_t source = RS_CB_DHT; // SHOULD ADD NEW SOURCE ZC???
	struct sockaddr_in dummyProxyAddr, dummySrcAddr;
	mConnCb->peerConnectRequest(qr.sslId, qr.addr, dummyProxyAddr, dummySrcAddr, 
						source, flags, 0, 0); 

	return 1;
}






/* First Step is to register the DNSService */

void p3ZeroConf_CallbackRegister( DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, 
				const char *name, const char *regtype, const char *domain, void *context )
{
	p3ZeroConf *zc = (p3ZeroConf *) context;
	zc->callbackRegister(sdRef, flags, errorCode, name, regtype, domain);
}


int p3ZeroConf::locked_startRegister()
{
	if (mRegistered)
	{
		return 0;
	}

	/* need, port & txtRecord setup already */
	if (!mTextOkay)
	{
		std::cerr << "p3ZeroConf::locked_startRegister()";
		std::cerr << "Text Not Setup";
		std::cerr << std::endl;
		return 0;
	}

	if (!mPortOkay)
	{
		std::cerr << "p3ZeroConf::locked_startRegister()";
		std::cerr << "Port Not Set";
		std::cerr << std::endl;
		return 0;
	}

	if (mRegisterStatus == ZC_SERVICE_ACTIVE)
	{
		return 0;
	}
	

	std::cerr << "p3ZeroConf::locked_startRegister() Registering!";
	std::cerr << std::endl;
	std::cerr << "p3ZeroConf::locked_startRegister() Text: " << mTextRecord;
	std::cerr << std::endl;
	std::cerr << "p3ZeroConf::locked_startRegister() Port: " << mLocalPort;
	std::cerr << std::endl;

	DNSServiceRef *sdRef = &mRegisterRef;
	DNSServiceFlags flags = 0; /* no flags */
	uint32_t interfaceIndex = 0; /* all interfaces */
	char *name = NULL; /* may be NULL */

	std::string regtype = "_librs._tcp"; 
	char *domain = NULL; /* may be NULL for all domains. */
	char *host = NULL; /* may be NULL for default hostname */

	uint16_t port = htons(mLocalPort);
	uint16_t txtLen = mTextRecord.length();
	const void *txtRecord = (void *) mTextRecord.c_str(); 
	DNSServiceRegisterReply callBack = p3ZeroConf_CallbackRegister; 
	void *context = this;

	DNSServiceErrorType errcode = DNSServiceRegister (sdRef, flags, interfaceIndex, 
						name, regtype.c_str(), domain, host, port,
						txtLen, txtRecord, callBack, context);

	if (errcode != kDNSServiceErr_NoError)
	{
		std::cerr <<  "p3ZeroConf::locked_startRegister() ERROR: ";
		std::cerr << displayDNSServiceError(errcode);
		std::cerr << std::endl;
	}
	else
	{
		mRegisterStatus = ZC_SERVICE_ACTIVE;
		mRegisterStatusTS = time(NULL);
		mRegistered = true;
	}

	return 1;
}



void p3ZeroConf::callbackRegister( DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, 
				const char *name, const char *regtype, const char *domain)
{
	std::cerr << "p3ZeroConf::callbackRegister()";
	std::cerr << std::endl;

	/* handle queryIp */
	if (errorCode != kDNSServiceErr_NoError)
	{
		std::cerr <<  "p3ZeroConf::callbackRegister() FAILED ERROR: ";
		std::cerr << displayDNSServiceError(errorCode);
		std::cerr << std::endl;
		return;
	}

	std::cerr <<  "p3ZeroConf::callbackRegister() Success";
	std::cerr << std::endl;

}


void p3ZeroConf::locked_stopRegister()
{
	std::cerr << "p3ZeroConf::locked_stopRegister()";
	std::cerr << std::endl;

	if (mRegisterStatus != ZC_SERVICE_ACTIVE)
	{
		return;
	}

	DNSServiceRefDeallocate(mRegisterRef);

	mRegisterStatus = ZC_SERVICE_STOPPED;
	mRegisterStatusTS = time(NULL);
	mRegistered = false;
}


void p3ZeroConf_CallbackBrowse( DNSServiceRef sdRef, DNSServiceFlags flags, 
				uint32_t interfaceIndex, DNSServiceErrorType errorCode, 
    				const char *serviceName, const char *regtype, const char *replyDomain, void *context )
{
	p3ZeroConf *zc = (p3ZeroConf *) context;
	zc->callbackBrowse(sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain);
}


int p3ZeroConf::locked_startBrowse()
{
	if (mBrowseStatus == ZC_SERVICE_ACTIVE)
	{
		return 0;
	}
	
	std::cerr << "p3ZeroConf::locked_startBrowse()";
	std::cerr << std::endl;

	DNSServiceRef *sdRef = &mBrowseRef;
	DNSServiceFlags flags = 0;
	uint32_t interfaceIndex = 0;
	const char *regtype = "_librs._tcp";
	const char *domain = NULL;
	DNSServiceBrowseReply callBack = p3ZeroConf_CallbackBrowse;
	void *context = this;

	DNSServiceErrorType errcode = DNSServiceBrowse(sdRef, flags, interfaceIndex, 
						regtype, domain, callBack, context);

	if (errcode != kDNSServiceErr_NoError)
	{
		std::cerr <<  "p3ZeroConf::locked_startBrowse() ERROR: ";
		std::cerr << displayDNSServiceError(errcode);
		std::cerr << std::endl;
	}
	else
	{
		mBrowseStatus = ZC_SERVICE_ACTIVE;
		mBrowseStatusTS = time(NULL);
	}

	return 1;
}

 
void p3ZeroConf::callbackBrowse(DNSServiceRef /* sdRef */, DNSServiceFlags flags, 
				uint32_t interfaceIndex, DNSServiceErrorType errorCode, 
    				const char *serviceName, const char *regtype, const char *replyDomain)
{
	std::cerr << "p3ZeroConf::callbackBrowse()";
	std::cerr << std::endl;

	/* handle queryIp */
	if (errorCode != kDNSServiceErr_NoError)
	{
		std::cerr <<  "p3ZeroConf::callbackBrowse() FAILED ERROR: ";
		std::cerr << displayDNSServiceError(errorCode);
		std::cerr << std::endl;
		return;
	}

	zcBrowseResult br;

	br.flags = flags;
	br.interfaceIndex = interfaceIndex;
	br.serviceName = serviceName;
	br.regtype = regtype;
	br.replyDomain = replyDomain;

	std::cerr << "p3ZeroConf::callbackBrowse() ";
	std::cerr << " flags: " << flags;
	std::cerr << " interfaceIndex: " << interfaceIndex;
	std::cerr << " serviceName: " << serviceName;
	std::cerr << " regtype: " << regtype;
	std::cerr << " replyDomain: " << replyDomain;
	std::cerr << std::endl;

	if (flags & kDNSServiceFlagsAdd)
	{
		std::cerr << "p3ZeroConf::callbackBrowse() Add Flags => so newly found Service => BrowseResults";
		std::cerr << std::endl;
		mBrowseResults.push_back(br);
	}
	else
	{
		std::cerr << "p3ZeroConf::callbackBrowse() Missing Add Flag => so Service now dropped, Ignoring";
		std::cerr << std::endl;
	}

}


int p3ZeroConf::locked_stopBrowse()
{
	std::cerr << "p3ZeroConf::locked_stopBrowse()";
	std::cerr << std::endl;

	if (mBrowseStatus != ZC_SERVICE_ACTIVE)
	{
		return 0;
	}

	DNSServiceRefDeallocate(mBrowseRef);
	mBrowseStatus = ZC_SERVICE_STOPPED;
	mBrowseStatusTS = time(NULL);
	return 1;
}



void p3ZeroConf_CallbackResolve( DNSServiceRef sdRef, DNSServiceFlags flags, 
				uint32_t interfaceIndex, DNSServiceErrorType errorCode, 
    				const char *fullname, const char *hosttarget, uint16_t port, 
				uint16_t txtLen, const unsigned char *txtRecord, void *context )  
{
	p3ZeroConf *zc = (p3ZeroConf *) context;
	zc->callbackResolve(sdRef, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtLen, txtRecord);

}

void p3ZeroConf::locked_startResolve(uint32_t idx, std::string name, 
				std::string regtype, std::string domain)
{
	if (mResolveStatus == ZC_SERVICE_ACTIVE)
	{
		return;
	}

	std::cerr << "p3ZeroConf::locked_startResolve()";
	std::cerr << std::endl;

	DNSServiceRef *sdRef = &mResolveRef;
	DNSServiceFlags flags = 0;
	DNSServiceResolveReply callBack = p3ZeroConf_CallbackResolve;
	void *context = (void *) this;

	DNSServiceErrorType errcode = DNSServiceResolve(sdRef, flags, idx,
			name.c_str(), regtype.c_str(), domain.c_str(), callBack, context);

	if (errcode != kDNSServiceErr_NoError)
	{
		std::cerr <<  "p3ZeroConf::locked_startResolve() ERROR: ";
		std::cerr << displayDNSServiceError(errcode);
		std::cerr << std::endl;
	}
	else
	{
		mResolveStatus = ZC_SERVICE_ACTIVE;
		mResolveStatusTS = time(NULL);
	}
}

 

void p3ZeroConf::callbackResolve( DNSServiceRef /* sdRef */, DNSServiceFlags flags, 
				uint32_t interfaceIndex, DNSServiceErrorType errorCode, 
    				const char *fullname, const char *hosttarget, uint16_t port, 
				uint16_t txtLen, const unsigned char *txtRecord)
{
	std::cerr << "p3ZeroConf::callbackResolve()";
	std::cerr << std::endl;

	if (errorCode != kDNSServiceErr_NoError)
	{
		std::cerr <<  "p3ZeroConf::callbackResolve() FAILED ERROR: ";
		std::cerr << displayDNSServiceError(errorCode);
		std::cerr << std::endl;
		return;
	}

	/* handle resolve */
	zcResolveResult rr;

	rr.flags = flags;
	rr.interfaceIndex = interfaceIndex;
	rr.fullname = fullname;
	rr.hosttarget = hosttarget;
	rr.port = ntohs(port);
	rr.txtLen = txtLen;
	//rr.txtRecord = NULL; //malloc(rr.txtLen);
	//memcpy(rr.txtRecord, txtRecord, txtLen);
	// We must consume the txtRecord and translate into strings...

	std::cerr << "p3ZeroConf::callbackResolve() ";
	std::cerr << " flags: " << flags;
	std::cerr << " interfaceIndex: " << interfaceIndex;
	std::cerr << " fullname: " << fullname;
	std::cerr << " hosttarget: " << hosttarget;
	std::cerr << " port: " << ntohs(port);
	std::cerr << " txtLen: " << txtLen;
	std::cerr << std::endl;

	std::string peerGpgId;
	std::string peerSslId;
	if (procPeerTxtRecord(txtLen, txtRecord, peerGpgId, peerSslId))
	{
		rr.gpgId = peerGpgId;
		rr.sslId = peerSslId;

		/* check if we care - install ssl record */
		int resolve = locked_checkResolvedPeer(rr);
		if (resolve)
		{
			mResolveResults.push_back(rr);
		}
	}

	if (flags & kDNSServiceFlagsMoreComing)
	{
		std::cerr << "p3ZeroConf::callbackResolve() Expecting More Results.. keeping Ref";
		std::cerr << std::endl;
	}
	else
	{
		std::cerr << "p3ZeroConf::callbackResolve() Result done.. closing Ref";
		std::cerr << std::endl;

		locked_stopResolve();
	}
}


/* returns if we are interested in the peer */
int p3ZeroConf::locked_checkResolvedPeer(const zcResolveResult &rr)
{
	/* if self - drop it */
	if ((rr.gpgId == mOwnGpgId) && (rr.sslId == mOwnSslId))
	{
		std::cerr << "p3ZeroConf::locked_checkpeer() Found Self on LAN:";
		std::cerr << std::endl;
		std::cerr << "gpgid = " << rr.gpgId;
		std::cerr << std::endl;
		std::cerr << "sslid = " << rr.sslId;
		std::cerr << std::endl;

		return 0;
	}

	/* check if peerGpgId is in structure already */
	std::map<std::string, zcPeerDetails>::iterator it;
	it = mPeerDetails.find(rr.gpgId);
	if (it == mPeerDetails.end())
	{
		/* possibility we don't have any SSL ID's for this person 
		 * check against AuthGPG()
	 	 */
		if ((AuthGPG::getAuthGPG()->isGPGAccepted(rr.gpgId)) || (rr.gpgId == mOwnGpgId))
		{
			zcPeerDetails newFriend;
			newFriend.gpgId = rr.gpgId;
			mPeerDetails[rr.gpgId] = newFriend;

			it = mPeerDetails.find(rr.gpgId);
		}
		else
		{
			std::cerr << "p3ZeroConf::locked_checkpeer() Found Unknown peer on LAN:";
			std::cerr << std::endl;
			std::cerr << "gpgid = " << rr.gpgId;
			std::cerr << std::endl;
			std::cerr << "sslid = " << rr.sslId;
			std::cerr << std::endl;

			return 0;
		}
	}

	/* now see if we have an sslId */
	std::map<std::string, zcLocationDetails>::iterator lit;
	lit = it->second.mLocations.find(rr.sslId);
	if (lit == it->second.mLocations.end())
	{
		/* install a new location -> flag it as this */
		zcLocationDetails loc;
		loc.mSslId = rr.sslId;
		loc.mStatus = ZC_STATUS_FOUND | ZC_STATUS_NEW_LOCATION;
		loc.mPort = rr.port;
		loc.mFullName = rr.fullname;
		loc.mHostTarget = rr.hosttarget;
		loc.mFoundTs = time(NULL);

		it->second.mLocations[rr.sslId] = loc;


		std::cerr << "p3ZeroConf::locked_checkpeer() Adding New Location for:";
		std::cerr << std::endl;
		std::cerr << "gpgid = " << rr.gpgId;
		std::cerr << std::endl;
		std::cerr << "sslid = " << rr.sslId;
		std::cerr << std::endl;
		std::cerr << "port = " << rr.port;
		std::cerr << std::endl;


		/* install item in ADD Queue */
		zcLocationResult locResult(rr.gpgId, rr.sslId);
		mLocationResults.push_back(locResult);

		/* return 1, to resolve fully */
		return 1;
	}


	std::cerr << "p3ZeroConf::locked_checkpeer() Updating Location for:";
	std::cerr << std::endl;
	std::cerr << "gpgid = " << rr.gpgId;
	std::cerr << std::endl;
	std::cerr << "sslid = " << rr.sslId;
	std::cerr << std::endl;
	std::cerr << "port = " << rr.port;
	std::cerr << std::endl;

	lit->second.mStatus |= ZC_STATUS_FOUND;
	lit->second.mPort = rr.port;
	lit->second.mFullName = rr.fullname;
	lit->second.mHostTarget = rr.hosttarget;
	lit->second.mFoundTs = time(NULL);

	/* otherwise we get here - and we see if we are already connected */
	if (lit->second.mStatus & ZC_STATUS_CONNECTED)
	{
		return 0;
	}
	return 1;
}

int p3ZeroConf::locked_stopResolve()
{
	std::cerr << "p3ZeroConf::locked_stopResolve()";
	std::cerr << std::endl;

	if (mResolveStatus != ZC_SERVICE_ACTIVE)
	{
		return 0;
	}

	DNSServiceRefDeallocate(mResolveRef);
	mResolveStatus = ZC_SERVICE_STOPPED;
	mResolveStatusTS = time(NULL);
	return 1;
}




void p3ZeroConf_CallbackQueryIp( DNSServiceRef sdRef, DNSServiceFlags flags, 
				uint32_t interfaceIndex, DNSServiceErrorType errorCode, 
    				const char *fullname, uint16_t rrtype, uint16_t rrclass, 
    				uint16_t rdlen, const void *rdata, uint32_t ttl, void *context )  
{
	p3ZeroConf *zc = (p3ZeroConf *) context;
	zc->callbackQueryIp(sdRef, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rdata, ttl);

}

void p3ZeroConf::locked_startQueryIp(uint32_t idx, std::string hosttarget, std::string gpgId, std::string sslId)
{
	if (mQueryStatus == ZC_SERVICE_ACTIVE)
	{
		return;
	}

	std::cerr << "p3ZeroConf::locked_startQueryIp()";
	std::cerr << std::endl;
	std::cerr << "\thosttarget: " << hosttarget;
	std::cerr << std::endl;
	std::cerr << "\tgpgId: " << gpgId;
	std::cerr << std::endl;
	std::cerr << "\tsslId: " << sslId;
	std::cerr << std::endl;


	DNSServiceRef *sdRef = &mQueryRef;
	DNSServiceFlags flags = 0;
	uint16_t rrtype = kDNSServiceType_A;
	uint16_t rrclass = kDNSServiceClass_IN;
	DNSServiceQueryRecordReply callBack = p3ZeroConf_CallbackQueryIp;
	void *context = (void *) this;

	DNSServiceErrorType errcode = DNSServiceQueryRecord(sdRef, flags, idx, 
			hosttarget.c_str(), rrtype, rrclass, callBack, context);

	if (errcode != kDNSServiceErr_NoError)
	{
		std::cerr <<  "p3ZeroConf::locked_startQueryIp() ERROR: ";
		std::cerr << displayDNSServiceError(errcode);
		std::cerr << std::endl;
	}
	else
	{
		std::cerr << "p3ZeroConf::locked_startQueryIp() Query Active";
		std::cerr << std::endl;

		mQueryStatus = ZC_SERVICE_ACTIVE;
		mQueryStatusTS = time(NULL);
		mQueryGpgId = gpgId;
		mQuerySslId = sslId;
	}
}

void p3ZeroConf::callbackQueryIp( DNSServiceRef /* sdRef */, DNSServiceFlags flags, 
				uint32_t interfaceIndex, DNSServiceErrorType errorCode, 
    				const char *fullname, uint16_t rrtype, uint16_t rrclass, 
    				uint16_t rdlen, const void *rdata, uint32_t ttl)
{
	std::cerr << "p3ZeroConf::callbackQueryIp()";
	std::cerr << std::endl;

	/* handle queryIp */
	if (errorCode != kDNSServiceErr_NoError)
	{
		std::cerr <<  "p3ZeroConf::callbackQueryIp() FAILED ERROR: ";
		std::cerr << displayDNSServiceError(errorCode);
		std::cerr << std::endl;
		return;
	}

	/* handle resolve */
	zcQueryResult qr;

	qr.flags = flags;
	qr.interfaceIndex = interfaceIndex;
	qr.fullname = fullname;
	qr.rrtype = rrtype;
	qr.rrclass = rrclass;
	qr.rdlen = rdlen;
	//qr.rdata = NULL; //malloc(rr.rdlen);
	//memcpy(rr.rdata, rdata, rdlen);
	qr.ttl = ttl;

	std::cerr << "p3ZeroConf::callbackQuery() ";
	std::cerr << " flags: " << flags;
	std::cerr << " interfaceIndex: " << interfaceIndex;
	std::cerr << " fullname: " << fullname;
	std::cerr << " rrtype: " << rrtype;
	std::cerr << " rrclass: " << rrclass;
	std::cerr << " rdlen: " << rdlen;
	std::cerr << " ttl: " << ttl;
	std::cerr << std::endl;

	/* add in the query Ids, that we saved... */
	qr.sslId = mQuerySslId;
	qr.gpgId = mQueryGpgId;

	if ((rrtype == kDNSServiceType_A) && (rdlen == 4))
	{
		qr.addr.sin_addr.s_addr = *((uint32_t *) rdata);

		if (locked_completeQueryResult(qr)) // Saves Query Results, and fills in Port details.
		{
			mQueryResults.push_back(qr);
		}
	}
	else
	{
		std::cerr << "p3ZeroConf::callbackQuery() Unknown rrtype";
		std::cerr << std::endl;
	}

	if (flags & kDNSServiceFlagsMoreComing)
	{
		std::cerr << "p3ZeroConf::callbackQuery() Expecting More Results.. keeping Ref";
		std::cerr << std::endl;
	}
	else
	{
		std::cerr << "p3ZeroConf::callbackQuery() Result done.. closing Ref";
		std::cerr << std::endl;

		locked_stopQueryIp();
	}
}


/* returns 1 if all is good */
int p3ZeroConf::locked_completeQueryResult(zcQueryResult &qr)
{
	/* check if peerGpgId is in structure already */
	std::map<std::string, zcPeerDetails>::iterator it;
	it = mPeerDetails.find(qr.gpgId);
	if (it == mPeerDetails.end())
	{
		/* Should not be possible to get here! */
		std::cerr << "p3ZeroConf::locked_completeQueryResults() Missing GPGID";
		std::cerr << std::endl;

		return 0;
	}

	/* now see if we have an sslId */
	std::map<std::string, zcLocationDetails>::iterator lit;
	lit = it->second.mLocations.find(qr.sslId);
	if (lit == it->second.mLocations.end())
	{
		/* Should not be possible to get here! */
		std::cerr << "p3ZeroConf::locked_completeQueryResults() Missing SSLID";
		std::cerr << std::endl;

		return 0;
	}


	/*** NOW HAVE FOUND ENTRY, exchange info ****/

	std::cerr << "p3ZeroConf::locked_completeQueryResults() Filling in Peer IpAddress";
	std::cerr << std::endl;

	qr.addr.sin_port = htons(lit->second.mPort);
	lit->second.mAddress = qr.addr;
	lit->second.mStatus |= ZC_STATUS_IPADDRESS;
	lit->second.mAddrTs = time(NULL);

	/* if we have connected? */
	if (lit->second.mStatus & ZC_STATUS_CONNECTED)
	{
		std::cerr << "p3ZeroConf::locked_completeQueryResults() Warning Already Connected";
		std::cerr << std::endl;
	}
	return 1;
}


int p3ZeroConf::locked_stopQueryIp()
{
	std::cerr << "p3ZeroConf::locked_stopQueryIp()";
	std::cerr << std::endl;

	if (mQueryStatus != ZC_SERVICE_ACTIVE)
	{
		return 0;
	}

	DNSServiceRefDeallocate(mQueryRef);
	mQueryStatus = ZC_SERVICE_STOPPED;
	mQueryStatusTS = time(NULL);
	return 1;
}




std::string displayDNSServiceError(DNSServiceErrorType errcode)
{
	std::string str;
	switch(errcode)
	{
		default:
			str = "kDNSServiceErr_??? UNKNOWN-CODE";
 			break;
	case kDNSServiceErr_NoError:
			str = "kDNSServiceErr_NoError";
 			break;
	case kDNSServiceErr_Unknown:
			str = "kDNSServiceErr_Unknown";
 			break;
	case kDNSServiceErr_NoSuchName:
			str = "kDNSServiceErr_NoSuchName";
 			break;
	case kDNSServiceErr_NoMemory:
			str = "kDNSServiceErr_NoMemory";
 			break;
	case kDNSServiceErr_BadParam:
			str = "kDNSServiceErr_BadParam";
 			break;
	case kDNSServiceErr_BadReference:
			str = "kDNSServiceErr_BadReference";
 			break;
	case kDNSServiceErr_BadState:
			str = "kDNSServiceErr_BadState";
 			break;
	case kDNSServiceErr_BadFlags:
			str = "kDNSServiceErr_BadFlags";
 			break;
	case kDNSServiceErr_Unsupported:
			str = "kDNSServiceErr_Unsupported";
 			break;
	case kDNSServiceErr_NotInitialized:
			str = "kDNSServiceErr_NotInitialized";
 			break;
	case kDNSServiceErr_AlreadyRegistered:
			str = "kDNSServiceErr_AlreadyRegistered";
 			break;
	case kDNSServiceErr_NameConflict:
			str = "kDNSServiceErr_NameConflict";
 			break;
	case kDNSServiceErr_Invalid:
			str = "kDNSServiceErr_Invalid";
 			break;
	case kDNSServiceErr_Firewall:
			str = "kDNSServiceErr_Firewall";
 			break;
	case kDNSServiceErr_Incompatible:
			str = "kDNSServiceErr_Incompatible";
 			break;
	case kDNSServiceErr_BadInterfaceIndex:
			str = "kDNSServiceErr_BadInterfaceIndex";
 			break;
	case kDNSServiceErr_Refused:
			str = "kDNSServiceErr_Refused";
 			break;
	case kDNSServiceErr_NoSuchRecord:
			str = "kDNSServiceErr_NoSuchRecord";
 			break;
	case kDNSServiceErr_NoAuth:
			str = "kDNSServiceErr_NoAuth";
 			break;
	case kDNSServiceErr_NoSuchKey:
			str = "kDNSServiceErr_NoSuchKey";
 			break;
	case kDNSServiceErr_NATTraversal:
			str = "kDNSServiceErr_NATTraversal";
 			break;
	case kDNSServiceErr_DoubleNAT:
			str = "kDNSServiceErr_DoubleNAT";
 			break;
	case kDNSServiceErr_BadTime:
			str = "kDNSServiceErr_BadTime";
 			break;
	case kDNSServiceErr_BadSig:
			str = "kDNSServiceErr_BadSig";
 			break;
	case kDNSServiceErr_BadKey:
			str = "kDNSServiceErr_BadKey";
 			break;
	case kDNSServiceErr_Transient:
			str = "kDNSServiceErr_Transient";
 			break;
	case kDNSServiceErr_ServiceNotRunning:
			str = "kDNSServiceErr_ServiceNotRunning";
 			break;
	case kDNSServiceErr_NATPortMappingUnsupported:
			str = "kDNSServiceErr_NATPortMappingUnsupported";
 			break;
	case kDNSServiceErr_NATPortMappingDisabled:
			str = "kDNSServiceErr_NATPortMappingDisabled";
 			break;
#if 0
	case kDNSServiceErr_NoRouter:
			str = "kDNSServiceErr_NoRouter";
 			break;
	case kDNSServiceErr_PollingMode:
			str = "kDNSServiceErr_PollingMode";
 			break;
#endif
	}

	return str;
}



/***********************************************************************************
 * Handle Peer Data, list of friends etc.
 *
 * We need data structures to handle the Info from libretroshare, 
 * and the info from ZeroConf too.
 *
 ****/

/* This needs to be a separate class - as we don't know which peer it is
 * associated with until we have Resolved the details.
 */
	
class RsZCBrowseDetails
{
	public:
	RsZCBrowseDetails();

	uint32_t mBrowseState;
	time_t   mBrowseUpdate;

	uint32_t mBrowseInterfaceIndex;
	std::string mBrowserServiceName;
	std::string mBrowserRegType;
	std::string mBrowserReplyDomain;

	uint32_t mResolveInterfaceIndex;
	std::string mResolveFullname;
	std::string mResolveHostTarget;
	uint32_t mResolvePort;
	std::string mResolveTxtRecord;
};



class RsZCPeerDetails
{
	/* passed from libretroshare */

	std::string mGpgId;
	std::string mSslId;

	uint32_t mPeerStatus; /* FRIEND, FOF, ONLINE, OFFLINE */

	/* Browse Info */

	uint32_t mBrowseState;
	time_t   mBrowseUpdate;

	uint32_t mBrowseInterfaceIndex;
	std::string mBrowserServiceName;
	std::string mBrowserRegType;
	std::string mBrowserReplyDomain;

	/* Resolve Info */

	uint32_t mResolveInterfaceIndex;
	std::string mResolveFullname;
	std::string mResolveHostTarget;
	uint32_t mResolvePort;
	std::string mResolveTxtRecord;
};



	







