/*
 * Copyright (c) 2003, 2004 Niels Provos <provos@citi.umich.edu>
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <sys/types.h>
#include <sys/param.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/ioctl.h>
#include <sys/tree.h>
#include <sys/queue.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <sha1.h>
#ifdef HAVE_ASSERT_U
#include <assert.h>
#else
#define assert(x)
#endif

#include <event.h>
#include <pcap.h>
#include <dnet.h>
#include <zlib.h>

#include "honeyd.h"
#include "hooks.h"
#include "tagging.h"
#include "osfp.h"
#include "stats.h"

int make_socket(int (*f)(int, const struct sockaddr *, socklen_t), int type,
    char *, uint16_t);
static void stats_make_fd(struct addr *, u_short);
static void stats_activate(struct stats *stats);
static void stats_deactivate(struct stats *stats);

/* Many static variables.  We don't like them */

/* We might have other consumers of our created records */
struct statscb {
	TAILQ_ENTRY(statscb) next;

	int (*cb)(const struct record *, void *);
	void *cb_arg;
};

struct statscontrol {
	char *user_name;
	char *user_key;
	struct addr *user_dst;
	u_short user_port;
	int stats_fd;
	struct event ev_send;

	struct measurement measurement;
	struct timeval tv_start;
	struct event ev_measure;
	struct evbuffer *evbuf_measure;
	struct evbuffer *evbuf_tmp;

	struct hmac_state hmac;

	TAILQ_HEAD(statscbq, statscb) callbacks;

	TAILQ_HEAD(statspackets, stats_packet) send_queue;
	TAILQ_HEAD(statsqueue, stats) active_stats;
	SPLAY_HEAD(statstree, stats) all_stats;
};

struct statscontrol sc;


static int
compare(struct stats *a, struct stats *b)
{
	return (conhdr_compare(&a->conhdr, &b->conhdr));
}

SPLAY_PROTOTYPE(statstree, stats, node, compare);
SPLAY_GENERATE(statstree, stats, node, compare);

/* Initialize the message authentication code that we use for signing */

void
hmac_init(struct hmac_state *hmac, const char *key)
{
	int i;

	memset(hmac->ipad, 0x36, sizeof(hmac->ipad));
	memset(hmac->opad, 0x5c, sizeof(hmac->opad));

	for (i = 0; i < strlen(key) + 1 && i < sizeof(hmac->ipad); i++) {
		hmac->ipad[i] ^= key[i];
		hmac->opad[i] ^= key[i];
	}

	SHA1Init(&hmac->ictx);
	SHA1Update(&hmac->ictx, hmac->ipad, sizeof(hmac->ipad));
	SHA1Init(&hmac->octx);
	SHA1Update(&hmac->octx, hmac->opad, sizeof(hmac->opad));
}

void
hmac_sign(const struct hmac_state *hmac, u_char *dst, size_t dstlen,
    const void *data, size_t len)
{
	SHA1_CTX ctx;
	u_char digest[SHA1_DIGESTSIZE];

	assert(dstlen <= SHA1_DIGESTSIZE);

	ctx = hmac->ictx;
	SHA1Update(&ctx, data, len);
	SHA1Final(digest, &ctx);

	ctx = hmac->octx;
	SHA1Update(&ctx, digest, sizeof(digest));
	SHA1Final(digest, &ctx);

	memcpy(dst, digest, dstlen);
}

int
hmac_verify(const struct hmac_state *hmac, u_char *sign, size_t signlen,
    const void *data, size_t len)
{
	u_char digest[SHA1_DIGESTSIZE];

	assert(signlen <= SHA1_DIGESTSIZE);

	hmac_sign(hmac, digest, sizeof(digest), data, len);

	return (memcmp(digest, sign, signlen) == 0);
}

/* Per packet compression */

void
stats_compress(struct evbuffer *evbuf)
{
	static struct evbuffer *tmp;
	static z_stream stream;
	static u_char buffer[2048];
	int status;
	
	/* Initialize buffer and compressor */
	if (tmp == NULL) {
		tmp = evbuffer_new();
		deflateInit(&stream, 9);
	}
	deflateReset(&stream);

	stream.next_in = EVBUFFER_DATA(evbuf);
	stream.avail_in = EVBUFFER_LENGTH(evbuf);

	do {
		stream.next_out = buffer;
		stream.avail_out = sizeof(buffer);

		status = deflate(&stream, Z_FULL_FLUSH);

		switch (status) {
		case Z_OK:
			/* Append compress data to buffer */
			evbuffer_add(tmp, buffer,
			    sizeof(buffer) - stream.avail_out);
			break;
		default:
			errx(1, "%s: deflate failed with %d",
			    __func__, status);
			/* NOTREACHED */
		}
	} while (stream.avail_out == 0);

	evbuffer_drain(evbuf, EVBUFFER_LENGTH(evbuf));
	evbuffer_add_buffer(evbuf, tmp);
}

int
stats_decompress(struct evbuffer *evbuf)
{
	static struct evbuffer *tmp;
	static z_stream stream;
	static u_char buffer[2048];
	int status, done = 0;
	
	/* Initialize buffer and compressor */
	if (tmp == NULL) {
		tmp = evbuffer_new();
		inflateInit(&stream);
	}
	inflateReset(&stream);

	stream.next_in = EVBUFFER_DATA(evbuf);
	stream.avail_in = EVBUFFER_LENGTH(evbuf);

	do {
		stream.next_out = buffer;
		stream.avail_out = sizeof(buffer);

		status = inflate(&stream, Z_FULL_FLUSH);

		switch (status) {
		case Z_OK:
			/* Append compress data to buffer */
			evbuffer_add(tmp, buffer,
			    sizeof(buffer) - stream.avail_out);
			break;

		case Z_BUF_ERROR:
			done = 1;
			break;

		default:
			warnx("%s: inflate failed with %d", __func__, status);
			return (-1);
		}
	} while (!done);

	evbuffer_drain(evbuf, EVBUFFER_LENGTH(evbuf));
	evbuffer_add_buffer(evbuf, tmp);

	return (0);
}

/* Quick shingling */

/*
 * We want to compute hashes over blocks that can potentially change,
 * so we use shingling; see rsync or lbfs.
 */

static void
stats_shingle_data(struct stats *stats)
{
	uint16_t hash = 0;

	while (EVBUFFER_LENGTH(stats->evbuf) >= SHINGLE_MIN) {
		u_char *data = EVBUFFER_DATA(stats->evbuf);
		int i;

		/* So, we are wasting some time here, but that's alright */
		for (i = SHINGLE_MIN;
		    i < EVBUFFER_LENGTH(stats->evbuf) - 4; i++) {
			if (i >= SHINGLE_MAX)
				break;
			hash = ((~data[i] << 8 | data[i+1]) +
			    (data[i + 3] << 8 | ~data[i+2])) % 213;
			if (hash == 0)
				break;
		}

		/* If we run out of data, then we just return */
		if (hash && i < SHINGLE_MAX)
			return;

		record_add_hash(&stats->hashes, data, i);
		evbuffer_drain(stats->evbuf, i);

		stats_activate(stats);
	}
}

/* Adds a regular timeout at which stats are sent off to a monitor */

static void
stats_measure_timeout(void)
{
	struct timeval tv, now;
	uint32_t diff_ms;

	gettimeofday(&now, NULL);
	timersub(&now, &sc.tv_start, &now);
	diff_ms = (now.tv_sec * 1000) + (now.tv_usec / 1000);
	diff_ms %= (STATS_MEASUREMENT_INTERVAL * 1000);
	diff_ms = (STATS_MEASUREMENT_INTERVAL * 1000) - diff_ms;

	tv.tv_sec = diff_ms / 1000;
	tv.tv_usec = diff_ms * 1000;

	evtimer_add(&sc.ev_measure, &tv);
}


static void
stats_add_timeout(struct stats *stats)
{
	struct timeval tv;

	timerclear(&tv);
	tv.tv_sec = STATS_TIMEOUT;
	evtimer_add(&stats->ev_timeout, &tv);
}

static struct stats *
stats_find(const struct tuple *conhdr)
{
	struct stats tmp, *res;
	tmp.conhdr = *conhdr;
	res = SPLAY_FIND(statstree, &sc.all_stats, &tmp);

	if (res != NULL)
		stats_add_timeout(res);

	return (res);
}

static void
stats_activate(struct stats *stats)
{
	if (stats->isactive)
		return;
	stats->isactive = 1;
	TAILQ_INSERT_TAIL(&sc.active_stats, stats, next);
}

static void
stats_reactivate(struct stats *stats)
{
	if (stats->isactive)
		return;
	stats->isactive = 1;
	TAILQ_INSERT_HEAD(&sc.active_stats, stats, next);
}

static void
stats_deactivate(struct stats *stats)
{
	if (!stats->isactive)
		return;

	/* Once it has been deactivate, it is not new any longer */
	stats->record.state &= ~RECORD_STATE_NEW;

	stats->isactive = 0;
	TAILQ_REMOVE(&sc.active_stats, stats, next);
}

static void
stats_ready_cb(int fd, short what, void *arg)
{
	struct stats_packet *tmp;

	tmp = TAILQ_FIRST(&sc.send_queue);
	TAILQ_REMOVE(&sc.send_queue, tmp, next);
	if (what != EV_TIMEOUT) {
		syslog(LOG_DEBUG, "writing stats of length %d",
		    EVBUFFER_LENGTH(tmp->evbuf));
		if (evbuffer_write(tmp->evbuf, fd) == -1) {
			syslog(LOG_WARNING,
			    "remote stats daemon unreachable: %m");
			close(fd);
			stats_make_fd(sc.user_dst, sc.user_port);
		}
	}

	/* Free the entry */
	evbuffer_free(tmp->evbuf);
	free(tmp);

	if (TAILQ_FIRST(&sc.send_queue) != NULL) {
		struct timeval tv;
		timerclear(&tv);
		tv.tv_sec = STATS_SEND_TIMEOUT;
		event_add(&sc.ev_send, &tv);
	}
}

static void
stats_prepare_send(struct evbuffer *evbuf)
{
	struct timeval tv;
	struct stats_packet *tmp;

	assert(sc.stats_fd != -1);
	
	if ((tmp = calloc(1, sizeof(struct stats_packet))) == NULL)
		err(1, "%s: calloc", __func__);

	tmp->evbuf = evbuf;
	TAILQ_INSERT_TAIL(&sc.send_queue, tmp, next);

	timerclear(&tv);
	tv.tv_sec = STATS_SEND_TIMEOUT;
	event_add(&sc.ev_send, &tv);
}

static void
stats_package_measurement()
{
	struct evbuffer *evbuf;
	u_char digest[SHA1_DIGESTSIZE];

	/* Do not send any file data when we don't have a collector defined */
	if (sc.stats_fd == -1)
		return;
	
	if ((evbuf = evbuffer_new()) == NULL)
		err(1, "%s: evbuffer_new", __func__);

	/* Compress the measured data */
	stats_compress(sc.evbuf_measure);

	/* Sign the data - at this point, we could use compression */
	hmac_sign(&sc.hmac, digest, sizeof(digest),
	    EVBUFFER_DATA(sc.evbuf_measure),
	    EVBUFFER_LENGTH(sc.evbuf_measure));

	/* Create the signed buffer */
	tag_marshal_string(evbuf, SIG_NAME, sc.user_name);
	tag_marshal(evbuf, SIG_DIGEST, digest, sizeof(digest));
	tag_marshal(evbuf, SIG_COMPRESSED_DATA,
	    EVBUFFER_DATA(sc.evbuf_measure),
	    EVBUFFER_LENGTH(sc.evbuf_measure));

	stats_prepare_send(evbuf);
}

void
measurement_marshal(struct evbuffer *evbuf, struct measurement *m)
{
	tag_marshal_int(sc.evbuf_measure, M_COUNTER, m->counter);
	tag_marshal_timeval(sc.evbuf_measure, M_TV_START, &m->tv_start);
	tag_marshal_timeval(sc.evbuf_measure, M_TV_END, &m->tv_end);
}

/*
 * Packages up the measured data and sents it to a collector.
 */

static void
stats_measure_cb(int fd, short what, void *arg)
{
	struct stats *stats;
	struct statscb *statscb;
	
	/* Schedule a new timeout */
	stats_measure_timeout();

	gettimeofday(&sc.measurement.tv_end, NULL);

	while ((stats = TAILQ_FIRST(&sc.active_stats)) != NULL) {
		evbuffer_drain(sc.evbuf_measure, -1);
		sc.measurement.counter++;
		measurement_marshal(sc.evbuf_measure, &sc.measurement);
		while ((stats = TAILQ_FIRST(&sc.active_stats)) != NULL &&
		    EVBUFFER_LENGTH(sc.evbuf_measure) < STATS_MAX_SIZE) {
			struct hash *hash;
			int i;

			/* 
			 * If the object is going to be deleted and we still
			 * have some unhashed data, we are going to hash it
			 * now.
			 */
			if (stats->needelete &&
			    EVBUFFER_LENGTH(stats->evbuf) >= SHINGLE_MIN) {
				record_add_hash(&stats->hashes,
				    EVBUFFER_DATA(stats->evbuf),
				    EVBUFFER_LENGTH(stats->evbuf));
				evbuffer_drain(stats->evbuf,
				    EVBUFFER_LENGTH(stats->evbuf));
			}

			/* 
			 * Add hashes to record, but limit to a
			 * reasonable number, so that we do not create
			 * too big a stats packet.
			 */
			for (i = 0, hash = TAILQ_FIRST(&stats->hashes);
			    i < STATS_MAX_HASHES && hash != NULL;
			    i++, hash = TAILQ_FIRST(&stats->hashes)) {
				TAILQ_REMOVE(&stats->hashes, hash, next);
				TAILQ_INSERT_TAIL(&stats->record.hashes, hash,
				    next);
			}

			/*
			 * Check if we have any external consumers of
			 * our records.
			 */
			TAILQ_FOREACH(statscb, &sc.callbacks, next) {
				if ((*statscb->cb)(&stats->record,
					statscb->cb_arg) == 1)
					break;
			}
			
			/*
			 * Add to temporary buffer first, so that we can 
			 * check our size contraints.
			 */

			evbuffer_drain(sc.evbuf_tmp, -1);
			tag_marshal_record(sc.evbuf_tmp, M_RECORD,
			    &stats->record);

			/* Remove data that we have reported */
			record_remove_hashes(&stats->record.hashes);
			stats->record.bytes = 0;

			/* Toggles the new state flag */
			stats_deactivate(stats);

			/*
			 * If there are still hashes left in the stats
			 * object, we need to reactivate it, so that the
			 * next round, we get the rest.
			 */
			if (TAILQ_FIRST(&stats->hashes) != NULL)
				stats_reactivate(stats);

			/* 
			 * If the entry is no longer used, we need to remove
			 * it after we reported it's data.
			 */
			if (stats->needelete && !stats->isactive)
				stats_free(stats);

			if (EVBUFFER_LENGTH(sc.evbuf_measure) +
			    EVBUFFER_LENGTH(sc.evbuf_tmp) >= STATS_MAX_SIZE) {
				/* Package up current packet */
				stats_package_measurement();

				/*
				 * Now clear the buffer and prepare it for
				 * more stats.
				 */
				evbuffer_drain(sc.evbuf_measure, -1);
				measurement_marshal(sc.evbuf_measure,
				    &sc.measurement);
			}

			evbuffer_add_buffer(sc.evbuf_measure, sc.evbuf_tmp);
		}

		stats_package_measurement();
	}

	/* Start the next measuring period */
	sc.measurement.tv_start = sc.measurement.tv_end;
	timerclear(&sc.measurement.tv_end);
}

static void
stats_timeout_cb(int fd, short what, void *arg)
{
	struct stats *stats = arg;

	stats_free(stats);
}

struct stats *
stats_new(const struct tuple *conhdr)
{
	struct stats *stats;

	syslog(LOG_DEBUG, "Creating new stats buffer for %s",
	    honeyd_contoa(conhdr));

	assert(stats_find(conhdr) == NULL);
	if ((stats = calloc(1, sizeof(struct stats))) == NULL)
		err(1, "%s: calloc", __func__);

	TAILQ_INIT(&stats->hashes);
	stats->conhdr = *conhdr;

	record_fill(&stats->record, conhdr);

	if ((stats->evbuf = evbuffer_new()) == NULL)
		err(1, "%s: evbuffer_new", __func__);

	evtimer_set(&stats->ev_timeout, stats_timeout_cb, stats);
	stats_add_timeout(stats);

	stats->record.state = RECORD_STATE_NEW;

	SPLAY_INSERT(statstree, &sc.all_stats, stats);
	stats_activate(stats);

	return (stats);
}

void
record_add_hash(struct hashq *hashes, void *data, size_t len)
{
	struct hash *hash, *tmp;
	u_char digest[SHA1_DIGESTSIZE];
	SHA1_CTX ctx;
	int i;

	SHA1Init(&ctx);
	SHA1Update(&ctx, data, len);
	SHA1Final(digest, &ctx);

	if ((hash = calloc(1, sizeof(struct hash))) == NULL)
		err(1, "%s: calloc", __func__);

	/* We just xor the overlap together */
	for (i = 0; i < sizeof(digest); i++)
		hash->digest[i % SHINGLE_SIZE] ^= digest[i];

	/* This is really slow, but maybe it's not that bad */
	TAILQ_FOREACH(tmp, hashes, next) {
		if (memcmp(tmp->digest, hash->digest, SHINGLE_SIZE) == 0)
			break;
	}

	if (tmp == NULL)
		TAILQ_INSERT_TAIL(hashes, hash, next);
}

void
record_fill(struct record *r, const struct tuple *hdr)
{
	struct ip_hdr ip;
	char *name;

	TAILQ_INIT(&r->hashes);

	/* Fill the connection header */
	addr_pack(&r->src, ADDR_TYPE_IP, IP_ADDR_BITS,
	    &hdr->ip_src, IP_ADDR_LEN);
	addr_pack(&r->dst, ADDR_TYPE_IP, IP_ADDR_BITS,
	    &hdr->ip_dst, IP_ADDR_LEN);

	r->src_port = hdr->sport;
	r->dst_port = hdr->dport;
	gettimeofday(&r->tv_start, NULL);
	r->proto = hdr->type == SOCK_STREAM ? IP_PROTO_TCP : IP_PROTO_UDP;

        ip.ip_src = hdr->ip_src;
        name = honeyd_osfp_name(&ip);
	if (name != NULL)
		r->os_fp = strdup(name);

	/*
	 * Mark this connection as one that originated locally.  This will
	 * allow our stats measurement mechanisms to ignore it.
	 */
	if (hdr->local)
		r->flags |= REC_FLAG_LOCAL;
}

void
record_remove_hashes(struct hashq *hashes)
{
	struct hash *hash;
	
	while ((hash = TAILQ_FIRST(hashes)) != NULL) {
		TAILQ_REMOVE(hashes, hash, next);
		free(hash);
	}
}

void
record_clean(struct record *record)
{
	record_remove_hashes(&record->hashes);

	if (record->os_fp) {
		free(record->os_fp);
		record->os_fp = NULL;
	}
}

void
stats_free(struct stats *stats)
{
	SPLAY_REMOVE(statstree, &sc.all_stats, stats);
	stats_deactivate(stats);

	evtimer_del(&stats->ev_timeout);

	record_clean(&stats->record);
	record_remove_hashes(&stats->hashes);
	evbuffer_free(stats->evbuf);
	free(stats);
}

/* Functions for processing incoming network data */

static void
stats_process_data(struct stats *stats, void *data, u_int len)
{
	if (data == NULL) {
		/* This object has been terminated */
		gettimeofday(&stats->record.tv_end, NULL);
		stats->needelete = 1;
		return;
	}

	stats->record.bytes += len;

	evbuffer_add(stats->evbuf, data, len);
	stats_shingle_data(stats);
}

static void
stats_tcp_input(struct tuple *conhdr, u_char *pkt, u_int pktlen, void *arg)
{
	struct stats *stats = stats_find(conhdr);

	if (stats == NULL)
		stats = stats_new(conhdr);
	else if (stats->record.os_fp == NULL) {
		/* Update the passive fingerprint, if possible */
		char *name;
		struct ip_hdr ip;
		ip.ip_src = conhdr->ip_src;
		name = honeyd_osfp_name(&ip);
		if (name != NULL)
			stats->record.os_fp = strdup(name);
	}
		
}

static void
stats_udp_input(struct tuple *conhdr, u_char *pkt, u_int pktlen, void *arg)
{
	struct stats *stats = stats_find(conhdr);

	if (stats == NULL)
		stats = stats_new(conhdr);
}

static void
stats_tcp_data(struct tuple *conhdr, u_char *pkt, u_int pktlen, void *arg)
{
	struct stats *stats = stats_find(conhdr);
	if (stats == NULL)
		return;

	stats_process_data(stats, pkt, pktlen);
}

static void
stats_udp_data(struct tuple *conhdr, u_char *pkt, u_int pktlen, void *arg)
{
	struct stats *stats = stats_find(conhdr);
	if (stats == NULL)
		return;

	stats_process_data(stats, pkt, pktlen);
}

static void
stats_make_fd(struct addr *dst, u_short port)
{
	sc.stats_fd = make_socket(connect, SOCK_DGRAM, addr_ntoa(dst), port);
	if (sc.stats_fd == -1)
		err(1, "%s: make_socket", __func__);
	event_set(&sc.ev_send, sc.stats_fd, EV_WRITE, stats_ready_cb, NULL);
}

void
stats_register_cb(int (*cb)(const struct record *, void *), void *cb_arg)
{
	struct statscb *statscb = calloc(1, sizeof(struct statscb));
	assert(statscb != NULL);

	statscb->cb = cb;
	statscb->cb_arg = cb_arg;

	TAILQ_INSERT_TAIL(&sc.callbacks, statscb, next);
}

void
stats_init_collect(struct addr *dst, u_short port, char *name, char *password)
{
	sc.user_name = name;
	sc.user_key = password;
	sc.user_dst = dst;
	sc.user_port = port;

	stats_make_fd(dst, port);

	/* Set up message authentication code */
	hmac_init(&sc.hmac, sc.user_key);
}

void
stats_init()
{
	/* Information to establish the authentication */
	memset(&sc, 0, sizeof(sc));
	sc.stats_fd = -1;

	/* Setup hooks that we use for data processing */
	hooks_add_packet_hook(IP_PROTO_TCP, HD_INCOMING,
	    stats_tcp_input, NULL);
	hooks_add_packet_hook(IP_PROTO_UDP, HD_INCOMING,
	    stats_udp_input, NULL);

	hooks_add_packet_hook(IP_PROTO_TCP, HD_INCOMING_STREAM,
	    stats_tcp_data, NULL);
	hooks_add_packet_hook(IP_PROTO_UDP, HD_INCOMING_STREAM,
	    stats_udp_data, NULL);

	TAILQ_INIT(&sc.callbacks);
	
	TAILQ_INIT(&sc.send_queue);

	TAILQ_INIT(&sc.active_stats);
	SPLAY_INIT(&sc.all_stats);

	sc.evbuf_measure = evbuffer_new();
	sc.evbuf_tmp = evbuffer_new();

	/* Let the measurements begin */
	memset(&sc.measurement, 0, sizeof(sc.measurement));
	gettimeofday(&sc.measurement.tv_start, NULL);
	sc.tv_start = sc.measurement.tv_start;
	evtimer_set(&sc.ev_measure, stats_measure_cb, NULL);

	stats_measure_timeout();
}

void
stats_hmac_test()
{
	u_char digest[SHA1_DIGESTSIZE];
	char *test1 = "test", *test2 = "txst";

	hmac_init(&sc.hmac, "1234");
	hmac_sign(&sc.hmac, digest, sizeof(digest), test1, strlen(test1));

	if (!hmac_verify(&sc.hmac, digest, sizeof(digest),
		test1, strlen(test1)))
		errx(1, "%s: verify failed", __func__);

	if (hmac_verify(&sc.hmac, digest, sizeof(digest),
		test2, strlen(test2)))
		errx(1, "%s: verify should have failed", __func__);

	fprintf(stderr, "\t%s: OK\n", __func__);
}

void
stats_compress_test()
{
	u_char something[1024];
	struct evbuffer *buf = evbuffer_new();
	int i;

	/* Just create some stupid data */
	for (i = 0; i < sizeof(something); i++) {
		if (i == 0)
			something[i] = 1;
		something[i] = i + something[i-1];
	}

	for (i = 0; i < 3; i++) {
		evbuffer_drain(buf, EVBUFFER_LENGTH(buf));
		evbuffer_add(buf, something, sizeof(something));
		stats_compress(buf);
		fprintf(stderr, "\t\t Decompressed: %d, Compressed: %d\n",
		    sizeof(something), EVBUFFER_LENGTH(buf));

		/* Simulate packet loss */
		if (i == 1)
			continue;

		if (stats_decompress(buf) == -1)
			errx(1, "Decompress failed");
		if (EVBUFFER_LENGTH(buf) != sizeof(something))
			errx(1, "Decompressed data has bad length: %d vs %d",
			    EVBUFFER_LENGTH(buf), sizeof(something));
		if (memcmp(something, EVBUFFER_DATA(buf), sizeof(something)))
			errx(1, "Decompressed data is corrupted");
	}

	evbuffer_free(buf);
	fprintf(stderr, "\t%s: OK\n", __func__);
}

void
stats_test(void)
{
	stats_hmac_test();
	stats_compress_test();
}


syntax highlighted by Code2HTML, v. 0.9.1