/*
 *	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-frag.c	1.21	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 <string.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 fragopt6);
	if (ptbl_update(pckt, 1, NULL, sizeof (struct fragopt6)) == 0)
		return (TCL_ERROR);
	return (TCL_OK);
}

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

	if (pckt->p_tbl[element].pt_offset + sizeof (struct fragopt6) >=
	    pckt->p_len) {
		*name = "end";
		*next_offset = pckt->p_len;
		return (0);
	}

	fptr = (uchar_t *)pckt->p_buf + pckt->p_tbl[element].pt_offset;
	proto_num = *fptr;	/*	 next header	*/
	(void) memcpy(&offset, fptr+2, 2);    /* offset field */
	if (ntohs(offset & FRAGOPT6_OFF_MASK) == 0) {
		*name = ipproto_name_by_number(proto_num);
	} else {
		*name = "data";
	}
	*next_offset = pckt->p_tbl[element].pt_offset +
		sizeof (struct fragopt6);
	return (1);
}

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

	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.fragopt6_nexthdr);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "reserved") == 0) {
		(void) sprintf(tmp, "0x%x", fopt.fragopt6_reserved);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "offset") == 0) {
		(void) sprintf(tmp, "%u",
		    ntohs(fopt.fragopt6_offset_and_flags & FRAGOPT6_OFF_MASK));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "moreflag") == 0) {
		(void) sprintf(tmp, "%u",
		    ntohs(fopt.fragopt6_offset_and_flags & FRAGOPT6_MORE_FRAG)
		    ? 1 : 0);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "ident") == 0) {
		(void) sprintf(tmp, "0x%x", ntohl(fopt.fragopt6_ident));
		Tcl_AppendResult(interp, tmp, NULL);
	}
	return (TCL_OK);
}

/*
 * Set a protocol element
 */
set(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	int val;
	struct fragopt6 fopt;

	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.fragopt6_nexthdr = (uchar_t)val;
		} else if ((Tcl_GetInt(interp, argv[1], &val) == TCL_OK) &&
		    val <= 255 && val >= 0) {
			fopt.fragopt6_nexthdr = (uchar_t)val;
		} else {
			Tcl_AppendResult(interp, "Bad value for nexthdr: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
	} else if (strcmp(argv[0], "reserved") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK ||
		    val > 255 || val < 0) {
			Tcl_AppendResult(interp, "Bad value for reserved: ",
				argv[1], NULL);
			return (TCL_ERROR);
		}
		fopt.fragopt6_reserved = (unsigned char)val;
	} else if (strcmp(argv[0], "offset") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK ||
		    val > 65528 || val < 0) {
			Tcl_AppendResult(interp, "Bad value for offset: ",
				argv[1], NULL);
			return (TCL_ERROR);
		}
		fopt.fragopt6_offset_and_flags =
		    (htons(val) & FRAGOPT6_OFF_MASK) |
		    (fopt.fragopt6_offset_and_flags & (FRAGOPT6_RESERVED_MASK |
		    FRAGOPT6_MORE_FRAG));
	} else if (strcmp(argv[0], "moreflag") == 0) {
		if (strcmp(argv[1], "on") == 0) {
			fopt.fragopt6_offset_and_flags |= FRAGOPT6_MORE_FRAG;
		} else if (strcmp(argv[1], "off") == 0) {
			fopt.fragopt6_offset_and_flags &= ~FRAGOPT6_MORE_FRAG;
		} else {
			Tcl_AppendResult(interp, "Bad value: ",
				argv[1], NULL);
			return (TCL_ERROR);
		}
	} else if (strcmp(argv[0], "ident") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "Bad value for ident: ",
				argv[1], NULL);
			return (TCL_ERROR);
		}
		fopt.fragopt6_ident = htonl(val);
	} else {
		Tcl_AppendResult(interp, "Unknown ", proto_name, " field: ",
		    argv[0], NULL);
		return (TCL_ERROR);
	}
	(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 fragopt6 fopt;
	char tmp[128];

	(void) memcpy(&fopt, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (fopt));
	(void) sprintf(tmp, " nexthdr %u  reserved 0x%x  offset %u ",
	    fopt.fragopt6_nexthdr, fopt.fragopt6_reserved,
	    ntohs(fopt.fragopt6_offset_and_flags & FRAGOPT6_OFF_MASK));
	Tcl_AppendResult(interp, tmp, NULL);
	(void) sprintf(tmp, " flags %s  ident %u ",
	    ntohs(fopt.fragopt6_offset_and_flags & FRAGOPT6_MORE_FRAG)
	    ? "MF" : "~MF", ntohl(fopt.fragopt6_ident));
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}
