/*
 *	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 - Protocol interpreter template
 */

#pragma ident	"@(#)ipv6-opts.c	1.20	99/05/28 SMI"

#include <sys/types.h>
#include <netdb.h>
#include <stdlib.h>
#include "ipv6.h"
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <net/if.h>
#include <string.h>
#include <tcl.h>
#include "psh.h"
#include "proto.h"
#include "name2int.h"
#define	OCTET_SIZE	8

static char *proto_name;

int num_opt;   /* number of  options set */

/*
 * Initialization
 */
int
init(proto_tbl_t *prototbl, Tcl_Interp *interp)
{
	if ((proto_name = strdup(prototbl->proto_name)) == NULL) {
		Tcl_AppendResult(interp, "Internal Error: malloc: no space",
		    NULL);
		return (0);
	}
	(void) Tcl_SetVar(interp, "pset-protocols", proto_name,
	TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
	(void) Tcl_SetVar(interp, "pget-protocols", proto_name,
	TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
	return (1);
}


/*
 * 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 = 2;
	if (ptbl_update(pckt, 1, NULL, 2) == 0)
		return (TCL_ERROR);
	return (TCL_OK);
}

int
decode(struct packet *pckt, int element, char **name, int *next_offset)
{
	uchar_t *optptr;
	int optlen;
	ulong_t proto_num;

	optptr = (unsigned char *)pckt->p_buf + pckt->p_tbl[element].pt_offset;
	optlen = (*(optptr + 1) + 1) * OCTET_SIZE;  /* rfc1883 section 4.3 */
	if ((pckt->p_tbl[element].pt_offset + optlen) >= pckt->p_len) {
		*name = "end";
		*next_offset = pckt->p_len;
		return (0);
	}

	proto_num = *optptr;
	*name = ipproto_name_by_number(proto_num);
	*next_offset = pckt->p_tbl[element].pt_offset + optlen;
	return (1);
}

/*
 * get_option_offset:
 *	Finds the option at the specified index and returns its offset
 *	from the beginning of the option header data.  Returns -1 if the
 *	specified index does not exist.
 */

static int
get_option_offset(uchar_t *optptr, int index, int optlen)
{
	int i = 0;
	int curroffset = 0;
	int curroptlen;

	while (i < index) {
		if (*optptr == 0) {
			/* This is a Pad1 option. */
			curroptlen = 1;
		} else {
			curroptlen = *(optptr + 1) + 2;
		}
		optptr += curroptlen;
		curroffset += curroptlen;
		if (curroffset  >= optlen) {
			return (-1);
		} else {
			i++;
		}
	}
	return (curroffset);
}

/*
 * Get protocol fields
 *  Syntax:
 *	pget <pkt> <proto> nexthdr
 *	pget <pkt> <proto> hdrextlen
 *	pget <pkt> <proto> option[:<index>]
 *      pget <pkt> <proto> optionval[:<index>]
 *
 *  When given the "option" argument, pget returns the type and length,
 *  separated by a space.  When given the "optionval" argument, pget returns
 *  an int for a jumbo payload option.  For all other options, pget returns
 *  a sequence of hex digits.  In the event that new options are defined,
 *  an argument may be added to identify a particular piece of that option.
 *  <index> starts at and defaults to 0.
 */
int
get(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	unsigned char *optptr;
	int optlen;
	char tmp[128];
	char *p;
	char *indexStr;
	int index;
	int optionOffset;
	ulong_t longtmp;
	int i;

	if (argc < 1) {
		Tcl_AppendResult(interp, "Usage: pget <pckt> ", proto_name,
		    " <item>", NULL);
		return (TCL_ERROR);
	}

	optptr = (unsigned char *) pckt->p_buf + pckt->p_tbl[element].pt_offset;
	optlen = (*(optptr + 1) + 1) * OCTET_SIZE;

	optlen -= 2;    /* optlen = Total optionlen - first two bytes */
	if (strcmp(argv[0], "nexthdr") == 0) {
		(void) strcpy(tmp, ipproto_name_by_number(*(optptr)));
		if (strcmp(tmp, "unknown") == 0) {
			(void) sprintf(tmp, "%u", *(optptr));
		}
		Tcl_AppendResult(interp, tmp, NULL);
		return (TCL_OK);
	} else if (strcmp(argv[0], "hdrextlen") == 0) {
		(void) sprintf(tmp, "%u", *(optptr + 1));
		Tcl_AppendResult(interp, tmp, NULL);
		return (TCL_OK);
	} else {
		if ((p = strchr(argv[0], ':')) == NULL) {
			indexStr = "0";
		} else {
			indexStr = p + 1;
			*p = '\0';
		}
	}
	optptr += 2; /* points to first tlv */
	if ((strcmp(argv[0], "option") == 0) ||
	    (strcmp(argv[0], "optionval") == 0)) {
		if ((Tcl_GetInt(interp, indexStr, &index) != TCL_OK) ||
		    (index < 0)) {
			Tcl_AppendResult(interp, "pget ", proto_name,
			    ": invalid index ", indexStr, NULL);
			return (TCL_ERROR);
		}
		optionOffset = get_option_offset(optptr, index, optlen);
		if (optionOffset < 0) {
			Tcl_AppendResult(interp, "pget ", proto_name,
			    ": index too large ", indexStr, NULL);
			return (TCL_ERROR);
		} else {
			optptr += optionOffset;
		}
	} else {
		Tcl_AppendResult(interp, "pget ", proto_name,
		    "invalid argument ", argv[0]);
		return (TCL_ERROR);
	}
	if (strcmp(argv[0], "option") == 0) {
		if (*(optptr) == IPV6OPT_PAD1) {
			(void) sprintf(tmp, "%u 0", *(optptr));
		} else {
			(void) sprintf(tmp, "%u %u", *(optptr), *(optptr + 1));
		}
		Tcl_AppendResult(interp, tmp, NULL);
		return (TCL_OK);
	} else {
		/* must be optionval arg */
		if (*(optptr) == IPV6OPT_JUMBO) {
			(void) memcpy(&longtmp, optptr + 2, 4);
			(void) sprintf(tmp, "%u", ntohl(longtmp));
		} else {
			/* set longtmp to the length of the option */
			longtmp = *(optptr + 1);
			optptr += 2;
			/* print each byte of the option data */
			for (i = 0; i < longtmp; i++) {
				(void) sprintf(tmp, " 0x%x", *optptr);
				Tcl_AppendResult(interp, tmp, NULL);
				optptr++;
			}
		}
		return (TCL_OK);
	}
	/* notreached */
}

/*
 * Set protocol fields
 *  Syntax:
 *	pset <pkt> <proto> nexthdr <value>
 *	pset <pkt> <proto> hdrextlen <value>
 *	pset <pkt> <proto> option[:<index>] <option_type>
 *	pset <pkt> <proto> option-new[:<index>] <option_type> <length>
 *      pset <pkt> <proto> optionval[:<index>] <value>
 *      pset <pkt> <proto> pad
 *
 *  For the set command, <index> can be either an integer index (starting
 *  at zero) or the string "end".  <index> defaults to zero.
 *  The "option-new" argument inserts a new option of the specified length
 *  at the specified index.
 *  The "option" argument only changes the type of option at the specified
 *  index.
 *
 *  The "pad" option pads the header to a multiple of 8 bytes.
 *
 *  If an option is added, the hdrextlen of the option header is updated with
 *  the new length and the packet table is redone.
 *
 *  It is not recommended that the hdrextlen be set directly by a script.
 *
 *  BUGS:  The user must make sure to pad the packet before using it for
 *         anything.
 */
set(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
	int argc, char *argv[])
{
	struct opt6 opt;
	struct protoent *pp;
	char *p;
	char *index_str;
	int index;
	uchar_t *optptr;
	uint_t opthdrlen, curr_opthdrlen;
	int val;
	int length_to_add;
	int option_offset;
	uint_t bytes_after_hdr;
	uint_t bytes_to_move;
	uint_t num_to_pad;
	char buf[128];
	char *tmppktbuf;
	uchar_t *endptr;
	int i;

	optptr = (uchar_t *)(pckt->p_buf + pckt->p_tbl[element].pt_offset);
	(void) memcpy(&opt, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (opt));
	curr_opthdrlen = opthdrlen = pckt->p_tbl[element+1].pt_offset -
	    pckt->p_tbl[element].pt_offset;
	bytes_after_hdr = pckt->p_len - pckt->p_tbl[element+1].pt_offset;

	/* First check the special case of pad */

	if ((argc > 0) && (strcmp(argv[0], "pad") == 0)) {
		endptr = (uchar_t *)(pckt->p_buf +
		    pckt->p_tbl[element+1].pt_offset);
		num_to_pad = (8 - (opthdrlen % 8)) % 8;
		if (num_to_pad == 0) {
			return (TCL_OK);
		}
		if ((pckt->p_len + num_to_pad) > pckt->p_maxlen) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": packet too small to add padding", NULL);
			return (TCL_ERROR);
		}
		if (bytes_after_hdr > 0) {
			tmppktbuf = malloc(bytes_after_hdr);
			(void) memcpy(tmppktbuf, endptr, bytes_after_hdr);
		}
		if (num_to_pad == 1) {
			*endptr = IPV6OPT_PAD1;
		} else {
			*endptr = IPV6OPT_PADN;
			*(endptr + 1) = num_to_pad - 2;
			(void) memset(endptr + 2, 0, num_to_pad - 2);
		}
		if (bytes_after_hdr > 0) {
			(void) memcpy(endptr + num_to_pad, tmppktbuf,
			    bytes_after_hdr);
			free(tmppktbuf);
		}
		for (i = element + 1; i <= pckt->p_lptbl; i++) {
			pckt->p_tbl[i].pt_offset += num_to_pad;
		}
		*(optptr + 1) = (((endptr - optptr) + num_to_pad) / 8) - 1;
		pckt->p_len += num_to_pad;
		return (TCL_OK);
	}
	if (argc < 2) {
		Tcl_AppendResult(interp, "Usage: pset <pckt> ", proto_name,
		    " <item> <value> [<value_specific>]", NULL);
		return (TCL_ERROR);
	}
	if (strcmp(argv[0], "nexthdr") == 0) {
		if ((pp = getprotobyname(argv[1])) != NULL) {
			opt.opt6_nexthdr = pp->p_proto;
		} else if (Tcl_GetInt(interp, argv[1], &val) == TCL_OK &&
		    val <= 255 && val >= 0) {
			opt.opt6_nexthdr = (uchar_t)val;
		} else {
			Tcl_AppendResult(interp, "Bad value for nexthdr: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(optptr, &opt, sizeof (opt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "hdrextlen") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "Bad value for hdrextlen: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		opt.opt6_hdrextlen = (uchar_t)val;
		(void) memcpy(optptr, &opt, sizeof (opt));
		return (TCL_OK);
	} else {
		if ((p = strchr(argv[0], ':')) == NULL) {
			index_str = "0";
		} else {
			index_str = p + 1;
			*p = '\0';
		}
	}

	optptr += sizeof (opt);
	curr_opthdrlen -= sizeof (opt);

	if (strcmp(argv[0], "optionval") == 0) {
		if ((Tcl_GetInt(interp, index_str, &index) != TCL_OK) ||
		    (index < 0)) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": invalid index ", index_str, NULL);
			return (TCL_ERROR);
		}
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": invalid value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		option_offset = get_option_offset(optptr, index,
		    curr_opthdrlen);
		if (option_offset < 0) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": index too large ", index_str, NULL);
			return (TCL_ERROR);
		}
		optptr += option_offset;
		if (*(optptr) != IPV6OPT_JUMBO) {
			(void) sprintf(buf,
			    "pset %s: can't set value for option type %u",
			    proto_name, *(optptr));
			Tcl_AppendResult(interp, buf, NULL);
			return (TCL_ERROR);
		} else {
			val = htonl(val);
			(void) memcpy(optptr + 2, &val, 4);
			return (TCL_OK);
		}
	} else if ((strcmp(argv[0], "option") == 0)) {
		/* We are just changing the option type */
		if (strcmp(index_str, "end") == 0) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": length required for adding new option", NULL);
			return (TCL_ERROR);
		}
		if ((Tcl_GetInt(interp, index_str, &index) != TCL_OK) ||
		    (index < 0)) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": invalid index ", index_str, NULL);
			return (TCL_ERROR);
		}
		option_offset = get_option_offset(optptr, index,
		    curr_opthdrlen);
		if (option_offset < 0) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": index too large ", index_str, NULL);
			return (TCL_ERROR);
		}
		optptr += option_offset;
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": invalid value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		*(optptr) = (uchar_t)val;
		return (TCL_OK);
	} else if ((strcmp(argv[0], "add-option") == 0) && (argc > 2)) {
		/* We are inserting an option */

		if (strcmp(index_str, "end") == 0) {
			option_offset = curr_opthdrlen;
		} else {
			if ((Tcl_GetInt(interp, index_str, &index)
			    != TCL_OK) || (index < 0)) {
				Tcl_AppendResult(interp, "pset ", proto_name,
				    ": invalid index ", index_str, NULL);
				return (TCL_ERROR);
			}
			option_offset = get_option_offset(optptr, index,
			    curr_opthdrlen);
			if (option_offset < 0) {
				Tcl_AppendResult(interp, "pset ", proto_name,
				    ": index too large ", index_str, NULL);
				return (TCL_ERROR);
			}
		}
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": invalid value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		if ((Tcl_GetInt(interp, argv[2], &length_to_add) != TCL_OK) ||
			(length_to_add <= 0) || (length_to_add > 255)) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": invalid length: ", argv[2], NULL);
			return (TCL_ERROR);
		}
		if (val == IPV6OPT_PAD1) {
			length_to_add = 1;
		} else {
			length_to_add += 2;  /* for type and length */
		}
		if ((pckt->p_len + length_to_add) > pckt->p_maxlen) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": packet too small to add option", NULL);
			return (TCL_ERROR);
		}
		curr_opthdrlen -= option_offset;
		optptr += option_offset;

		/*
		 * Now optptr is the location for the new option and
		 * curr_opthdrlen holds the number of bytes left in this
		 * option header.
		 */

		bytes_to_move = bytes_after_hdr + curr_opthdrlen;
		if (bytes_to_move > 0) {
			tmppktbuf = malloc(bytes_to_move);
			(void) memcpy(tmppktbuf, optptr, bytes_to_move);
		}
		(void) memset(optptr, 0, length_to_add);
		*(optptr) = (uchar_t)val;
		if (val != IPV6OPT_PAD1) {
			*(optptr + 1) = length_to_add - 2;
		}
		if (bytes_to_move > 0) {
			(void) memcpy(optptr + length_to_add, tmppktbuf,
			    bytes_to_move);
			free(tmppktbuf);
		}
		pckt->p_len += length_to_add;
		opthdrlen += length_to_add;
		opt.opt6_hdrextlen = (opthdrlen/8) - 1;
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &opt, sizeof (opt));
		for (i = element + 1; i <= pckt->p_lptbl; i++) {
			pckt->p_tbl[i].pt_offset += length_to_add;
		}
		return (TCL_OK);
	} else {
		Tcl_AppendResult(interp, "pset ", proto_name, ": invalid ",
		    "element ", argv[0]);
		return (TCL_ERROR);
	}
}

int
list(int element, ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	char tmp[1024];
	uchar_t *optptr;
	int optlen;

	optptr = (uchar_t *)(pckt->p_buf + pckt->p_tbl[element].pt_offset);
	optlen = (*(optptr + 1) + 1) * 8;  /* rfc1883 section 4.3 */

	while (optlen > 0) {
		(void) sprintf(tmp, "0x %x %x %x %x\n", *(optptr),
		    *(optptr + 1), *(optptr + 2), *(optptr + 3));
		Tcl_AppendResult(interp, tmp, NULL);
		optptr += sizeof (int);
		optlen -= sizeof (int);
	}    /* while */

	return (TCL_OK);
}
