/*
** CBLM v3
** $Id: probe.c,v 1.22 2011/10/27 20:57:52 sella Exp $
** Copyright (c) 2004-2007 James M. Sella. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
** 
** Cheap Bastard Latency Monitor v3
*/

static const char rcsid[] = "$Id: probe.c,v 1.22 2011/10/27 20:57:52 sella Exp $";

#include "probe.h"
#include "transmit.h"

#include <stdio.h>
#include <stdlib.h>
#include <mysql/mysql.h>

/* Global variables */
extern struct args_t args;
extern struct prefs_t prefs;
extern pthread_mutex_t probe_mutex, db_mutex;

struct probe_t *probes = NULL;
uint64_t probe_crc = 0;
uint32_t probe_count = 0;

int register_probe() {
	int ret_val = 1;
	char sql[1024];
	char buf[INET_ADDRSTRLEN];
	MYSQL_RES *res;
	MYSQL_ROW row;

	pthread_mutex_lock(&db_mutex);

	if (db_isconnected() != 1) {
		if (args.debug >= DEBUG_INTERNAL) {
			syslog(LOG_DEBUG, "Skipping register_probe() - no database connection.");
		}

		goto exit;
	}

	if (db_query(&res, "LOCK TABLE probes WRITE") != 0) {
		syslog(LOG_CRIT, "register_probe() failed to lock probes tables.");

		goto exit;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "SELECT id, UNIX_TIMESTAMP(updated) updated FROM probes WHERE ip = %lu", ntohl(args.source.s_addr));
	if (db_query(&res, sql) != 0) {
		syslog(LOG_CRIT, "register_probe() failed select last updated time for probe.");

		goto cleanup;
	}

	if (mysql_num_rows(res) < 1) {
		/* We currently aren't registered, do an insert. */
		snprintf(sql, sizeof(sql), "INSERT INTO probes (id, updated, started, ip, name, version, location, detail) VALUES (NULL, NULL, NULL, %lu, '%s', '%s', '%s', '%s')", ntohl(args.source.s_addr), args.name, CBLM_VERSION, (args.location == NULL) ? "" : args.location, (args.description == NULL) ? "" : args.description);

		if (args.debug >= DEBUG_INTERNAL) {
			syslog(LOG_INFO, "Registering probe %s with name %s.", inet_ntop(AF_INET, &(args.source), buf, sizeof(buf)), args.name);
		}
	} else {
		/* Already registered, update as needed */
		row = mysql_fetch_row(res);

		if (row[1] != NULL && atoi(row[1]) < 1) { /* Register old probe */
			snprintf(sql, sizeof(sql), "UPDATE probes SET updated = NULL, started = NULL, name = '%s',version = '%s', location = '%s', detail = '%s' WHERE id = %s", args.name, CBLM_VERSION, (args.location == NULL) ? "" : args.location, (args.description == NULL) ? "" : args.description, row[0]);

			if (args.debug >= DEBUG_INTERNAL) {
				syslog(LOG_INFO, "Initial refresh of probe %s registration with name %s.", inet_ntop(AF_INET, &(args.source), buf, sizeof(buf)), args.name);
			}
		} else { /* Updating timestamp on registeration */
			snprintf(sql, sizeof(sql), "UPDATE probes SET updated = NULL WHERE id = %s", row[0]);

			if (args.debug >= DEBUG_INTERNAL) {
				syslog(LOG_INFO, "Refreshing probe %s registration.", inet_ntop(AF_INET, &(args.source), buf, sizeof(buf)));
			}
		}

	}
	mysql_free_result(res);

	if (db_query(&res, sql) != 0) {
		syslog(LOG_CRIT, "register_probe() failed to update updated time for probe.");

		goto cleanup;
	}
	mysql_free_result(res);

	/* Successful. */
	ret_val = 0;

cleanup:
	if (db_query(&res, "UNLOCK TABLES") == 0) {
		mysql_free_result(res);
	} else {
		syslog(LOG_CRIT, "register_probe() failed to unlock probes tables.");
	}

exit:
	pthread_mutex_unlock(&db_mutex);

	return ret_val;
}

int unregister_probe() {
	int ret_val = 1;
	char sql[1024];
	char buf[INET_ADDRSTRLEN];
	MYSQL_RES *res;
	MYSQL_ROW row;

	pthread_mutex_lock(&db_mutex);

	if (db_isconnected() != 1) {
		if (args.debug >= DEBUG_INTERNAL) {
			syslog(LOG_DEBUG, "Skipping unregister_probe() - no database connection.");
		}

		goto exit;
	}

	if (args.debug >= DEBUG_INTERNAL) {
		syslog(LOG_INFO, "Unregistering probe %s with name %s", inet_ntop(AF_INET, &(args.source), buf, sizeof(buf)), args.name);
	}

	if (db_query(&res, "LOCK TABLE probes WRITE") != 0) {
		syslog(LOG_CRIT, "unregister_probe() failed to lock probes tables.");

		goto exit;
	}
	mysql_free_result(res);

	/* Set updated to 0, forcing it to be expired. */
	snprintf(sql, sizeof(sql), "UPDATE probes SET updated = 0 WHERE ip = %lu", ntohl(args.source.s_addr));
	if (db_query(&res, sql) != 0) {
		syslog(LOG_CRIT, "unregister_probe() failed to update updated time for probe.");

		goto cleanup;
	}
	mysql_free_result(res);

	/* Successful. */
	ret_val = 0;

cleanup:
	if (db_query(&res, "UNLOCK TABLES") == 0) {
		mysql_free_result(res);
	} else {
		syslog(LOG_CRIT, "unregister_probe() failed to unlock probes tables.");
	}

exit:
	pthread_mutex_unlock(&db_mutex);

	return ret_val;
}

int read_probes() {
	int ret_val = 1;
	char sql[1024];
	MYSQL_RES *res;
	MYSQL_ROW row;
	struct in_addr in;
	struct probe_t **cur, **prev;
	unsigned long crc;

	pthread_mutex_lock(&probe_mutex);
	pthread_mutex_lock(&db_mutex);

	if (db_isconnected() != 1) {
		if (args.debug >= DEBUG_INTERNAL) {
			syslog(LOG_DEBUG, "Skipping read_probe() - no database connection.");
		}

		goto exit;
	}

	if (args.debug >= DEBUG_INTERNAL) {
		syslog(LOG_DEBUG, "Checking for updates to the list of probes..");
	}

	if (db_query(&res, "LOCK TABLE probes WRITE") != 0) {
		syslog(LOG_CRIT, "read_probes() failed to lock probes tables.");

		goto exit;
	}
	mysql_free_result(res);

	/* FIXME: Using the SUM() here isn't suffient. If the number of probes doesn't change, it doesn't always notice a new probe. */
	snprintf(sql, sizeof(sql), "SELECT COUNT(id) probes, SUM(started) AS crc FROM probes WHERE updated > SUBTIME(NOW(),SEC_TO_TIME(%lu))", prefs.maxage);
	if (db_query(&res, sql) != 0) {
		syslog(LOG_CRIT, "read_probes() failed to select crc from probes tables.");

		goto cleanup;
	}
 
	/* Verify we got a row. */
	if (mysql_num_rows(res) < 1) {
		mysql_free_result(res);

		goto cleanup;
	}

	/* Verify the result isn't NULL. At least one probe must be registered. */
	row = mysql_fetch_row(res);
	if (row[0] == NULL || row[1] == NULL) {
		mysql_free_result(res);

		goto cleanup;
	}

	/* Calculate the crc. */
	crc = (strtoul(row[0], NULL, 10) / 2) + (strtoul(row[1], NULL, 10) / 7);

	/* Capture probe count */
	probe_count = atoi(row[0]);

	/* Free result */
	mysql_free_result(res);

	if (crc != probe_crc) {
		if (args.debug >= DEBUG_INTERNAL) {
			syslog(LOG_DEBUG, "Update available. Rebuilding probe list.");
		}

		/* Release old probes struct. */
		free_probes_nolock();

		/* Change crc to something else. */
		probe_crc--;

		/* Build probes structure. */
		snprintf(sql, sizeof(sql), "SELECT ip, name, updated FROM probes WHERE ip != %lu AND updated > SUBTIME(NOW(),SEC_TO_TIME(%lu))", ntohl(args.source.s_addr), prefs.maxage);
		if (db_query(&res, sql) != 0) {
			syslog(LOG_CRIT, "read_probes() failed to select updated time from probes table.");

			goto cleanup;
		} else if (mysql_num_rows(res) > 0) {
			/* Build probes structure. */
			cur = &probes;
			while (row = mysql_fetch_row(res)) {
				if (args.debug >= DEBUG_INTERNAL) {
					in.s_addr = htonl(strtoul(row[0], NULL, 10));

					syslog(LOG_DEBUG, "probe: %s (%s), updated: %s", inet_ntoa(in), row[1], row[2]);
				}

				in.s_addr = htonl(strtoul(row[0], NULL, 10));
				*cur = malloc(sizeof(struct probe_t));
				(*cur)->ip.s_addr = htonl(strtoul(row[0], NULL, 10));
				strncpy((*cur)->name, row[1], sizeof((*cur)->name));
				(*cur)->next = NULL;
				cur = &(*cur)->next;
			}

			/* Update the CRC. */
			probe_crc = crc;
		}
		mysql_free_result(res);
	} else {
		if (args.debug >= DEBUG_INTERNAL) {
			syslog(LOG_DEBUG, "No updates to probe list.");
		}
	}

	/* Successful. */
	ret_val = 0;

cleanup:
	if (db_query(&res, "UNLOCK TABLES") == 0) {
		mysql_free_result(res);
	} else {
		syslog(LOG_CRIT, "read_probes() failed to unlock probes tables.");
	}

exit:
	pthread_mutex_unlock(&db_mutex);
	pthread_mutex_unlock(&probe_mutex);

	return ret_val;
}

int is_probe(const char* ip) {
	char sql[1024];
	MYSQL_RES* res;
	int ret_val = -1;

	/* Verify destination is still a registered probe. */
	snprintf(sql, sizeof(sql), "SELECT id FROM probes WHERE ip = INET_ATON('%s') AND updated > 0 AND SUBTIME(NOW(),SEC_TO_TIME(%lu)) > started LIMIT 1", ip, prefs.maxage);
	if (db_query(&res, sql) == 0) {
		ret_val = mysql_num_rows(res);

		mysql_free_result(res);
	} else {
		syslog(LOG_CRIT, "is_proba() failed to select id from probes.");
	}

	return ret_val;
}

void free_probes() {
	pthread_mutex_lock(&probe_mutex);
	free_probes_nolock();
	pthread_mutex_unlock(&probe_mutex);
}

void free_probes_nolock() {
	struct probe_t *cur, *prev;

	cur = probes;
	while (cur != NULL) {
		prev = cur;
		cur = cur->next;
		free(prev);
		prev = NULL;
	}
	probes = NULL;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim: noet ts=3 sw=3
*/
