/*
 *	Copyright (c) 1995, 1996, Sun Microsystems, Inc.
 *
 * This software is provided by Sun ``AS IS'' and any express or implied
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose are disclaimed.
 * In no event shall Sun Microsystems be liable for any direct, indirect,
 * incidental, special, exemplary, or consequential damages.
 *
 * This software is not a product, and is provided for evaluation purposes
 * only.
 *
 * This software may not be resold without the express permission of
 * Sun Microsystems.
 *
 * Feedback on this tool may be sent to packet-shell-owner@sunroof.eng.sun.com.
 *
 * Packet Shell - Capture file 'protocol'.  Provide access to
 * a snoop capture file.
 */

#pragma ident	"@(#)file.c	1.30	99/05/28 SMI"

#include <tcl.h>
#include "psh.h"
#include "proto.h"
#include "pcap-int.h"

#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <strings.h>

static char *proto_name;

#define	roundup(x)	((x & 3) ? ((x & ~3) + 4) : (x))

/* XXX begin snoop file format definitions */

/*
 * Snoop files open with this.
 */
struct snoop_fheader {
	char name[8];
	int version;
	int mactype;
};

/*
 * DLPI Media types.
 *
 * (incomplete: consult DLPI standard.)
 */
#define	DL_CSMACD	0	/* 802.3 */
#define	DL_TPB		1	/* 802.4 */
#define	DL_TPR		2	/* 802.5 */
#define	DL_METRO	3	/* 802.6 */
#define	DL_ETHER	4	/* Ethernet */
#define	DL_HDLC		5
#define	DL_CHAR		6	/* character synchronous */
#define	DL_CTCA		7	/* IBM channel-to-channel */
#define	DL_FDDI		8
#define	DL_FC		16	/* Fibre channel */
#define	DL_ATM		17
#define	DL_IPATM	18	/* classical IP over ATM */
#define	DL_X25		19	/* X.25 LAPB */
#define	DL_ISDN		20
#define	DL_100BT	25	/* 100BaseT */


#define	SNOOP_V2	2

struct snoop_pheader {
	int	pktlen;		/* length of original packet */
	int	caplen;		/* length of packet captured into file */
	int	reclen;		/* length of this packet record */
	int	drops;		/* cumulative drops */
	struct timeval pktime;	/* packet arrival time */
};


/*
 * sf_next_packet, lifted from libpcap's internals, is linked to this module.
 */
int	sf_next_packet(pcap_t *p, struct pcap_pkthdr *hdr, uchar_t *buf,
	    int buflen);

/*
 * End points for snoop capture file I/O.  We retain here enough to be able
 * to do whatever communication operations we might need.  E.g., the fd
 * for the interface.
 */
struct file_ep {
	ep_t	fe_ep;
	int	fe_fd;
	int	fe_len;		/* length of the file map */
	int	fe_rwmode;
	char	*fe_first;
	char	*fe_nextpkt;
	char	*fe_offset;
	pcap_t	*fe_pd;
};

/*
 * XXX The normalize() function assumes that there is only one file
 * end-point, and that it can get to the data within this end-point.
 * This is a bogus assumption, but for backward compatibility,
 * we have this global pointer to a file end-point which it can use.
 * propen() just points this to the latest end-point which was opened.
 */
static struct file_ep *first_ep = NULL;

static double
normalize(struct timeval ptime)
{
	struct snoop_pheader *php;
	struct timeval pkttime, start;
	double d;

	if (first_ep == NULL) {
		(void) printf(" XXX: no origin for time value?\n");
		return (0.0);
	}
	php = (struct snoop_pheader *)first_ep->fe_first;
	start.tv_sec = ntohl(php->pktime.tv_sec);
	start.tv_usec = ntohl(php->pktime.tv_usec);
	pkttime.tv_sec = ntohl(ptime.tv_sec);
	pkttime.tv_usec = ntohl(ptime.tv_usec);
	d = pkttime.tv_sec - start.tv_sec;
	d += ((double)(pkttime.tv_usec - start.tv_usec)) / 1.0e+6;
	return (d);
}

/*
 * Important: Things to Understand about this protocol library & file formats!
 *
 * First, packet buffers which are present to be written, may or may not
 * have a 'file' entry which starts the ptbl for the packet.  This is because
 * pretty much the only way you have a 'file' entry in a packet is because
 * you read it from a file.  The 'file' entry is a snoop file format packet
 * header record (struct snoop_pheader).  The writepkt() routine crafts an
 * appropriate one if none is present.
 *
 * Endian-ness is tricky here.  The 'file' protocol header in a packet is
 * ALWAYS in host order.  (Having, e.g., the packet length in network order
 * is pretty useless.)  That means that both writepkt() and readpkt() must
 * take care to shift things about appropriately.
 *
 * Tcpdump file format is also supported, both for read and write.  However,
 * tcpdump format differs depending upon the architecture its run on.  It
 * always writes the packet header structures (struct pcap_pkthdr) in *host*
 * order, but takes care to read the file regardless of which endian it was
 * written to disk.  This means it internally tracks whether the file needs
 * byte-swapping, and does that before returning the structure to us.
 *
 * Note that I use ntohl() here for all conversions, despite the fact
 * in any given situation I might be translating from host to network
 * order, or in some situations where neither 'host' nor 'network' strictly
 * apply.  Therefore read 'ntohl()' as 'change the endianness', approximately.
 */
static int
writepkt(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct packet *pckt;
	struct snoop_pheader snoop_phdr;
	struct file_ep *ep;
	struct pcap_pkthdr phdr;
	char *p;

	ep = (struct file_ep *)clientData;
	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0], " <packet>",
		    NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, "file write operation: ",
		    "packet not found: ", argv[1], NULL);
		return (TCL_ERROR);
	}
	if (ep->fe_pd == NULL) {
		/*
		 * Write to a snoop format file.  We need to do some fixes,
		 * but start by copying whole packet, and then fix up if
		 * necessary.
		 */
		bcopy(pckt->p_buf, ep->fe_nextpkt, pckt->p_len);
		if (strcmp(pckt->p_tbl[0].pt_name, proto_name) != 0) {
			/*
			 * There isn't a snoop packet header in this packet.
			 * Provide one, for ease of use...
			 */
			snoop_phdr.pktlen = htonl(pckt->p_len);
			snoop_phdr.caplen = htonl(pckt->p_len);
			snoop_phdr.reclen = htonl(roundup(pckt->p_len +
			    sizeof (struct snoop_pheader)));
			snoop_phdr.drops = 0;
			snoop_phdr.pktime = pckt->p_pat;
			bcopy(&snoop_phdr, ep->fe_nextpkt, sizeof (snoop_phdr));
			ep->fe_nextpkt += sizeof (snoop_phdr);
			ep->fe_len += sizeof (snoop_phdr);
		} else {
			p = ep->fe_nextpkt;
			bcopy(pckt->p_buf, &snoop_phdr, sizeof (snoop_phdr));
			snoop_phdr.pktlen = htonl(snoop_phdr.pktlen);
			snoop_phdr.reclen = htonl(snoop_phdr.reclen);
			snoop_phdr.caplen = htonl(snoop_phdr.caplen);
			snoop_phdr.drops = htonl(snoop_phdr.drops);
			snoop_phdr.pktime.tv_sec =
			    htonl(snoop_phdr.pktime.tv_sec);
			snoop_phdr.pktime.tv_usec =
			    htonl(snoop_phdr.pktime.tv_usec);
			bcopy(&snoop_phdr, ep->fe_nextpkt, sizeof (snoop_phdr));
		}
		if (roundup(pckt->p_len) - pckt->p_len > 0) {
			bzero(ep->fe_nextpkt + pckt->p_len,
			    roundup(pckt->p_len) - pckt->p_len);
		}
		ep->fe_nextpkt += roundup(pckt->p_len);
		ep->fe_len += roundup(pckt->p_len);
		return (TCL_OK);
	}
	/*
	 * Write to a tcpdump file.  First, create the pcap_pkthdr structure.
	 * We want to use the preconstructed stuff if there was a 'file'
	 * layer in the packet already, otherwise we just automatically
	 * provide our best guess.  Note the endian considerations and notes
	 * at the head of this function.
	 */
	if (strcmp(pckt->p_tbl[0].pt_name, proto_name) == 0) {
		phdr.ts.tv_sec = htonl(pckt->p_pat.tv_sec);
		phdr.ts.tv_usec = htonl(pckt->p_pat.tv_usec);
		bcopy(pckt->p_buf, &snoop_phdr, sizeof (snoop_phdr));
		phdr.caplen = snoop_phdr.caplen;
		phdr.len = snoop_phdr.pktlen;
		/*
		 * Since libpcap takes two pointers, one to the "header" and
		 * one to the "packet", when the packet-shell packet contains
		 * both, we must skip the header.
		 */
		p = pckt->p_buf + pckt->p_tbl[1].pt_offset;
	} else {
		/*
		 * No 'file' header present, just best guess it.
		 */
		phdr.ts = pckt->p_pat;
		phdr.len = pckt->p_len;
		phdr.caplen = phdr.len;
		p = pckt->p_buf;
	}
	/*
	 * Now the pcap_phdr is ready.  Write the packet out.
	 */
	pcap_dump((uchar_t *)ep->fe_pd->sf.rfile,
	    (const struct pcap_pkthdr *)&phdr, (const uchar_t *)p);
	return (TCL_OK);
}

/*
 * <x>.read <pktname> [<packet number>]
 * Read into the specified packet the next packet, or the packet
 * indicated.
 *
 * See notes for writepkt() about important endian and file format
 *  considerations.
 */
static int
readpkt(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct file_ep *ep;
	struct snoop_pheader *sphp;
	struct snoop_pheader snoop_phdr;
	struct packet *pckt;
	int retval;
	struct pcap_pkthdr phdr;
	char *buf;
	int len = 1500;

	Tcl_ResetResult(interp);
	if (argc > 3 || argc < 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0],
		    " <packet> [<number>]", NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, "file read operation: ",
		    "packet not found: ", argv[1], NULL);
		return (TCL_ERROR);
	}
	if (argc == 3) {
		Tcl_AppendResult(interp, "file seek operation not implemeted",
		    NULL);
		return (TCL_ERROR);
	}
	ep = (struct file_ep *)clientData;
	/*
	 * Check what kind of capture file we are using.
	 */
	if (ep->fe_pd == NULL) {
		/*
		 * File is a snoop format capture file.
		 */
		sphp = (struct snoop_pheader *)ep->fe_nextpkt;
		/*
		 * Have we exhausted the file yet?
		 */
		if (ep->fe_nextpkt -
		    (ep->fe_first - sizeof (struct snoop_fheader)) >=
		    ep->fe_len) {
			Tcl_AppendResult(interp, "eof", NULL);
			return (TCL_OK);
		}
		if (pckt->p_maxlen < htonl(sphp->reclen)) {
			Tcl_AppendResult(interp, "file:  packet resize",
			    " operation not implemeted", NULL);
			return (TCL_ERROR);
		}
		bcopy(ep->fe_nextpkt, pckt->p_buf, htonl(sphp->reclen));
		bcopy(ep->fe_nextpkt, &snoop_phdr, sizeof (snoop_phdr));
		pckt->p_len = htonl(sphp->caplen) +
		    sizeof (struct snoop_pheader);
		if (ptbl_init(pckt, proto_name) == 0) {
			Tcl_AppendResult(interp, proto_name,
			    ": ptbl_init failed", NULL);
			return (TCL_ERROR);
		}
		ep->fe_nextpkt += htonl(sphp->reclen);
		/*
		 * And must fix byte order of the copy in the packet.
		 */
		snoop_phdr.pktlen = htonl(snoop_phdr.pktlen);
		snoop_phdr.reclen = htonl(snoop_phdr.reclen);
		snoop_phdr.caplen = htonl(snoop_phdr.caplen);
		snoop_phdr.drops = htonl(snoop_phdr.drops);
		snoop_phdr.pktime.tv_sec = htonl(snoop_phdr.pktime.tv_sec);
		snoop_phdr.pktime.tv_usec = htonl(snoop_phdr.pktime.tv_usec);
		bcopy(&snoop_phdr, pckt->p_buf, sizeof (snoop_phdr));
		pckt->p_pat = snoop_phdr.pktime;
		return (TCL_OK);
	}

	/*
	 * Handle the tcpdump dump file case
	 */
	buf = malloc(len);
	if (buf == NULL) {
		Tcl_AppendResult(interp, proto_name, ": malloc: no space",
		    NULL);
		return (TCL_ERROR);
	}
	retval = sf_next_packet((struct pcap *)ep->fe_pd, &phdr,
	    (unsigned char *)buf, len);
	if (retval != 0) {
		free(buf);
		Tcl_AppendResult(interp, "eof", NULL);
		return (TCL_OK);
	}
	snoop_phdr.pktlen = phdr.len;
	snoop_phdr.caplen = phdr.caplen;
	snoop_phdr.reclen = roundup(phdr.len + sizeof (struct snoop_pheader));
	snoop_phdr.drops = 0;
	bcopy(&phdr.ts, &snoop_phdr.pktime, sizeof (struct timeval));
	snoop_phdr.pktime.tv_sec = htonl(phdr.ts.tv_sec);
	snoop_phdr.pktime.tv_usec = htonl(phdr.ts.tv_usec);
	bcopy(&snoop_phdr, pckt->p_buf, sizeof (struct snoop_pheader));
	bcopy(buf, pckt->p_buf + sizeof (struct snoop_pheader), len);
	pckt->p_len = phdr.len + sizeof (snoop_phdr);
	pckt->p_pat = snoop_phdr.pktime;
	if (ptbl_init(pckt, proto_name) == 0) {
		Tcl_AppendResult(interp, proto_name,
		    ": ptbl_init failed", NULL);
		return (TCL_ERROR);
	}
	free(buf);
	return (TCL_OK);
}

/*
 * Initialization
 */
int
init(proto_tbl_t *prototbl, Tcl_Interp *interp)
{
	/* set the size of a file end-point */
	prototbl->proto_epsize = sizeof (struct file_ep);

	if ((proto_name = strdup(prototbl->proto_name)) == NULL) {
		Tcl_AppendResult(interp, "Internal Error: malloc: no space",
		    NULL);
		return (0);
	}
	return (1);
}

int
propen(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct stat st;
	struct file_ep *ep;
	char cmdname[128];
	char *p;
	int fd, save_errno, rw_mode = O_RDONLY;
	struct snoop_fheader *sfhp;
	char ebuf[PCAP_ERRBUF_SIZE];
	bool_t ep_fd_is_open = BFALSE;

	Tcl_ResetResult(interp);
	if (argc < 2 || argc > 3) {
usage:
		Tcl_AppendResult(interp, "Usage: popen ", proto_name,
		    " <id> <filename> [r|w|wtcpdump]", NULL);
		return (TCL_ERROR);
	}

	ep = (struct file_ep *)clientData;

	if (argc == 3) {
		if (strcmp(argv[2], "r") == 0) {
			rw_mode = O_RDONLY;
		} else if (strcmp(argv[2], "w") == 0) {
			/*
			 * Open snoop format file for writing.
			 */
			rw_mode = O_RDWR|O_TRUNC|O_CREAT;
		} else if (strcmp(argv[2], "wtcpdump") == 0) {
			/*
			 * Open tcpdump format file for writing.
			 */
			pcap_t *fake_pcap;

			rw_mode = O_RDWR|O_TRUNC|O_CREAT;
			fake_pcap = (pcap_t *)malloc(sizeof (struct pcap));
			bzero(fake_pcap, sizeof (struct pcap));
			if (fake_pcap == NULL) {
				Tcl_AppendResult(interp, proto_name,
				    ": malloc: no space", NULL);
				return (TCL_ERROR);
			}
			fake_pcap->linktype = DLT_EN10MB;
			/*
			 * libpcap doesn't seem to ever set this, so
			 * neither do we.
			 */
			fake_pcap->tzoff = 0;
			fake_pcap->snapshot = 1500;
			fake_pcap->sf.rfile = (FILE *)pcap_dump_open(fake_pcap,
			    argv[1]);
			if (fake_pcap->sf.rfile == NULL) {
				/*
				 * file open failed for some reason.  Bail.
				 */
				Tcl_AppendResult(interp, "file-open (tcpdump) ",
				    argv[1], " failed.", NULL);
				free(fake_pcap);
				return (TCL_ERROR);
			}
			fake_pcap->fd = NULL;
			ep->fe_pd = fake_pcap;
			Tcl_AppendResult(interp, "file-open (tcpdump) ",
			    argv[1], " on id ", argv[0], NULL);
			goto end;

		} else {
			goto usage;
		}
	}

	ep->fe_pd = (pcap_t *)pcap_open_offline(argv[1], ebuf);
	if (ep->fe_pd != NULL) {
		ep->fe_rwmode = O_RDONLY;
		Tcl_AppendResult(interp, "file-open (tcpdump) ",
		    argv[1], " on id ", argv[0], NULL);
		goto end;
	}
	if ((fd = open(argv[1], rw_mode, 0664)) < 0) {
file_error:
		save_errno = errno;
		Tcl_AppendResult(interp, "popen file: open: ",
		    argv[1], ": ",
		strerror(save_errno), NULL);
		return (TCL_ERROR);
	}
	ep_fd_is_open = BTRUE;
	if (fstat(fd, &st) < 0) {
		goto file_error;
	}
	if (rw_mode == O_RDONLY) {
		p = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
	} else {
		p = mmap(0, 1024 * 1024, PROT_READ|PROT_WRITE,
		    MAP_SHARED, fd, 0);
	}
	if (p == (char *)-1) {
		Tcl_AppendResult(interp, "popen file: mmap failed: ",
		    argv[1], " ", strerror(errno), NULL);
		return (TCL_ERROR);
	}
	sfhp = (struct snoop_fheader *)p;
	if (rw_mode == O_RDONLY) {
		/*
		 * Check the header of the file we will read.
		 */
		if (strcmp("snoop", sfhp->name) != 0 ||
		    htonl(sfhp->version) != SNOOP_V2 ||
		    htonl(sfhp->mactype) != DL_ETHER) {
			Tcl_AppendResult(interp, "popen file:",
			    " bad format: ", argv[1], NULL);
			return (TCL_ERROR);
		}
	} else {
		/*
		 * Establish initial file header
		 */
		(void) printf("Create snoop header...\n");
		if (lseek(fd, 1024 * 1024, SEEK_SET) < 0 ||
		    write(fd, "x", 1) < 0) {
			Tcl_AppendResult(interp, "popen file: ",
			    "Unable to create file: ", strerror(errno),
			    NULL);
			return (TCL_ERROR);
		}
		(void) strcpy(sfhp->name, "snoop");
		sfhp->version = htonl(SNOOP_V2);
		sfhp->mactype = htonl(DL_ETHER);
	}
	Tcl_AppendResult(interp, "file-open (snoop) ", argv[1], " on id ",
	    argv[0], NULL);
	ep->fe_first = p + sizeof (struct snoop_fheader);
	ep->fe_nextpkt = ep->fe_first;
end:
	ep->fe_rwmode = rw_mode;
	if (ep_fd_is_open) {
		ep->fe_len = st.st_size;
		ep->fe_fd = fd;
		ep->fe_rwmode = rw_mode;
	}

	if (first_ep == NULL)
		first_ep = ep;

	if (rw_mode == O_RDONLY) {
		(void) sprintf(cmdname, "%s.read", argv[0]);
		(void) Tcl_CreateCommand(interp, cmdname, readpkt, ep, NULL);
	} else {
		(void) sprintf(cmdname, "%s.write", argv[0]);
		(void) Tcl_CreateCommand(interp, cmdname, writepkt, ep, NULL);
	}
	return (TCL_OK);
}

int
prclose(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct file_ep *ep;
	char tmp[128];

	ep = (struct file_ep *)clientData;

	if (ep->fe_rwmode == O_RDONLY) {
		(void) sprintf(tmp, "%s.read", argv[0]);
	} else {
		(void) sprintf(tmp, "%s.write", argv[0]);
	}
	if (ep->fe_pd == NULL) {
		(void) Tcl_DeleteCommand(interp, tmp);
		(void) munmap(ep->fe_first - sizeof (struct snoop_fheader),
		    ep->fe_len);
		if (ep->fe_rwmode != O_RDONLY) {
			/*
			 * Finish off the file:  Truncate to the correct size.
			 */
			if (ftruncate(ep->fe_fd,
			    (ep->fe_nextpkt - ep->fe_first) +
			    sizeof (struct snoop_fheader)) < 0) {
				Tcl_AppendResult(interp, "pclose ",
				    proto_name, " ", "ftruncate fails: ",
				    strerror(errno), NULL);
				return (TCL_ERROR);
			}
		}
	} else {
		(void) Tcl_DeleteCommand(interp, tmp);
		pcap_close((struct pcap *)ep->fe_pd);
	}
	return (TCL_OK);
}

/*
 * Create a "default" packet.
 */
int
pinit(ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	if (pckt->p_len == 0)
		pckt->p_len = 28;
	if (ptbl_update(pckt, 1, NULL, pckt->p_len) == 0) {
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

int
decode(struct packet *pckt, int element, char **name, int *next_offset)
{
	/*
	 * This is really an error, since we aren't a real protocol.
	 */
	*name = "ether"; /* XXX DLPI MAC type */
	*next_offset = pckt->p_tbl[element].pt_offset +
	    sizeof (struct snoop_pheader);
	return (1);
}

/*
 * Get protocol fields.
 *	plen - original packet length
 *	clen - captured packet length
 *	drops - number of cumulative packet drops
 *	time - number of seconds from first packet, as a float
 *	date - the date and time of the packet
 */
int
get(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct snoop_pheader *php;
	char tmp[64];
	time_t tsecs;
	char *p;

	Tcl_ResetResult(interp);
	if (argc != 1) {
		Tcl_AppendResult(interp, "Usage: pget ", proto_name,
		    " <pckt> <item>", NULL);
		return (TCL_ERROR);
	}
	php = (struct snoop_pheader *)(pckt->p_buf +
	    pckt->p_tbl[element].pt_offset);
	if (strcmp(argv[0], "plen") == 0) {
		(void) sprintf(tmp, "0x%x", php->pktlen);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "clen") == 0) {
		(void) sprintf(tmp, "0x%x", php->caplen);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "rlen") == 0) {
		(void) sprintf(tmp, "0x%x", php->reclen);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "drops") == 0) {
		(void) sprintf(tmp, "0x%x", php->drops);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "time") == 0) {
		(void) sprintf(tmp, "%2.6f", normalize(php->pktime));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "date") == 0) {
		tsecs = php->pktime.tv_sec;
		p = ctime(&tsecs);
		p += 4; /* Skip the day of the week */
		p[15] = '\0'; /* Nuke the year */
		(void) sprintf(tmp, "%s.%6ld", p, php->pktime.tv_usec);
		Tcl_AppendResult(interp, p, tmp, NULL);
	} else {
		Tcl_AppendResult(interp, "Unknown file field: ", argv[0], NULL);
		return (TCL_ERROR);
	}

	return (TCL_OK);
}

int
list(int element, ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	char tmp[128];
	time_t tsecs;
	struct snoop_pheader *ph;
	char *p;

	Tcl_ResetResult(interp);
	ph = (struct snoop_pheader *)(pckt->p_buf +
	    pckt->p_tbl[element].pt_offset);
	tsecs = ph->pktime.tv_sec;
	p = ctime(&tsecs);
	p += 4; /* Skip the day of the week */
	p[15] = '\0'; /* Nuke the year */
	(void) sprintf(tmp, ".%ld  pktlen 0x%x  caplen 0x%x  drops %d "
	    "reclen 0x%x", ph->pktime.tv_usec, ph->pktlen,
	    ph->caplen, ph->drops, ph->reclen);
	Tcl_AppendResult(interp, "time ", p, tmp, NULL);
	return (TCL_OK);
}
