/*
** CBLM v3
** $Id: thread.c,v 1.25 2007/12/09 06:22:33 sella Exp $
** Copyright (c) 2004 James M. Sella. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
** 
** Cheap Bastard Latency Monitor v3
*/

#include "thread.h"
#include "cblm.h"
#include "probe.h"
#include "prefs.h"
#include "data.h"
#include "db.h"
#include "util.h"
#include "data_hash.h"

/* Global variables */
extern struct args_t args;
extern struct prefs_t prefs;
extern struct probe_t *probes;
extern int running, restart;
extern pthread_mutex_t probe_mutex, packet_list_mutex, db_mutex, hash_mutex;

extern MYSQL mysql;

extern struct packet_list_t *packets;
extern struct packet_list_t **pl_end;
extern long p_count;

void transmit_thread() {
	struct probe_t *curp;
	struct tos_t *curt;
	struct protoent *protocol;
	long remaining, pending;
	struct timeval now;
	long start, end, slices_past;
	double slice_packets, should_be_done;
	struct timespec timer;
	long slices;
	unsigned int set = 0;
	time_t t;
	int s = 0, no_probes = 0, optval;

	if (args.debug == 0 && fileno(stdin) != -1) fclose(stdin);
	if (args.debug == 0 && fileno(stdout) != -1) fclose(stdout);
	if (args.debug == 0 && fileno(stderr) != -1) fclose(stderr);

	/* Setup transmit socket */
	protocol = (struct protoent*) getprotobyname("udp");
	if (!protocol) {
		syslog(LOG_CRIT, "Failed to get protocol. Verify that /etc/services is readable from current root directory.");

		running = 0;
		goto exit;
	}

	if ((s = socket(AF_INET, SOCK_DGRAM, protocol->p_proto)) < 0) {
		syslog(LOG_CRIT, "Failed to open socket: %s", strerror(errno));
		goto exit;
	}

#ifdef SO_SNDBUF
	optval = SNDBUF_SIZE;
	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*) &optval, sizeof(optval)) < 0) {
		syslog(LOG_CRIT, "Failed to setsockopt SO_SNDBUF to %d: %s", optval, strerror(errno));
	}
#endif

#ifdef IP_TTL
	optval = prefs.ttl;
	if (setsockopt(s, IPPROTO_IP, IP_TTL, (void*) &optval, sizeof(optval)) < 0) {
		syslog(LOG_CRIT, "Failed to setsockopt IP_TTL to %d: %s", optval, strerror(errno));
	}
#endif

#ifdef IP_DONTFRAGMENT
	if (prefs.fragment == 1) {
		optval = 0;
		if (setsockopt(s, IPPROTO_IP, IP_DONTFRAGMENT, (void*) &optval, sizeof(optval)) < 0) {
			syslog(LOG_CRIT, "Failed to setsockopt IP_DONTFRAGMENT to %d: %s", optval, strerror(errno));
		}
	}
#endif

	/* Slice sleep time. */
	timer.tv_sec = 0;
	timer.tv_nsec = 1000000 * 1000 / SLICES_PER_SEC;

	while (running) {
		/* Update probe list. */
		read_probes();

		/* If there are no probes, wait and try again. */
		if (probes == NULL) {
			no_probes = 1;
			syslog(LOG_INFO, "No remote probe(s) available. Retry in %lu seconds.", NOPROBES_WAIT);

			t = time(NULL) + NOPROBES_WAIT;
			while (running && t >= time(NULL)) {
				usleep(250000); /* 1/4 sec */
			};

			continue;
		}

		if (no_probes == 1) {
			no_probes = 0;
			syslog(LOG_INFO, "Remote probe(s) found. Transmit cycle beginning.");
		}

		pthread_mutex_lock(&probe_mutex);

		/* Initialize data for transmit cycle */
		gettimeofday(&now, NULL);
		start = tvtousec(now);
		end = start + CYCLE_USEC;
		slice_packets = (double) prefs.rate / CYCLE_SECONDS / SLICES_PER_SEC;
		remaining = prefs.rate;
		slices = 0;

		if (args.debug >= DEBUG_INTERNAL) {
			syslog(LOG_INFO, "Transmit cycle (%lu sec) sending %0.5f pkts/slice over %lu slices.", CYCLE_SECONDS, slice_packets, SLICES_PER_CYCLE);
		}

		if (args.debug >= DEBUG_ERROR && (probes == NULL || prefs.tos == NULL)) {
			syslog(DEBUG_ERROR, "No probes and/or ToS found. Nothing will be sent. probes == %08x, prefs.tos == %08x", probes, prefs.tos);
		}

		/* Transmit cycle loop. */
		while (remaining > 0 && running) {
			/* How many should we transmit this cycle? */
			slices_past = (CYCLE_USEC - (end - tvtousec(now))) / (1000000 / SLICES_PER_SEC);
			should_be_done = (slices_past * slice_packets);
			pending = should_be_done - (prefs.rate - remaining);

			if (pending > 0) {
				/* Loop for probes. */
				for (curp = probes; curp != NULL; curp = curp->next) {
					/* Loop though ToS. */
					for (curt = prefs.tos; curt != NULL; curt = curt->next) {
						transmit_packet(s, &(curp->ip), pending, curt, set);
					}
				}

				/* Subtract what we transmitted. */
				remaining -= pending;
			}

			/* Update now to see if the cycle is complete. */
			gettimeofday(&now, NULL);

			/* Sleep */
			nanosleep(&timer, NULL);

			slices++;
		}

		if (args.debug >= DEBUG_INTERNAL && running) {
			syslog(LOG_INFO, "Transmit cycle complete in %0.4f sec using %lu of %lu slices.", (tvtousec(now) - start) / 1000000.0, slices, SLICES_PER_CYCLE);
		}

		pthread_mutex_unlock(&probe_mutex);

		/* Increament to the next set. */
		set++;

		/* Update preferences. */
		if (running && prefs.last != last_prefs()) {
			if (args.debug >= DEBUG_NOTICE) {
				syslog(LOG_INFO, "Preferences have been updated. Restarting.\n");
			}
			running = 0;
			restart = 1;
		}
	}

exit:
	if (s > 0) {
		close(s);
	}

	pthread_exit(NULL);
}

void receive_thread() {
	int s, n, c, fd, optval;
	pid_t pid;
	fd_set fdset, fds;
	struct timeval timeout;

	if (args.debug == 0 && fileno(stdin) != -1) fclose(stdin);
	if (args.debug == 0 && fileno(stdout) != -1) fclose(stdout);
	if (args.debug == 0 && fileno(stderr) != -1) fclose(stderr);

	/* Bind our socket */
	if ((s = bind_port(&fdset)) == -1) {
		running = 0;

		goto exit;
	}
	n = s + 1;

	while (running) {
		/* Copy our fdset, select screws with it. */
		FD_ZERO(&fds);
		bcopy(&fdset, &fds, sizeof(fd_set));

		/* Use a 3/4 sec timeout. */
		timeout.tv_sec = 0;
		timeout.tv_usec = 750000;

		/* Wait for a connection. */
		c = select(n, &fds, 0, 0, &timeout);
		if (c == -1) {
			syslog(LOG_CRIT, "select() %s", strerror(errno));
			return;
		} else if (c > 0) {
			/* Read incoming packets */
			receive_packet(s);
		}
	}

	/* Close ports */
	if (s > 0) {
		close(s);
	}

exit:
	pthread_exit(NULL);
}

void data_thread() {
	struct timespec timer;
	struct packet_t *packet;
	struct packet_list_t *pl_ptr;
	time_t tm = 0;

	if (args.debug == 0 && fileno(stdin) != -1) fclose(stdin);
	if (args.debug == 0 && fileno(stdout) != -1) fclose(stdout);
	if (args.debug == 0 && fileno(stderr) != -1) fclose(stderr);

	/* Initialize our hash */
	pthread_mutex_lock(&hash_mutex);
	hash_init();
	pthread_mutex_unlock(&hash_mutex);

	/* Slice sleep time. */
	timer.tv_sec = 0;
	timer.tv_nsec = 1000000 * 1000 / SLICES_PER_SEC;

	while (running) {
		if (time(NULL) > tm) {
			tm = time(NULL) + (prefs.maxage / 3);
			register_probe();
		}

		while (packets) {
			/* Retrieve packet from the packet_list. */
			pthread_mutex_lock(&packet_list_mutex);

			/* Grab reference to current packet. */
			pl_ptr = packets;
			packet = &(pl_ptr->packet);

			/* Move to the next packet. */
			packets = packets->next;

			if (packets == NULL) {
				pl_end = &packets;
			}

			/* Decrease packet count by 1 */
			p_count--;

			pthread_mutex_unlock(&packet_list_mutex);

			/* Process the packet. */
			process_packet(packet);

			/* Free the node. */
			free(pl_ptr);
			pl_ptr = NULL;
		}

		/* Sleep */
		if (p_count < 1) {
			nanosleep(&timer, NULL);
		}
	}

	/* Free our hash */
	pthread_mutex_lock(&hash_mutex);
	hash_free();
	pthread_mutex_unlock(&hash_mutex);

	pthread_exit(NULL);
}

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