/*
 * 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 <event.h>
#include <pcap.h>
#include <dnet.h>

#include "tagging.h"
#include "untagging.h"

extern struct evbuffer *_buf;

static int __inline
decode_int_internal(uint32_t *pnumber, struct evbuffer *evbuf, int dodrain)
{
	uint32_t number = 0;
	uint8_t *data = EVBUFFER_DATA(evbuf);
	int len = EVBUFFER_LENGTH(evbuf);
	int nibbles = 0, off;

	if (!len)
		return (-1);

	nibbles = ((data[0] & 0xf0) >> 4) + 1;
	if (nibbles > 8 || (nibbles >> 1) > len - 1)
		return (-1);

	off = nibbles;
	while (off > 0) {
		number <<= 4;
		if (off & 0x1)
			number |= data[off >> 1] & 0x0f;
		else
			number |= (data[off >> 1] & 0xf0) >> 4;
		off--;
	}

	len = (nibbles >> 1) + 1;
	if (dodrain)
		evbuffer_drain(evbuf, len);

	*pnumber = number;

	return (len);
}

int
decode_int(uint32_t *pnumber, struct evbuffer *evbuf)
{
	return (decode_int_internal(pnumber, evbuf, 1) == -1 ? -1 : 0);
}

int
tag_peek(struct evbuffer *evbuf, uint8_t *ptag)
{
	if (EVBUFFER_LENGTH(evbuf) < 2)
		return (-1);
	*ptag = EVBUFFER_DATA(evbuf)[0];

	return (0);
}

int
tag_peek_length(struct evbuffer *evbuf, uint32_t *plength)
{
	struct evbuffer tmp;
	int res;

	if (EVBUFFER_LENGTH(evbuf) < 2)
		return (-1);

	tmp = *evbuf;
	tmp.buffer += 1;
	tmp.off -= 1;

	res = decode_int_internal(plength, &tmp, 0);
	if (res == -1)
		return (-1);

	*plength += res + 1;

	return (0);
}

int
tag_consume(struct evbuffer *evbuf)
{
	uint32_t len;
	evbuffer_drain(evbuf, 1);
	if (decode_int(&len, evbuf) == -1)
		return (-1);
	evbuffer_drain(evbuf, len);

	return (0);
}

/* Reads the data type from an event buffer */

int
tag_unmarshal(struct evbuffer *src, uint8_t *ptag, struct evbuffer *dst)
{
	uint8_t tag;
	uint16_t len;
	uint32_t integer;

	if (evbuffer_remove(src, &tag, sizeof(tag)) != sizeof(tag))
		return (-1);
	if (decode_int(&integer, src) == -1)
		return (-1);
	len = integer;

	if (EVBUFFER_LENGTH(src) < len)
		return (-1);

	if (evbuffer_add(dst, EVBUFFER_DATA(src), len) == -1)
		return (-1);

	evbuffer_drain(src, len);

	*ptag = tag;
	return (len);
}

/* Marshaling for integers */

int
tag_unmarshal_int(struct evbuffer *evbuf, uint8_t need_tag, uint32_t *pinteger)
{
	uint8_t tag;
	uint16_t len;
	uint32_t integer;

	if (evbuffer_remove(evbuf, &tag, sizeof(tag)) != sizeof(tag) ||
	    tag != need_tag)
		return (-1);
	if (decode_int(&integer, evbuf) == -1)
		return (-1);
	len = integer;

	if (EVBUFFER_LENGTH(evbuf) < len)
		return (-1);
	
	evbuffer_drain(_buf, EVBUFFER_LENGTH(_buf));
	if (evbuffer_add(_buf, EVBUFFER_DATA(evbuf), len) == -1)
		return (-1);

	evbuffer_drain(evbuf, len);

	return (decode_int(pinteger, _buf));
}

/* Unmarshal a fixed length tag */

int
tag_unmarshal_fixed(struct evbuffer *src, uint8_t need_tag, void *data,
    size_t len)
{
	uint8_t tag;

	/* Initialize this event buffer so that we can read into it */
	evbuffer_drain(_buf, EVBUFFER_LENGTH(_buf));

	/* Now unmarshal a tag and check that it matches the tag we want */
	if (tag_unmarshal(src, &tag, _buf) == -1 || tag != need_tag)
		return (-1);

	if (EVBUFFER_LENGTH(_buf) != len)
		return (-1);

	memcpy(data, EVBUFFER_DATA(_buf), len);
	return (0);
}

int
tag_unmarshal_string(struct evbuffer *evbuf, uint8_t need_tag, char **pstring)
{
	uint8_t tag;

	evbuffer_drain(_buf, EVBUFFER_LENGTH(_buf));

	if (tag_unmarshal(evbuf, &tag, _buf) == -1 || tag != need_tag)
		return (-1);

	*pstring = calloc(EVBUFFER_LENGTH(_buf) + 1, 1);
	if (*pstring == NULL)
		err(1, "%s: calloc", __func__);
	evbuffer_remove(_buf, *pstring, EVBUFFER_LENGTH(_buf));

	return (0);
}

int
tag_unmarshal_timeval(struct evbuffer *evbuf, uint8_t need_tag,
    struct timeval *ptv)
{
	uint8_t tag;
	uint32_t integer;

	evbuffer_drain(_buf, EVBUFFER_LENGTH(_buf));
	if (tag_unmarshal(evbuf, &tag, _buf) == -1 || tag != need_tag)
		return (-1);

	if (decode_int(&integer, _buf) == -1)
		return (-1);
	ptv->tv_sec = integer;
	if (decode_int(&integer, _buf) == -1)
		return (-1);
	ptv->tv_usec = integer;

	return (0);
}

int
tag_unmarshal_record(struct evbuffer *evbuf, uint8_t need_tag,
    struct record *record)
{
	uint8_t tag;

	struct evbuffer *tmp = evbuffer_new();

	if (tag_unmarshal(evbuf, &tag, tmp) == -1 || tag != need_tag)
		goto error;

	if (record_unmarshal(record, tmp) == -1)
		goto error;

	evbuffer_free(tmp);
	return (0);

 error:
	evbuffer_free(tmp);
	return (-1);
}

/* 
 * Functions for un/marshaling dnet's struct addr; we create a tagged
 * stream to save space.  Otherwise, we would have pay the overhead of
 * IPv6 address sizes for every kind of address.
 */

#define UNMARSHAL(tag, what) \
	tag_unmarshal_fixed(evbuf, tag, &(what), sizeof(what))

int
addr_unmarshal(struct addr* addr, struct evbuffer *evbuf)
{
	uint32_t tmp_int;

	memset(addr, 0, sizeof(struct addr));

	if (tag_unmarshal_int(evbuf, ADDR_TYPE,	&tmp_int) == -1)
		return (-1);
	addr->addr_type = tmp_int;

	if (tag_unmarshal_int(evbuf, ADDR_BITS, &tmp_int) == -1)
		return (-1);
	addr->addr_bits = tmp_int;

	switch (addr->addr_type) {
	case ADDR_TYPE_ETH:
		tag_unmarshal_fixed(evbuf, ADDR_ADDR,
		    &addr->addr_eth, sizeof(addr->addr_eth));
		break;
	case ADDR_TYPE_IP:
		tag_unmarshal_fixed(evbuf, ADDR_ADDR,
		    &addr->addr_ip, sizeof(addr->addr_ip));
		break;
	case ADDR_TYPE_IP6:
		tag_unmarshal_fixed(evbuf, ADDR_ADDR,
		    &addr->addr_ip6, sizeof(addr->addr_ip6));
		break;
	default:
		return (-1);
	}

	return (0);
}

/* 
 * Functions to un/marshal records.
 */

int
record_unmarshal(struct record *record, struct evbuffer *evbuf)
{
	struct evbuffer *tmp = evbuffer_new();
	uint32_t integer;
	uint8_t tag;

	memset(record, 0, sizeof(struct record));
	TAILQ_INIT(&record->hashes);

	/* The timevals are optional, so we need to check their presence */
	if (tag_peek(evbuf, &tag) != -1 && tag == REC_TV_START) {
		if (tag_unmarshal_timeval(evbuf, REC_TV_START,
			&record->tv_start) == -1)
			goto error;
	}
	if (tag_peek(evbuf, &tag) != -1 && tag == REC_TV_END) {
		if (tag_unmarshal_timeval(evbuf, REC_TV_END,
			&record->tv_end) == -1)
			goto error;
	}

	evbuffer_drain(tmp, EVBUFFER_LENGTH(tmp));
	if (tag_unmarshal(evbuf, &tag, tmp) == -1 || tag != REC_SRC)
		goto error;
	if (addr_unmarshal(&record->src, tmp) == -1)
		goto error;

	evbuffer_drain(tmp, EVBUFFER_LENGTH(tmp));
	if (tag_unmarshal(evbuf, &tag, tmp) == -1 || tag != REC_DST)
		goto error;
	if (addr_unmarshal(&record->dst, tmp) == -1)
		goto error;

	if (tag_unmarshal_int(evbuf, REC_SRC_PORT, &integer) == -1)
		goto error;
	record->src_port = integer;
	if (tag_unmarshal_int(evbuf, REC_DST_PORT, &integer) == -1)
		goto error;
	record->dst_port = integer;
	if (tag_unmarshal_int(evbuf, REC_PROTO, &integer) == -1)
		goto error;
	record->proto = integer;
	if (tag_unmarshal_int(evbuf, REC_STATE, &integer) == -1)
		goto error;
	record->state = integer;

	while (tag_peek(evbuf, &tag) != -1) {
		switch(tag) {
		case REC_OS_FP:
			if (tag_unmarshal_string(evbuf, tag,
				&record->os_fp) == -1)
				goto error;
			break;

		case REC_HASH: {
			struct hash *tmp;

			if ((tmp = calloc(1, sizeof(struct hash))) == NULL)
				err(1, "%s: calloc", __func__);
			if (tag_unmarshal_fixed(evbuf, REC_HASH, tmp->digest,
				sizeof(tmp->digest)) == -1) {
				free(tmp);
				goto error;
			}
			TAILQ_INSERT_TAIL(&record->hashes, tmp, next);
		}
			break;
		case REC_BYTES:
			if (tag_unmarshal_int(evbuf, tag,&record->bytes) == -1)
				goto error;
			break;
		case REC_FLAGS:
			if (tag_unmarshal_int(evbuf, tag,&record->flags) == -1)
				goto error;
			break;
		default:
			syslog(LOG_DEBUG, "Ignoring unknown record tag %d",
			    tag);
			tag_consume(evbuf);
			break;
		}
	}

	evbuffer_free(tmp);
	return (0);

 error:
	evbuffer_free(tmp);
	return (-1);
}

#define TEST_MAX_INT	6

void
tagging_int_test(void)
{
	struct evbuffer *tmp = evbuffer_new();
	uint32_t integers[TEST_MAX_INT] = {
		0xaf0, 0x1000, 0x1, 0xdeadbeef, 0x00, 0xbef000
	};
	uint32_t integer;
	int i;

	for (i = 0; i < TEST_MAX_INT; i++) {
		int oldlen, newlen;
		oldlen = EVBUFFER_LENGTH(tmp);
		encode_int(tmp, integers[i]);
		newlen = EVBUFFER_LENGTH(tmp);
		fprintf(stderr, "\t\tencoded 0x%08x with %d bytes\n",
		    integers[i], newlen - oldlen);
	}

	for (i = 0; i < TEST_MAX_INT; i++) {
		if (decode_int(&integer, tmp) == -1)
			errx(1, "decode %d failed", i);
		if (integer != integers[i])
			errx(1, "got %x, wanted %x", integer, integers[i]);
	}

	if (EVBUFFER_LENGTH(tmp) != 0)
		errx(1, "trailing data");
	evbuffer_free(tmp);

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

void
tagging_addr_test(void)
{
	struct evbuffer *tmp = evbuffer_new();
	struct addr one, two;

	addr_pton("192.168.1.16/28", &one);
	addr_marshal(tmp, &one);
	if (addr_unmarshal(&two, tmp) == -1)
		errx(1, "addr unmarshal failed.");
	if (addr_cmp(&one, &two) != 0)
		errx(1, "addr %s != %s", addr_ntoa(&one), addr_ntoa(&two));

	evbuffer_free(tmp);

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

void
tagging_record_test(void)
{
	struct evbuffer *tmp = evbuffer_new();
	struct record one, two;
	uint32_t length = 0;

	memset(&one, 0, sizeof(one));
	memset(&two, 0, sizeof(two));

	TAILQ_INIT(&one.hashes);
	addr_pton("127.0.0.1", &one.src);
	addr_pton("192.168.0.1", &one.dst);
	gettimeofday(&one.tv_start, NULL);
	one.proto = IP_PROTO_TCP;
	one.os_fp = "Honeyd Machine";
	one.bytes = 100;

	record_marshal(tmp, &one);
	if (tag_peek_length(tmp, &length) == -1 || length == 0)
		errx(1, "tag_peek_length failed.");

	if (record_unmarshal(&two, tmp) == -1)
		errx(1, "record unmarshal failed.");
	if (strcmp(one.os_fp, two.os_fp) != 0)
		errx(1, "fingerprints not the same");

	/* Equal out the variable fields */
	free(two.os_fp); two.os_fp = one.os_fp;
	two.hashes = one.hashes;

	if (memcmp(&one, &two, sizeof(one)) != 0)
		errx(1, "records not the same");

	evbuffer_free(tmp);

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

void
tagging_fuzz()
{
	u_char buffer[4096];
	struct evbuffer *tmp = evbuffer_new();
	rand_t *rand = rand_open();
	struct record record;
	struct addr addr;
	int i, j;

	for (j = 0; j < 100; j++) {
		for (i = 0; i < sizeof(buffer); i++)
			buffer[i] = rand_uint8(rand);
		evbuffer_drain(tmp, -1);
		evbuffer_add(tmp, buffer, sizeof(buffer));

		if (tag_unmarshal_record(tmp, 1, &record) != -1)
			errx(1, "tag_unmarshal_record should have failed");
		if (addr_unmarshal(&addr, tmp) != -1)
			errx(1, "addr_unmarshal should have failed");
		if (record_unmarshal(&record, tmp) != -1)
			errx(1, "record_unmarshal should have failed");
	}

	/* Now insert some corruption into the tag length field */
	evbuffer_drain(tmp, -1);
	addr_pton("127.0.0.0/20", &addr);
	addr_marshal(tmp, &addr);
	evbuffer_add(tmp, buffer, sizeof(buffer));

	EVBUFFER_DATA(tmp)[1] = 0xff;
	if (addr_unmarshal(&addr, tmp) != -1)
		errx(1, "addr_unmarshal should have failed");

	evbuffer_free(tmp);

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

void
tagging_test(void)
{
	tagging_init();
	tagging_int_test();
	tagging_addr_test();
	tagging_record_test();
	tagging_fuzz();
}
