/***************************************************************
 *
 * Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************/


#include "condor_common.h"
#include "condor_debug.h"
#include "condor_config.h"
#include "condor_attributes.h"
#include "internet.h"
#include "daemon.h"
#include "condor_daemon_core.h"
#include "dc_collector.h"
#include "subsystem_info.h"

#include <algorithm>

std::map< std::string, Timeslice > DCCollector::blacklist;


// Instantiate things

DCCollector::DCCollector( const char* dcName, UpdateType uType )
	: Daemon( DT_COLLECTOR, dcName, NULL )
{
	this->constructorName = dcName;

	up_type = uType;
	init( true );
}

void
DCCollector::init( bool needs_reconfig )
{
	static long bootTime = 0;
	reconfigTime = 0;

	update_rsock = NULL;
	use_tcp = true;
	use_nonblocking_update = true;
	update_destination = NULL;
	timerclear( &m_blacklist_monitor_query_started );

	if (bootTime == 0) {
		bootTime = time( NULL );
	} 
	reconfigTime = startTime = bootTime;

	if( needs_reconfig ) {
		reconfigTime = time( NULL );
		reconfig();
	}
}


DCCollector::DCCollector( const DCCollector& copy ) : Daemon(copy)
{
	init( false );
	deepCopy( copy );
}


DCCollector&
DCCollector::operator = ( const DCCollector& copy )
{
		// don't copy ourself!
	if (&copy != this) {
		deepCopy( copy );
	}

	return *this;
}


void
DCCollector::deepCopy( const DCCollector& copy )
{
	if( update_rsock ) {
		delete update_rsock;
		update_rsock = NULL;
	}
		/*
		  for now, we're not going to attempt to copy the update_rsock
		  from the copy, since i'm not sure i trust ReliSock's copy
		  constructor to do the right thing once the original goes
		  away... DCCollector will be able to re-create this socket
		  for TCP updates.  it's a little expensive, since we need a
		  whole new connect(), etc, but in most cases, we're not going
		  to be doing this very often, and correctness is more
		  important than speed at the moment.  once we have more time
		  for testing, we can figure out if just copying the
		  update_rsock works and TCP updates are still happy...
		*/

	use_tcp = copy.use_tcp;
	use_nonblocking_update = copy.use_nonblocking_update;

	up_type = copy.up_type;

	if( update_destination ) {
		free(update_destination);
	}
	update_destination = copy.update_destination ? strdup( copy.update_destination ) : NULL;

	startTime = copy.startTime;
}


bool
DCCollector::requestScheddToken(const std::string &schedd_name,
	const std::vector<std::string> &authz_bounding_set,
	int lifetime, std::string &token, CondorError &err)
{
	classad::ClassAd request_ad;

	if (!authz_bounding_set.empty())
	{
		if (!request_ad.InsertAttr(ATTR_SEC_LIMIT_AUTHORIZATION, join(authz_bounding_set, ","))) {
			err.push("DCCollector", 1, "Failed to insert authorization bound.");
			return false;
		}
	}

	if ((lifetime >= 0) &&
		!request_ad.InsertAttr(ATTR_SEC_TOKEN_LIFETIME, lifetime))
	{
		err.push("DCCollector", 1, "Failed to insert lifetime.");
		return false;
	}

	if (!request_ad.InsertAttr(ATTR_NAME, schedd_name))
	{
		err.push("DCCollector", 1, "Failed to insert schedd name.");
		return false;
	}

	ReliSock rSock;
	rSock.timeout(5);
	if (!connectSock(&rSock)) {
		err.pushf("DCCollector", 2, "Failed to connect "
			"to remote daemon at '%s'", _addr.c_str());
		dprintf(D_FULLDEBUG, "DCCollector::requestScheddToken() failed to connect "
			"to remote daemon at '%s'\n", _addr.c_str());
		return false;
	}

	if (!startCommand(IMPERSONATION_TOKEN_REQUEST, &rSock, 20, &err))
	{
		err.pushf("DAEMON", 1, "failed to start "
			"command for token request with remote collector at '%s'.",
			_addr.c_str());
		dprintf(D_FULLDEBUG, "DCCollector::requestScheddToken() failed to start "
			"command for token request with remote collector at '%s'.",
			_addr.c_str());
		return false;
	}

	rSock.encode();
	if (!putClassAd(&rSock, request_ad) || !rSock.end_of_message()) {
		err.pushf("DAEMON", 1, "Failed to send request to "
			"remote collector at '%s'",
			_addr.c_str());
		dprintf(D_FULLDEBUG, "DCCollector::requestScheddToken() failed to send "
			"request to remote collector at '%s'\n",
			_addr.c_str());
		return false;
	}

	rSock.decode();
	classad::ClassAd result_ad;
	if (!getClassAd(&rSock, result_ad) || !rSock.end_of_message()) {
		err.pushf("DAEMON", 1, "Failed to recieve "
			"response from remote collector at '%s'",
			_addr.c_str());
		dprintf(D_FULLDEBUG, "DCCollector::requestScheddToken() failed to recieve "
			"response from remote daemon at '%s'\n",
			_addr.c_str());
		return false;
	}

	std::string err_msg;
	if (result_ad.EvaluateAttrString(ATTR_ERROR_STRING, err_msg)) {
		int error_code = 0;
		result_ad.EvaluateAttrInt(ATTR_ERROR_CODE, error_code);
		if (!error_code) error_code = -1;

		err.push("DAEMON", error_code, err_msg.c_str());
		return false;
	}

	if (!result_ad.EvaluateAttrString(ATTR_SEC_TOKEN, token) || token.empty()) {
		err.pushf("DAEMON", 1, "BUG! DCCollector::requestScheddToken() "
			"received a malformed ad, containing no resulting token and no "
			"error message, from remote collector at '%s'",
			_addr.c_str());
		dprintf(D_FULLDEBUG, "BUG!  DCCollector::requestScheddToken() "
			"received a malformed ad, containing no resulting token and no "
			"error message, from remote daemon at '%s'\n",
			_addr.c_str());
		return false;
	}

	return true;
}

void
DCCollector::reconfig( void )
{
	use_nonblocking_update = param_boolean("NONBLOCKING_COLLECTOR_UPDATE",true);

	if( _addr.empty() ) {
		locate();
		if( ! _is_configured ) {
			dprintf( D_FULLDEBUG, "COLLECTOR address not defined in "
					 "config file, not doing updates\n" );
			return;
		}
	}

	parseTCPInfo();
	initDestinationStrings();
	displayResults();
}


void
DCCollector::parseTCPInfo( void )
{
	switch( up_type ) {
	case TCP:
		use_tcp = true;
		break;
	case UDP:
		use_tcp = false;
		break;
	case CONFIG:
	case CONFIG_VIEW:
		use_tcp = false;
		char *tmp = param( "TCP_UPDATE_COLLECTORS" );
		if( tmp ) {
			std::vector<std::string> tcp_collectors = split(tmp);
			free( tmp );
			if( ! _name.empty() &&
				contains_anycase_withwildcard(tcp_collectors, _name) )
			{
				use_tcp = true;
				break;
			}
		}
		if ( up_type == CONFIG_VIEW ) {
			use_tcp = param_boolean( "UPDATE_VIEW_COLLECTOR_WITH_TCP", false );
		} else {
			use_tcp = param_boolean( "UPDATE_COLLECTOR_WITH_TCP", true );
		}
		if( !hasUDPCommandPort() ) {
			use_tcp = true;
		}
		break;
	}
}

// do a >= version check
bool DCCollector::checkCachedVersion(int major, int minor, int subminor, bool default_value) {
	if (_version.empty()) return default_value;
	return CondorVersionInfo(_version.c_str()).built_since_version(major, minor, subminor);
}

bool
DCCollector::sendUpdate( int cmd, ClassAd* ad1, DCCollectorAdSequences& adSeq, ClassAd* ad2, bool nonblocking, StartCommandCallbackType callback_fn, void *miscdata)
{
	if( ! _is_configured ) {
			// nothing to do, treat it as success...
		return true;
	}

	if(!use_nonblocking_update || !daemonCore) {
			// Either caller OR config may turn off nonblocking updates.
			// In other words, both must be true to enable nonblocking.
			// Also, must have DaemonCore intialized.
		nonblocking = false;
	}

	// if we have not yet stashed the collector version from the sock into the daemon object do that now.
	// the update sock version is set by security negotiatiation, so we won't know it for the first update.
	// By the time we *do* know it we are committed to the command int <sigh>. Because of this it is the
	// callers responsibility to make sure that the first command over the update sock is safe for
	// all versions of the collector.
	// The adtype INVALIDATE commands are safe, the adtype UPDATE commands are only safe if adtype
	// is one of the adtypes that has been around forever *and* the ad itself is one that the collector
	// is willing to store.  Thus it is *not* safe to a STARTD daemon ad to be the first update, because
	// a pre 23.2 collector will either store it in the slot ad table, or it will close the connection if
	// it cannot be hashed as a slot ad.
	if (_version.empty() && update_rsock) {
		auto * verinfo = update_rsock->get_peer_version();
		if (verinfo) { _version = verinfo->get_version_stdstring(); }
		dprintf(D_ZKM, "DCCollector::sendUpdate collector %s version was unknown, is now %s\n",
			_name.c_str(), _version.c_str());
	}

	// Add start time & seq # to the ads before we publish 'em
	if ( ad1 ) {
		ad1->Assign(ATTR_DAEMON_START_TIME, startTime);
		ad1->Assign(ATTR_DAEMON_LAST_RECONFIG_TIME, reconfigTime);
	}
	if ( ad2 ) {
		ad2->Assign(ATTR_DAEMON_START_TIME, startTime);
		ad2->Assign(ATTR_DAEMON_LAST_RECONFIG_TIME, reconfigTime);
	}

	if ( ad1 ) {
		auto & seqgen = adSeq.getAdSeq(*ad1);
		if (cmd == UPDATE_STARTD_AD && seqgen.getAdType() == STARTDAEMON_AD
			&& do_version_check_before_startd_daemon_ad_update) {
			// We can't send STARTD daemon ads to collectors that are older than 23.2
			// so when we don't know the version, or the collector is older, just fail now
			const char * err_reason = nullptr;
			if (_version.empty()) {
				err_reason = "version is not known";
			} else if ( ! CondorVersionInfo(_version.c_str()).built_since_version(23,2,0)) {
				err_reason = "version is older than 23.2";
			}
			if (err_reason) {
				std::string err_msg, adname;
				ad1->LookupString(ATTR_NAME, adname);
				formatstr(err_msg, "Collector %s %s - will not send STARD daemon ad %s",
					_name.c_str(), err_reason, adname.c_str());
				newError(CA_INVALID_REQUEST, err_msg.c_str());
				if (callback_fn) {
					(*callback_fn)(false, nullptr, nullptr, "", false, miscdata);
				}
				dprintf(D_ZKM, "DCCollector::sendUpdate will not send STARTD daemon ad because %s\n", err_reason);
				return false;
			}
		}
		long long seq = seqgen.getSequence();
		ad1->Assign(ATTR_UPDATE_SEQUENCE_NUMBER, seq);
		if (ad2) { ad2->Assign(ATTR_UPDATE_SEQUENCE_NUMBER, seq); }
	}

		// Prior to 7.2.0, the negotiator depended on the startd
		// supplying matching MyAddress in public and private ads.
	if ( ad1 && ad2 ) {
		CopyAttribute(ATTR_MY_ADDRESS,*ad2,*ad1);
	}

		// We never want to try sending an update to port 0.  If we're
		// about to try that, and we're trying to talk to a local
		// collector, we should try re-reading the address file and
		// re-setting our port.
	if( _port == 0 ) {
		dprintf( D_HOSTNAME, "About to update collector with port 0, "
				 "attempting to re-read address file\n" );
		if( readAddressFile(_subsys.c_str()) ) {
			_port = string_to_port( _addr.c_str() );
			parseTCPInfo(); // update use_tcp
			dprintf( D_HOSTNAME, "Using port %d based on address \"%s\"\n",
					 _port, _addr.c_str() );
		}
	}

	if( _port <= 0 ) {
			// If it's still 0, we've got to give up and fail.
		std::string err_msg;
		formatstr(err_msg, "Can't send update: invalid collector port (%d)",
						 _port );
		newError( CA_COMMUNICATION_ERROR, err_msg.c_str() );
		if (callback_fn) {
			(*callback_fn)(false, nullptr, nullptr, "", false, miscdata);
		}
		return false;
	}

	//
	// We don't want the collector to send TCP updates to itself, since
	// this could cause it to deadlock.  Since the only ad a collector
	// will ever advertise is its own, only check for *_COLLECTOR_ADS.
	//
	if( cmd == UPDATE_COLLECTOR_AD || cmd == INVALIDATE_COLLECTOR_ADS ) {
		if( daemonCore ) {
			const char * myOwnSinful = daemonCore->InfoCommandSinfulString();
			if( myOwnSinful == NULL ) {
				dprintf( D_ALWAYS, "Unable to determine my own address, will not update or invalidate collector ad to avoid potential deadlock.\n" );
				if (callback_fn) {
					(*callback_fn)(false, nullptr, nullptr, "", false, miscdata);
				}
				return false;
			}
			if( _addr.empty() ) {
				dprintf( D_ALWAYS, "Failing attempt to update or invalidate collector ad because of missing daemon address (probably an unresolved hostname; daemon name is '%s').\n", _name.c_str() );
				if (callback_fn) {
					(*callback_fn)(false, nullptr, nullptr, "", false, miscdata);
				}
				return false;
			}
			if( strcmp( myOwnSinful, _addr.c_str() ) == 0 ) {
				EXCEPT( "Collector attempted to send itself an update." );
			}
		}
	}

	if( use_tcp ) {
		return sendTCPUpdate( cmd, ad1, ad2, nonblocking, callback_fn, miscdata );
	}
	return sendUDPUpdate( cmd, ad1, ad2, nonblocking, callback_fn, miscdata );
}



bool
DCCollector::finishUpdate( DCCollector *self, Sock* sock, ClassAd* ad1, ClassAd* ad2, StartCommandCallbackType callback_fn, void *miscdata )
{
		// Only send secrets in the case where
		// the collector has been build since 8.9.3 and understands not
		// to share submitter secrets.
	auto *verinfo = sock->get_peer_version();
	bool send_submitter_secrets = false;
	if (verinfo) {
		if (self && self->_version.empty()) { self->_version = verinfo->get_version_stdstring(); };
		if (verinfo->built_since_version(8, 9, 3)) {
			send_submitter_secrets = true;
		}
	}

		// If we are advertising to an admin-configured pool, then
		// we allow the admin to skip authorization; they presumably
		// know what they are doing.  However, if we are acting on
		// behalf of a user, then we assume that secrets must be encrypted!
	if (!self || (!self->getOwner().empty() && !sock->set_crypto_mode(true))) {
		send_submitter_secrets = false;
	}

	int options = send_submitter_secrets ? 0: PUT_CLASSAD_NO_PRIVATE;

	// This is a static function so that we can call it from a
	// nonblocking startCommand() callback without worrying about
	// longevity of the DCCollector instance.

	sock->encode();
	if( ad1 && ! putClassAd(sock, *ad1, options) ) {
		if(self) {
			self->newError( CA_COMMUNICATION_ERROR,
			                "Failed to send ClassAd #1 to collector" );
		}
		if (callback_fn) {
			(*callback_fn)(false, sock, nullptr, sock->getTrustDomain(), sock->shouldTryTokenRequest(), miscdata);
		}
		return false;
	}
		// This is always a private ad.
	if( ad2 && ! putClassAd(sock, *ad2, 0) ) {
		if(self) {
			self->newError( CA_COMMUNICATION_ERROR,
			          "Failed to send ClassAd #2 to collector" );
		}
		if (callback_fn) {
			(*callback_fn)(false, sock, nullptr, sock->getTrustDomain(), sock->shouldTryTokenRequest(), miscdata);
		}
		return false;
	}
	if( ! sock->end_of_message() ) {
		if(self) {
			self->newError( CA_COMMUNICATION_ERROR,
			          "Failed to send EOM to collector" );
		}
		if (callback_fn) {
			(*callback_fn)(false, sock, nullptr, sock->getTrustDomain(), sock->shouldTryTokenRequest(), miscdata);
		}
		return false;
	}
	if (callback_fn) {
		(*callback_fn)(true, sock, nullptr, sock->getTrustDomain(), sock->shouldTryTokenRequest(), miscdata);
	}
	return true;
}

class UpdateData {

private:
	int cmd;
	Stream::stream_type sock_type;
public:
	ClassAd *ad1;
	ClassAd *ad2;
	DCCollector *dc_collector;
	StartCommandCallbackType *m_callback_fn{nullptr};
	void *m_miscdata{nullptr};

	UpdateData(int ad_cmd, Stream::stream_type stype, ClassAd *cad1, ClassAd *cad2, DCCollector *dc_collect, StartCommandCallbackType *callback_fn, void *miscdata)
	  : cmd(ad_cmd),
	    sock_type(stype),
	    ad1(cad1 ? new ClassAd(*cad1) : NULL),
	    ad2(cad2 ? new ClassAd(*cad2) : NULL),
	    dc_collector(dc_collect),
	    m_callback_fn(callback_fn),
	    m_miscdata(miscdata)
	{
			// In case the collector object gets destructed before this
			// update is finished, we need to register ourselves with
			// the dc_collector object so that it can null out our
			// pointer to it.  This is done using a linked-list of
			// UpdateData objects.

		dc_collect->pending_update_list.push_back(this);
	}

	~UpdateData() {
		delete ad1;
		delete ad2;
			// Remove ourselves from the dc_collector's list.
		if(dc_collector) {
			std::deque<UpdateData *>::iterator iter = std::find(dc_collector->pending_update_list.begin(), dc_collector->pending_update_list.end(), this);
			if (iter != dc_collector->pending_update_list.end())
			{
				dc_collector->pending_update_list.erase(iter);
			}
		}
	}

	void DCCollectorGoingAway() {
			// The DCCollector object is being deleted.  We don't
			// need it in order to finish the update.  We only keep
			// a reference to it in order to do non-essential things.

		dc_collector = NULL;
	}

	static void startUpdateCallback(bool success,Sock *sock,CondorError * /* errstack */, const std::string &trust_domain, bool should_try_token_request, void *misc_data) {
		UpdateData *ud = (UpdateData *)misc_data;

			// We got here because a nonblocking call to startCommand()
			// has called us back.  Now we will finish sending the update.

			// NOTE: it is possible that by the time we get here,
			// dc_collector has been deleted.  If that is the case,
			// dc_collector will be NULL.  We will go ahead and finish
			// the update anyway, but we will not do anything that
			// modifies dc_collector (such as saving the TCP sock for
			// future use).

		DCCollector *dc_collector = ud->dc_collector;
		if(!success) {
			char const *who = "unknown";
			if(sock) {
				who = sock->get_sinful_peer();
			}
			if (ud->m_callback_fn) {
				(*ud->m_callback_fn)(false, sock, nullptr, trust_domain, should_try_token_request, ud->m_miscdata);
			}
			dprintf(D_ALWAYS,"Failed to start non-blocking update to %s.\n",who);
			if (dc_collector) {
				dc_collector->clearAndNotifyUpdateQueue(sock, trust_domain, should_try_token_request, ud);
				dc_collector->relocate();
			}
		}
		else if(sock && !DCCollector::finishUpdate(ud->dc_collector,sock,ud->ad1,ud->ad2, ud->m_callback_fn, ud->m_miscdata)) {
			char const *who = "unknown";
			if(sock) who = sock->get_sinful_peer();
			dprintf(D_ALWAYS,"Failed to send non-blocking update to %s.\n",who);
			if (dc_collector) {
				dc_collector->clearAndNotifyUpdateQueue(sock, trust_domain, should_try_token_request, ud);
				dc_collector->relocate();
			}
		}
		else if(sock && sock->type() == Sock::reli_sock) {
			// We keep the TCP socket around for sending more updates.
			if(ud->dc_collector && ud->dc_collector->update_rsock == NULL) {
				ud->dc_collector->update_rsock = (ReliSock *)sock;
				sock = NULL;
			}
		}
		if(sock) {
			delete sock;
		}
		if (ud) {
			delete ud;
		}

			// Now that we finished sending the update, we can start sequentially sending
			// the pending updates.  We send these updates synchronously in sequence
			// because if we did it all asynchronously, we may end up with many TCP
			// connections to the collector.  Instead we send the updates one at a time
			// via a single connection.

		if (dc_collector && dc_collector->pending_update_list.size())
		{

			// Here we handle pending updates by sending them over a stashed
			// TCP socket to the collector.
			//
			while (dc_collector->update_rsock && dc_collector->pending_update_list.size())
			{
				UpdateData *ud = dc_collector->pending_update_list.front();
				dc_collector->update_rsock->encode();
					// NOTE: If there's a valid TCP socket available, we always
					// push our updates over that (even if the update requested UDP).
					// I don't think mixing TCP/UDP to the same collector is supported, so
					// I believe this shortcut acceptable.
				if (!dc_collector->update_rsock->put( ud->cmd ) ||
					!DCCollector::finishUpdate(ud->dc_collector,dc_collector->update_rsock,ud->ad1,ud->ad2,ud->m_callback_fn,ud->m_miscdata))
				{
					char const *who = "unknown";
					if(dc_collector->update_rsock) {
						who = dc_collector->update_rsock->get_sinful_peer();
					}
					dprintf(D_ALWAYS,"Failed to send update to %s.\n",who);
					delete dc_collector->update_rsock;
					dc_collector->update_rsock = NULL;
					dc_collector->relocate();

					// Notice we remove the element from the list of pending updates
					// even on failure.
				}
				delete ud;
			}

			// Here we handle pending updates in the event that we do not have
			// a stashed TCP socket to the collector.  This could occur if
			// our TCP socket to the collector was closed for some reason
			// (e.g. the collector was restarted), or it may occur in the
			// case of UDP.  Note that we just start handling the next pending
			// update here, then go back to daemonCore with a callback registered
			// to ensure we do not block in the event we need to re-establish
			// a new TCP socket.
			if (dc_collector->pending_update_list.size())
			{
				UpdateData *ud = dc_collector->pending_update_list.front();
				dc_collector->startCommand_nonblocking(ud->cmd, ud->sock_type, 20, NULL, UpdateData::startUpdateCallback, ud );
			}
		}
	}
};


void
DCCollector::clearAndNotifyUpdateQueue(Sock* sock, const std::string &trust_domain, bool should_try_token_request, UpdateData*& failed) {
	for (auto& ud : pending_update_list) {
		// Run callback function for all except for the failed (handled outside prior)
		if (ud != failed && ud->m_callback_fn) {
			(*(ud->m_callback_fn))(false, sock, nullptr, trust_domain, should_try_token_request, ud->m_miscdata);
		}

		delete ud;
		ud = nullptr;
	}

	// The failed UpdateData point will have been deleted so remove dangling pointer
	failed = nullptr;

	pending_update_list.clear();
}


bool
DCCollector::sendUDPUpdate( int cmd, ClassAd* ad1, ClassAd* ad2, bool nonblocking, StartCommandCallbackType callback_fn, void *miscdata )
{
		// with UDP it's pretty straight forward.  We always want to
		// use Daemon::startCommand() so we get all the security stuff
		// in every update.  In fact, we want to recreate the SafeSock
		// itself each time, since it doesn't seem to work if we reuse
		// the SafeSock object from one update to the next...

	dprintf( D_FULLDEBUG,
			 "Attempting to send update via UDP to collector %s\n",
			 update_destination );

	if(nonblocking) {
		UpdateData *ud = new UpdateData(cmd, Sock::safe_sock, ad1, ad2, this, callback_fn, miscdata);
		if (this->pending_update_list.size() == 1)
		{
			startCommand_nonblocking(cmd, Sock::safe_sock, 20, NULL, UpdateData::startUpdateCallback, ud);
		}
		return true;
	}

	Sock *ssock = startCommand(cmd, Sock::safe_sock, 20);

	if(!ssock) {
		newError( CA_COMMUNICATION_ERROR,
				  "Failed to send UDP update command to collector" );
		if (callback_fn) {
			(*callback_fn)(false, nullptr, nullptr, "", false, miscdata);
		}
		return false;
	}

	bool success = finishUpdate( this, ssock, ad1, ad2, callback_fn, miscdata );
	delete ssock;

	return success;
}


bool
DCCollector::sendTCPUpdate( int cmd, ClassAd* ad1, ClassAd* ad2, bool nonblocking, StartCommandCallbackType callback_fn, void *miscdata )
{
	dprintf( D_FULLDEBUG,
			 "Attempting to send update via TCP to collector %s\n",
			 update_destination );

	if( ! update_rsock ) {
			// we don't have a TCP sock for sending an update.  we've
			// got to create one.  this is a somewhat complicated
			// process, since we've got to create a ReliSock, connect
			// to the collector, and start a security session.
			// unfortunately, the only way to start a security session
			// is to send an initial command, so we'll handle that
			// update at the same time.  if the security API changes
			// in the future, we'll be able to make this code a little
			// more straight-forward...
		return initiateTCPUpdate( cmd, ad1, ad2, nonblocking, callback_fn, miscdata );
	}

		// otherwise, we've already got our socket, it's connected,
		// the security session is going, etc.  so, all we have to do
		// is send the actual update to the existing socket.  the only
		// thing we have to watch out for is if the collector
		// invalidated our cached socket, and if so, we'll have to
		// start another connection.
		// since finishUpdate() assumes we've already sent the command
		// int, and since we do *NOT* want to use startCommand() again
		// on a cached TCP socket, just code the int ourselves...
		//
		// also note we do NOT pass the callback_fn to finishUpdate here - the idea
		// is if finishUpdate fails, we turn around and try to start a new connection,
		// and we only want to invoke the callback once.  So we avoid passing the callback to
		// finishUpdate to prevent both finishUpdate and initiateUpdate from invoking the
		// callback function in the case we need to create a new connection.
	update_rsock->encode();
	if (update_rsock->put(cmd) && finishUpdate(this, update_rsock, ad1, ad2, nullptr, nullptr)) {
		if (callback_fn) {
			(*callback_fn)(true, update_rsock, nullptr, update_rsock->getTrustDomain(), update_rsock->shouldTryTokenRequest(), miscdata);
		}
		return true;
	}
	dprintf( D_FULLDEBUG,
			 "Couldn't reuse TCP socket to update collector, "
			 "starting new connection\n" );
	delete update_rsock;
	update_rsock = NULL;
	relocate();

	return initiateTCPUpdate( cmd, ad1, ad2, nonblocking, callback_fn, miscdata );
}



bool
DCCollector::initiateTCPUpdate( int cmd, ClassAd* ad1, ClassAd* ad2, bool nonblocking, StartCommandCallbackType *callback_fn, void *miscdata )
{
	if( update_rsock ) {
		delete update_rsock;
		update_rsock = NULL;
	}
	if(nonblocking) {
		UpdateData *ud = new UpdateData(cmd, Sock::reli_sock, ad1, ad2, this, callback_fn, miscdata);
			// Note that UpdateData automatically adds itself to the pending_update_list.
		if (this->pending_update_list.size() == 1)
		{
			startCommand_nonblocking(cmd, Sock::reli_sock, 20, NULL, UpdateData::startUpdateCallback, ud );
		}
		return true;
	}

	Sock *sock = startCommand(cmd, Sock::reli_sock, 20);

	if(!sock) {
		newError( CA_COMMUNICATION_ERROR,
				  "Failed to send TCP update command to collector" );
		dprintf(D_ALWAYS,"Failed to send update to %s.\n",idStr());
		if (callback_fn) {
			(*callback_fn)(false, nullptr, nullptr, "", false, miscdata);
		}
		return false;
	}
	update_rsock = (ReliSock *)sock;
	return finishUpdate( this, update_rsock, ad1, ad2, callback_fn, miscdata );
}


void
DCCollector::displayResults( void )
{
	// Write to log file the collector receiving updates.
	// Note: the gangliad does not do this, as it is misleading and
	// worries admin (gangliad supposedly only reading collectors, not updating)
	if (strcmp(get_mySubSystem()->getName(),"GANGLIAD")) {
		dprintf( D_FULLDEBUG, "Will use %s to update collector %s\n", 
			 use_tcp ? "TCP" : "UDP", updateDestination() );
	}
}


// Figure out how we're going to identify the destination for our
// updates when printing to the logs, etc. 
const char*
DCCollector::updateDestination( void )
{
	return update_destination;
}


void
DCCollector::initDestinationStrings( void )
{
	if( update_destination ) {
		free(update_destination);
		update_destination = NULL;
	}

	std::string dest;

		// Updates will always be sent to whatever info we've got
		// in the Daemon object.  So, there's nothing hard to do for
		// this... just see what useful info we have and use it. 
	if( ! _full_hostname.empty() ) {
		dest = _full_hostname;
		if (! _addr.empty()) {
			dest += ' ';
			dest += _addr;
		}
	} else {
		dest = _addr;
	}
	update_destination = strdup( dest.c_str() );
}


//
// Ad Sequence Number class methods
//

// Get a sequence number class for this classad, creating it if needed.
DCCollectorAdSeq& DCCollectorAdSequences::getAdSeq(const ClassAd & ad)
{
	AdTypes mytype = NO_AD;
	std::string name, attr;
	ad.LookupString( ATTR_NAME, name );
	ad.LookupString( ATTR_MY_TYPE, attr );
	name += "\n"; name += attr;
	if ( ! attr.empty()) { mytype = AdTypeStringToAdType(attr.c_str()); }
	ad.LookupString( ATTR_MACHINE, attr );
	name += "\n"; name += attr;

	DCCollectorAdSeqMap::iterator it = seqs.find(name);
	if (it != seqs.end()) {
		return it->second;
	}
	auto & seq = seqs[name];
	seq.setAdType(mytype);
	return seq;
}

DCCollector::~DCCollector( void )
{
	if( update_rsock ) {
		delete( update_rsock );
	}
	if( update_destination ) {
		free(update_destination);
	}

		// In case there are any nonblocking updates in progress,
		// let them know this DCCollector object is going away.
	for (std::deque<UpdateData*>::const_iterator it = pending_update_list.begin();
			it != pending_update_list.end();
			it++) {
		if (*it) {
			(*it)->DCCollectorGoingAway();
		}
	}
}

Timeslice &DCCollector::getBlacklistTimeslice()
{
	std::map< std::string, Timeslice >::iterator itr;
	std::string address = (addr() == nullptr) ? "" : addr();
	itr = blacklist.find(address);
	if( itr == blacklist.end() ) {
		Timeslice ts;

			// Blacklist this collector if last failed contact took more
			// than 1% of the time that has passed since that operation
			// started.  (i.e. if contact fails quickly, don't worry, but
			// if it takes a long time to fail, be cautious.
		ts.setTimeslice(0.01);
			// Set an upper bound of one hour for the collector to be blacklisted.
		int avoid_time = param_integer("DEAD_COLLECTOR_MAX_AVOIDANCE_TIME",3600);
		ts.setMaxInterval(avoid_time);
		ts.setInitialInterval(0);

		itr = blacklist.emplace(address,ts).first;
	}
	return itr->second;
}

bool
DCCollector::isBlacklisted() {
	Timeslice &blacklisted = getBlacklistTimeslice();
	return !blacklisted.isTimeToRun();
}

void
DCCollector::blacklistMonitorQueryStarted() {
	condor_gettimestamp( m_blacklist_monitor_query_started );
}

void
DCCollector::blacklistMonitorQueryFinished( bool success ) {
	Timeslice &blacklisted = getBlacklistTimeslice();
	if( success ) {
		blacklisted.reset();
	}
	else {
		struct timeval finished;
		condor_gettimestamp( finished );
		blacklisted.processEvent(m_blacklist_monitor_query_started,finished);

		unsigned int delta = blacklisted.getTimeToNextRun();
		if( delta > 0 ) {
			dprintf( D_ALWAYS, "Will avoid querying collector %s %s for %us "
			         "if an alternative succeeds.\n",
			         name(),
					 addr(),
			         delta );
		}
	}
}

void
DCCollector::relocate() {
	dprintf( D_HOSTNAME, "DCCollector::relocate(%s)\n", this->constructorName.c_str() );

	// This is awful, but easier than trying to unknot the logic that
	// EXCEPT()s when trying to call locate() a second time.
	DCCollector self( this->constructorName.c_str(), up_type );
	self.locate();

	// The assignment operator for DCCollector doesn't call the assignment
	// operator for Daemon[Client], its superclass, so it doesn't actually
	// do anything useful.
	this->theRealDeepCopy(self);
}

void
DCCollector::theRealDeepCopy(const DCCollector & other) {
	Daemon::deepCopy(other);
	deepCopy(other);
}
