/*
 * $Id: network_instruments.c 34909 2010-11-16 17:43:11Z gerald $
 */

/***************************************************************************
                          NetworkInstruments.c  -  description
                             -------------------
    begin                : Wed Oct 29 2003
    copyright            : (C) 2003 by root
    email                : scotte[AT}netinst.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
 
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "wtap-int.h"
#include "file_wrappers.h"
#include "buffer.h"
#include "network_instruments.h"

static const char network_instruments_magic[] = {"ObserverPktBufferVersion=09.00"};
static const int true_magic_length = 17;

static const guint32 observer_packet_magic = 0x88888888;

static const int observer_encap[] = {
	WTAP_ENCAP_ETHERNET,
	WTAP_ENCAP_TOKEN_RING
};
#define NUM_OBSERVER_ENCAPS (sizeof observer_encap / sizeof observer_encap[0])

static const int from_wtap_encap[] = {
	OBSERVER_UNDEFINED,
	OBSERVER_ETHERNET,
	OBSERVER_TOKENRING,
};
#define NUM_FROM_WTAP_ENCAPS (sizeof from_wtap_encap / sizeof observer_encap[0])

#define CAPTUREFILE_HEADER_SIZE sizeof(capture_file_header)

/*
 * The time in Observer files is in nanoseconds since midnight, January 1,
 * 2000, 00:00:00 local time.
 *
 * We want the seconds portion to be seconds since midnight, January 1,
 * 1970, 00:00:00 GMT.
 *
 * To do that, we add the number of seconds between midnight, January 1,
 * 2000, 00:00:00 local time and midnight, January 1, 1970, 00:00:00 GMT.
 * (That gets the wrong answer if the time zone is being read in a different
 * time zone, but there's not much we can do about that.)
 */
static gboolean have_time_offset;
static time_t seconds1970to2000;

static void init_time_offset(void)
{
	if (!have_time_offset) {
		struct tm midnight_2000_01_01;

		/*
		 * Get the number of seconds between midnight, January 1,
		 * 2000, 00:00:00 local time - that's just the UNIX
		 * time stamp for 2000-01-01 00:00:00 local time.
		 */
		midnight_2000_01_01.tm_year = 2000 - 1900;
		midnight_2000_01_01.tm_mon = 0;
		midnight_2000_01_01.tm_mday = 1;
		midnight_2000_01_01.tm_hour = 0;
		midnight_2000_01_01.tm_min = 0;
		midnight_2000_01_01.tm_sec = 0;
		midnight_2000_01_01.tm_isdst = -1;
		seconds1970to2000 = mktime(&midnight_2000_01_01);
		have_time_offset = TRUE;
	}
}

static gboolean observer_read(wtap *wth, int *err, gchar **err_info,
    gint64 *data_offset);
static gboolean observer_seek_read(wtap *wth, gint64 seek_off,
    union wtap_pseudo_header *pseudo_header, guchar *pd, int length,
    int *err, gchar **err_info);
static int read_packet_header(FILE_T fh, packet_entry_header *packet_header,
    int *err, gchar **err_info);
static gboolean read_packet_data(FILE_T fh, int offset_to_frame, int offset,
    guint8 *pd, int length, int *err, char **err_info);
static gboolean skip_to_next_packet(wtap *wth, int offset,
    int offset_to_next_packet, int *err, char **err_info);
static gboolean observer_dump(wtap_dumper *wdh, const struct wtap_pkthdr *phdr,
    const union wtap_pseudo_header *pseudo_header, const guchar *pd, int *err);

int network_instruments_open(wtap *wth, int *err, gchar **err_info)
{
	int bytes_read;
	int offset;
	capture_file_header file_header;
	guint i;
	tlv_header tlvh;
	int seek_increment;
	packet_entry_header packet_header;

	errno = WTAP_ERR_CANT_READ;
	offset = 0;

	/* read in the buffer file header */
	bytes_read = file_read(&file_header, sizeof file_header, 1, wth->fh);
	if (bytes_read != sizeof file_header) {
		*err = file_error(wth->fh);
		if (*err != 0)
			return -1;
		return 0;
	}
	offset += bytes_read;

	/* check the magic number */
	if (memcmp(file_header.observer_version, network_instruments_magic, true_magic_length)!=0) {
		return 0;
	}

	/* check the version */
	if (strncmp(network_instruments_magic, file_header.observer_version, 30)!=0) {
		*err = WTAP_ERR_UNSUPPORTED_ENCAP;
		*err_info = g_strdup_printf("Observer: unsupported file version %s", file_header.observer_version);
		return -1;
	}

	/* process extra information */
	for (i = 0; i < file_header.number_of_information_elements; i++) {
		/* read the TLV header */
		bytes_read = file_read(&tlvh, sizeof tlvh, 1, wth->fh);
		if (bytes_read != sizeof tlvh) {
			*err = file_error(wth->fh);
			if (*err == 0)
				*err = WTAP_ERR_SHORT_READ;
			return -1;
		}
		offset += bytes_read;

		tlvh.length = GUINT16_FROM_LE(tlvh.length);
		if (tlvh.length < sizeof tlvh) {
			*err = WTAP_ERR_BAD_RECORD;
			*err_info = g_strdup_printf("Observer: bad record (TLV length %u < %lu)",
			    tlvh.length, (unsigned long)sizeof tlvh);
			return -1;
		}

		/* skip the TLV data */
		seek_increment = tlvh.length - (int)sizeof tlvh;
		if (seek_increment > 0) {
			if (file_seek(wth->fh, seek_increment, SEEK_CUR, err) == -1)
				return -1;
		}
		offset += seek_increment;
	}

	/* get to the first packet */
	file_header.offset_to_first_packet =
	    GUINT16_FROM_LE(file_header.offset_to_first_packet);
	if (file_header.offset_to_first_packet < offset) {
		*err = WTAP_ERR_BAD_RECORD;
		*err_info = g_strdup_printf("Observer: bad record (offset to first packet %d < %d)",
		    file_header.offset_to_first_packet, offset);
		return FALSE;
	}
	seek_increment = file_header.offset_to_first_packet - offset;
	if (seek_increment > 0) {
		if (file_seek(wth->fh, seek_increment, SEEK_CUR, err) == -1)
			return -1;
	}

	/* pull off the packet header */
	bytes_read = file_read(&packet_header, sizeof packet_header, 1, wth->fh);
	if (bytes_read != sizeof packet_header) {
		*err = file_error(wth->fh);
		if (*err != 0)
			return -1;
		return 0;
	}

	/* check the packet's magic number; the magic number is all 8's,
	   so the byte order doesn't matter */
	if (packet_header.packet_magic != observer_packet_magic) {
		*err = WTAP_ERR_UNSUPPORTED_ENCAP;
		*err_info = g_strdup_printf("Observer: unsupported packet version %ul", packet_header.packet_magic);
		return -1;
	}

	/* check the data link type */
	if (packet_header.network_type >= NUM_OBSERVER_ENCAPS) {
		*err = WTAP_ERR_UNSUPPORTED_ENCAP;
		*err_info = g_strdup_printf("Observer: network type %u unknown or unsupported", packet_header.network_type);
		return -1;
	}
	wth->file_encap = observer_encap[packet_header.network_type];

	wth->file_type = WTAP_FILE_NETWORK_INSTRUMENTS_V9;

	/* set up the rest of the capture parameters */
	wth->subtype_read = observer_read;
	wth->subtype_seek_read = observer_seek_read;
	wth->subtype_close = NULL;
	wth->subtype_sequential_close = NULL;
	wth->snapshot_length = 0;	/* not available in header */
	wth->tsprecision = WTAP_FILE_TSPREC_NSEC;

	/* reset the pointer to the first packet */
	if (file_seek(wth->fh, file_header.offset_to_first_packet, SEEK_SET,
	    err) == -1)
		return -1;
	wth->data_offset = file_header.offset_to_first_packet;

	init_time_offset();

	return 1;
}

/* Reads the next packet. */
static gboolean observer_read(wtap *wth, int *err, gchar **err_info,
    gint64 *data_offset)
{
	int offset;
	packet_entry_header packet_header;

	/* skip records other than data records */
	for (;;) {
		*data_offset = wth->data_offset;

		/* process the packet header, including TLVs */
		offset = read_packet_header(wth->fh, &packet_header, err,
		    err_info);
		if (offset <= 0)
			return FALSE;	/* EOF or error */

		wth->data_offset += offset;

		if (packet_header.packet_type == PACKET_TYPE_DATA_PACKET)
			break;

		/* skip to next packet */
		if (!skip_to_next_packet(wth, offset,
		    packet_header.offset_to_next_packet, err, err_info))
			return FALSE;	/* EOF or error */
	}

	/* neglect frame markers for wiretap */
	if (packet_header.network_size < 4) {
		*err = WTAP_ERR_BAD_RECORD;
		*err_info = g_strdup_printf("Observer: bad record: Packet length %u < 4",
		    packet_header.network_size);
		return FALSE;
	}

	/* set the wiretap packet header fields */
	wth->phdr.pkt_encap = observer_encap[packet_header.network_type];
	wth->phdr.len = packet_header.network_size - 4;
	wth->phdr.caplen = MIN(packet_header.captured_size, wth->phdr.len);
	wth->phdr.ts.secs =
	    (time_t) (packet_header.nano_seconds_since_2000/1000000000 + seconds1970to2000);
	wth->phdr.ts.nsecs = (int) (packet_header.nano_seconds_since_2000%1000000000);

	/* update the pseudo header */
	switch (wth->file_encap) {

	case WTAP_ENCAP_ETHERNET:
		/* There is no FCS in the frame */
		wth->pseudo_header.eth.fcs_len = 0;
		break;
	}

	/* set-up the packet buffer */
	buffer_assure_space(wth->frame_buffer, packet_header.captured_size);

	/* read the frame data */
	if (!read_packet_data(wth->fh, packet_header.offset_to_frame, offset,
	    buffer_start_ptr(wth->frame_buffer), packet_header.captured_size,
	    err, err_info))
		return FALSE;
	wth->data_offset += packet_header.captured_size;
	offset += packet_header.captured_size;

	/* skip over any extra bytes following the frame data */
	if (!skip_to_next_packet(wth, offset, packet_header.offset_to_next_packet,
		err, err_info))
		return FALSE;

	return TRUE;
}

/* Reads a packet at an offset. */
static gboolean observer_seek_read(wtap *wth, gint64 seek_off,
    union wtap_pseudo_header *pseudo_header, guchar *pd, int length,
    int *err, gchar **err_info)
{
	packet_entry_header packet_header;
	int offset;

	if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
		return FALSE;

	/* process the packet header, including TLVs */
	offset = read_packet_header(wth->random_fh, &packet_header, err,
	    err_info);
	if (offset <= 0)
		return FALSE;	/* EOF or error */

	/* update the pseudo header */
	switch (wth->file_encap) {

	case WTAP_ENCAP_ETHERNET:
		/* There is no FCS in the frame */
		pseudo_header->eth.fcs_len = 0;
		break;
	}

	/* read the frame data */
	if (!read_packet_data(wth->random_fh, packet_header.offset_to_frame,
	    offset, pd, length, err, err_info))
		return FALSE;

	return TRUE;
}

static int
read_packet_header(FILE_T fh, packet_entry_header *packet_header, int *err,
    gchar **err_info)
{
	int offset;
	int bytes_read;
	guint i;
	tlv_header tlvh;
	int seek_increment;

	offset = 0;

	/* pull off the packet header */
	bytes_read = file_read(packet_header, sizeof *packet_header, 1, fh);
	if (bytes_read != sizeof *packet_header) {
		*err = file_error(fh);
		if (*err != 0)
			return -1;
		return 0;	/* EOF */
	}
	offset += bytes_read;

	/* swap all multi-byte fields immediately */
	packet_header->packet_magic = GUINT32_FROM_LE(packet_header->packet_magic);
	packet_header->network_speed = GUINT32_FROM_LE(packet_header->network_speed);
	packet_header->captured_size = GUINT16_FROM_LE(packet_header->captured_size);
	packet_header->network_size = GUINT16_FROM_LE(packet_header->network_size);
	packet_header->offset_to_frame = GUINT16_FROM_LE(packet_header->offset_to_frame);
	packet_header->offset_to_next_packet = GUINT16_FROM_LE(packet_header->offset_to_next_packet);
	packet_header->errors = GUINT16_FROM_LE(packet_header->errors);
	packet_header->reserved = GUINT16_FROM_LE(packet_header->reserved);
	packet_header->packet_number = GUINT64_FROM_LE(packet_header->packet_number);
	packet_header->original_packet_number = GUINT64_FROM_LE(packet_header->original_packet_number);
	packet_header->nano_seconds_since_2000 = GUINT64_FROM_LE(packet_header->nano_seconds_since_2000);

	/* check the packet's magic number */
	if (packet_header->packet_magic != observer_packet_magic) {

		/*
		 * Some files are zero-padded at the end. There is no warning of this
		 * in the previous packet header information, such as setting
		 * offset_to_next_packet to zero. So detect this situation by treating
		 * an all-zero header as a sentinel. Return EOF when it is encountered,
		 * rather than treat it as a bad record.
		 */
		for (i = 0; i < sizeof *packet_header; i++) {
			if (((guint8*) packet_header)[i] != 0)
				break;
		}
		if (i == sizeof *packet_header) {
			*err = 0;
			return 0;	/* EOF */
		}

		*err = WTAP_ERR_BAD_RECORD;
		*err_info = g_strdup_printf("Observer: bad record: Invalid magic number 0x%08x",
		    GUINT32_FROM_LE(packet_header->packet_magic));
		return -1;
	}

	/* process extra information */
	for (i = 0; i < packet_header->number_of_information_elements; i++) {
		/* read the TLV header */
		bytes_read = file_read(&tlvh, sizeof tlvh, 1, fh);
		if (bytes_read != sizeof tlvh) {
			*err = file_error(fh);
			if (*err == 0)
				*err = WTAP_ERR_SHORT_READ;
			return -1;
		}
		offset += bytes_read;

		tlvh.length = GUINT16_FROM_LE(tlvh.length);
		if (tlvh.length < sizeof tlvh) {
			*err = WTAP_ERR_BAD_RECORD;
			*err_info = g_strdup_printf("Observer: bad record (TLV length %u < %lu)",
			    tlvh.length, (unsigned long)sizeof tlvh);
			return -1;
		}

		/* skip the TLV data */
		seek_increment = tlvh.length - (int)sizeof tlvh;
		if (seek_increment > 0) {
			if (file_seek(fh, seek_increment, SEEK_CUR, err) == -1)
				return -1;
		}
		offset += seek_increment;
	}

	return offset;
}

static gboolean
read_packet_data(FILE_T fh, int offset_to_frame, int offset, guint8 *pd,
    int length, int *err, char **err_info)
{
	int seek_increment;

	/* get to the packet data */
	if (offset_to_frame < offset) {
		*err = WTAP_ERR_BAD_RECORD;
		*err_info = g_strdup_printf("Observer: bad record (offset to packet data %d < %d)",
		    offset_to_frame, offset);
		return FALSE;
	}
	seek_increment = offset_to_frame - offset;
	if (seek_increment > 0) {
		if (file_seek(fh, seek_increment, SEEK_CUR, err) == -1)
			return FALSE;
	}

	/* read in the packet */
	wtap_file_read_expected_bytes(pd, length, fh, err);
	return TRUE;
}

static gboolean
skip_to_next_packet(wtap *wth, int offset, int offset_to_next_packet, int *err,
    char **err_info)
{
	int seek_increment;

	if (offset_to_next_packet < offset) {
		*err = WTAP_ERR_BAD_RECORD;
		*err_info = g_strdup_printf("Observer: bad record (offset to next packet %d < %d)",
		    offset_to_next_packet, offset);
		return FALSE;
	}
	seek_increment = offset_to_next_packet - offset;
	if (seek_increment > 0) {
		if (file_seek(wth->fh, seek_increment, SEEK_CUR, err) == -1)
			return FALSE;
	}
	wth->data_offset += seek_increment;
	return TRUE;
}

typedef struct {
	guint64 packet_count;
	guint8  network_type;
} niobserver_dump_t;

/* Returns 0 if we could write the specified encapsulation type,
   an error indication otherwise. */
int network_instruments_dump_can_write_encap(int encap)
{
	/* per-packet encapsulations aren't supported */
	if (encap == WTAP_ENCAP_PER_PACKET)
		return WTAP_ERR_ENCAP_PER_PACKET_UNSUPPORTED;
	
	if (encap < 0 || (unsigned) encap >= NUM_FROM_WTAP_ENCAPS || from_wtap_encap[encap] == OBSERVER_UNDEFINED)
		return WTAP_ERR_UNSUPPORTED_ENCAP;

	return 0;
}

/* Returns TRUE on success, FALSE on failure; sets "*err" to an error code on
   failure. */
gboolean network_instruments_dump_open(wtap_dumper *wdh, gboolean cant_seek, int *err)
{
	capture_file_header file_header;
	tlv_header comment_header;
	char comment[64];
	struct tm *current_time;
	time_t system_time;
	niobserver_dump_t *niobserver;

	if (cant_seek) {
		*err = WTAP_ERR_CANT_WRITE_TO_PIPE;
		return FALSE;
	}

	wdh->subtype_write = observer_dump;

	niobserver = (niobserver_dump_t *)g_malloc(sizeof(niobserver_dump_t));
	wdh->priv = (void *)niobserver;
	niobserver->packet_count = 0;
	niobserver->network_type = from_wtap_encap[wdh->encap];

	/* create the file comment */
	time(&system_time);
	current_time = localtime(&system_time);
	memset(&comment, 0x00, sizeof(comment));
	g_snprintf(comment, 64, "This capture was saved from Wireshark on %s", asctime(current_time));

	/* create the file header */
	if (fseek(wdh->fh, 0, SEEK_SET) == -1) {
		*err = errno;
		return FALSE;
	}
	memset(&file_header, 0x00, sizeof(capture_file_header));
	g_strlcpy(file_header.observer_version, network_instruments_magic, 32);
	file_header.offset_to_first_packet = (guint16) (sizeof(capture_file_header) + sizeof(tlv_header) + strlen(comment));
	file_header.offset_to_first_packet = GUINT16_TO_LE(file_header.offset_to_first_packet);
	file_header.number_of_information_elements = 1;
	if(!wtap_dump_file_write(wdh, &file_header, sizeof(capture_file_header), err))
		return FALSE;

	/* create the comment entry */
	comment_header.type = GUINT16_TO_LE(INFORMATION_TYPE_COMMENT);
	comment_header.length = (guint16) (sizeof(tlv_header) + strlen(comment));
	comment_header.length = GUINT16_TO_LE(comment_header.length);
	if(!wtap_dump_file_write(wdh, &comment_header, sizeof(tlv_header), err))
		return FALSE;
	if(!wtap_dump_file_write(wdh, &comment, strlen(comment), err))
		return FALSE;

	init_time_offset();

	return TRUE;
}

/* Write a record for a packet to a dump file.
   Returns TRUE on success, FALSE on failure. */
static gboolean observer_dump(wtap_dumper *wdh, const struct wtap_pkthdr *phdr,
    const union wtap_pseudo_header *pseudo_header _U_, const guchar *pd,
    int *err)
{
	niobserver_dump_t *niobserver = (niobserver_dump_t *)wdh->priv;
	packet_entry_header packet_header;
	guint64 capture_nanoseconds;

	if (phdr->ts.secs < seconds1970to2000) {
		if (phdr->ts.secs < 0)
			capture_nanoseconds = 0;
		else
			capture_nanoseconds = phdr->ts.secs;
	} else
		capture_nanoseconds = phdr->ts.secs - seconds1970to2000;
	capture_nanoseconds = capture_nanoseconds*1000000000 + phdr->ts.nsecs;

	memset(&packet_header, 0x00, sizeof(packet_entry_header));
	packet_header.packet_magic = GUINT32_TO_LE(observer_packet_magic);
	packet_header.network_speed = GUINT32_TO_LE(1000000);
	packet_header.captured_size = GUINT16_TO_LE((guint16)phdr->caplen);
	packet_header.network_size = GUINT16_TO_LE((guint16)(phdr->len+4));
	packet_header.offset_to_frame = GUINT16_TO_LE(sizeof(packet_entry_header));
	packet_header.offset_to_next_packet = GUINT16_TO_LE(sizeof(packet_entry_header) + phdr->caplen);
	packet_header.network_type = niobserver->network_type;
	packet_header.flags = 0x00;
	packet_header.number_of_information_elements = 0;
	packet_header.packet_type = PACKET_TYPE_DATA_PACKET;
	packet_header.packet_number = GUINT64_TO_LE(niobserver->packet_count);
	packet_header.original_packet_number = GUINT64_TO_LE(niobserver->packet_count);
	niobserver->packet_count++;
	packet_header.nano_seconds_since_2000 = GUINT64_TO_LE(capture_nanoseconds);

	if (!wtap_dump_file_write(wdh, &packet_header, sizeof(packet_header),
	    err))
		return FALSE;

	if (!wtap_dump_file_write(wdh, pd, phdr->caplen, err))
		return FALSE;

	return TRUE;
}
