/*
 *	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-route.c	1.20	99/05/28 SMI"

#include <sys/types.h>
#include <stdlib.h>
#include <netdb.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 <strings.h>
#include <tcl.h>
#include "psh.h"
#include "proto.h"
#include "name2int.h"


static char *proto_name;

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

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

	fptr = (uchar_t *)pckt->p_buf + pckt->p_tbl[element].pt_offset;
	optlen = (*(fptr + 1) + 1) * 8;
	if ((pckt->p_tbl[element].pt_offset + optlen) >= pckt->p_len) {
		*name = "end";
		*next_offset = pckt->p_len;
		return (0);
	}

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

/*
 * Get protocol fields
 */
int
get(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct routeopt6 fopt;
	char	tmp[128];
	struct ip6_addr *addrp;
	int i, addr_num;

	if (argc != 1) {
		Tcl_AppendResult(interp, "Usage: pget ", proto_name,
		    " <pckt> <item>", NULL);
		return (TCL_ERROR);
	}
	(void) memcpy(&fopt, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (fopt));
	if (strcmp(argv[0], "nexthdr") == 0) {
		(void) sprintf(tmp, "%u", fopt.routeopt6_nexthdr);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "hdrextlen") == 0) {
		(void) sprintf(tmp, "%u", fopt.routeopt6_hdrextlen);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "type") == 0) {
		(void) sprintf(tmp, "%u", fopt.routeopt6_routingtype);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "segment") == 0) {
		(void) sprintf(tmp, "%u", fopt.routeopt6_segmentsleft);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "flag") == 0) {
		(void) sprintf(tmp, "0x%x", fopt.routeopt6_bitflag);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "addr") == 0) {
		addr_num = fopt.routeopt6_hdrextlen / 2;
		addrp = (struct ip6_addr *)(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset + sizeof (fopt));
		for (i = 0; i < addr_num; i++) {
			if (psh_addr2ascii(AF_INET6, (char *)addrp, tmp) ==
			    NULL) {
				Tcl_ResetResult(interp);
				Tcl_AppendResult(interp, "Badly formed ipv6 "
				    "addr route hdr", NULL);
				return (TCL_ERROR);
			}

			Tcl_AppendResult(interp, " addr ", tmp, NULL);
			addrp++;
		}
	}
	return (TCL_OK);
}

/*
 * Set a protocol element
 */
set(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	int val;
	int routegiven = 0;
	struct routeopt6 fopt;
	struct ip6_addr addr;
	struct ip6_addr *addrp;
	int num_addr;
	char tmp[BUFSIZ];
	unsigned char *copy_begin;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: pset ", proto_name,
		    " <pckt> <item> <value>", NULL);
		return (TCL_ERROR);
	}
	(void) memcpy(&fopt, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (fopt));
	if (strcmp(argv[0], "nexthdr") == 0) {
		if ((val = ipproto_number_by_name(argv[1])) > -1) {
			fopt.routeopt6_nexthdr = htons(val);
		} else if ((Tcl_GetInt(interp, argv[1], &val) != TCL_OK) ||
		    val > 255 || val < 0) {
			Tcl_AppendResult(interp, "Bad value for nexthdr: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		fopt.routeopt6_nexthdr = htons(val);
	} else if (strcmp(argv[0], "hdrextlen") == 0) {
		if ((Tcl_GetInt(interp, argv[1], &val) != TCL_OK) ||
		    val > 255 || val < 0) {
			Tcl_AppendResult(interp, "Bad value for hdrextlen: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		fopt.routeopt6_hdrextlen = (unsigned char)val;
	} else if (strcmp(argv[0], "type") == 0) {
		if ((Tcl_GetInt(interp, argv[1], &val) != TCL_OK) ||
		    val > 255 || val < 0) {
			Tcl_AppendResult(interp, "Bad value for routing type: ",
			    argv[2], NULL);
			return (TCL_ERROR);
		}
		fopt.routeopt6_routingtype = (unsigned char)val;
	} else if (strcmp(argv[0], "segment") == 0) {
		if ((Tcl_GetInt(interp, argv[1], &val) != TCL_OK) ||
		    val > 255 || val < 0) {
			Tcl_AppendResult(interp,
			    "Bad value for segments left: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		fopt.routeopt6_segmentsleft = (unsigned char)val;
	} else if (strcmp(argv[0], "flag") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "Bad value for bitflag : ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		fopt.routeopt6_reserved &= ~fopt.routeopt6_reserved;
		fopt.routeopt6_bitflag &= ~fopt.routeopt6_bitflag;
		fopt.routeopt6_bitflag |= htonl(val);
	} else if (strcmp(argv[0], "add-addr") == 0) {
		if (pckt->p_len + sizeof (struct ip6_addr) > pckt->p_maxlen) {
			Tcl_AppendResult(interp, proto_name, " set add-addr: ",
			    "XXX packet resizing unimplemented", NULL);
			return (TCL_ERROR);
		}
		if (psh_ascii2addr(AF_INET6, argv[1], &addr) < 0) {
bad_addr:
			Tcl_ResetResult(interp);
			Tcl_AppendResult(interp, "Badly formed ipv6 addr ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		fopt.routeopt6_hdrextlen += 2;	/* added 2 * 8 octets */
		routegiven = 1;
		/*
		 * Copy up what follows the routing header.
		 */
		bcopy(pckt->p_buf + pckt->p_tbl[element + 1].pt_offset,
		    pckt->p_buf + pckt->p_tbl[element + 1].pt_offset +
		    sizeof (struct ip6_addr),
		    (size_t)(pckt->p_len - pckt->p_tbl[element + 1].pt_offset));
		pckt->p_len += sizeof (struct ip6_addr);
		/*
		 * Copy the routing header back into the packet.
		 */
		bcopy(&fopt, pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    sizeof (struct routeopt6));
		/*
		 * Copy the new address into the routing header.
		 */
		bcopy(&addr, pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    fopt.routeopt6_hdrextlen * 8 - 8, sizeof (struct ip6_addr));
		/*
		 * Update the ptbl since the offsets have changed.
		 */
		val = element;
		for (element++; element <= pckt->p_lptbl; element++) {
			if (ptbl_update(pckt, element, NULL,
			    pckt->p_tbl[element].pt_offset +
			    sizeof (struct ip6_addr)) == 0)
				return (TCL_ERROR);
		}
		element = val;

	} else if (strcmp(argv[0], "del-addr") == 0) {
		Tcl_ResetResult(interp);
		if (fopt.routeopt6_hdrextlen == 0) {
			Tcl_AppendResult(interp,
			    "hdrextlen is zero - no address entry exists ",
			    NULL);
			return (TCL_ERROR);
		}

		num_addr = (int)fopt.routeopt6_hdrextlen /2;
		addrp = (struct ip6_addr *)(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset + sizeof (fopt));

		while (num_addr > 0) {
			if (psh_addr2ascii(AF_INET6, (char *)addrp, tmp) ==
			    NULL) {
				Tcl_ResetResult(interp);
				Tcl_AppendResult(interp,
				    "Badly formed ipv6 addr ", argv[1], NULL);
				return (TCL_ERROR);
			}

			if (strcmp(argv[1], tmp) != 0) {
				/* this isn't the address, go on. */
				num_addr--;
				addrp++;
				continue;
			}

			/*
			 * This is the address, delete it.
			 */
			copy_begin = (unsigned  char *) addrp;
			if ((num_addr - 1) > 0)
				(void) memcpy(copy_begin,
				    copy_begin + sizeof (addr),
				    (num_addr - 1) * sizeof (addr));
			fopt.routeopt6_hdrextlen -= 2;
			pckt->p_tbl[element + 1].pt_offset -= sizeof (addr);
			pckt->p_len -= sizeof (addr);
			(void) memcpy(pckt->p_buf +
			    pckt->p_tbl[element].pt_offset, &fopt,
			    sizeof (fopt));
			return (TCL_OK);
		}
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "del-addr: address not found ",
		    argv[1], NULL);
		return (TCL_ERROR);

	} else {
		Tcl_AppendResult(interp, "Unknown ", proto_name, " field: ",
		    argv[0], NULL);
		return (TCL_ERROR);
	}
	if (!routegiven) {
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &fopt, sizeof (fopt));
	}
	return (TCL_OK);
}

int
list(int element, ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	struct routeopt6 fopt;
	char tmp[1024];
	struct ip6_addr *addrp;
	int num;

	(void) memcpy(&fopt, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (fopt));
	(void) sprintf(tmp, " nexthdr %u  hdrextlen %u  type %u ",
	    fopt.routeopt6_nexthdr, fopt.routeopt6_hdrextlen,
	    fopt.routeopt6_routingtype);
	Tcl_AppendResult(interp, tmp, NULL);
	(void) sprintf(tmp, " segments left %u  flags 0x%x ",
	    fopt.routeopt6_segmentsleft, fopt.routeopt6_bitflag);
	Tcl_AppendResult(interp, tmp, NULL);
	if (fopt.routeopt6_hdrextlen) {
		num = (int)fopt.routeopt6_hdrextlen /2;
		addrp = (struct ip6_addr *)(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset + sizeof (fopt));
		while (num > 0) {
			if (psh_addr2ascii(AF_INET6, (char *)addrp, tmp) ==
			    NULL) {
				Tcl_ResetResult(interp);
				Tcl_AppendResult(interp,
				    "Badly formed ipv6 addr ", argv[1], NULL);
				return (TCL_ERROR);
			}
			Tcl_AppendResult(interp, " addr ", tmp, NULL);
			num--;
			addrp++;
		}	/* while */
	}	/* if */
	return (TCL_OK);
}
