/*
 *	Copyright (c) 1995, 1996, 1997, 1998, 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
 *
 */

#pragma ident	"@(#)psh_utils.c	1.6	99/05/28 SMI"

#include <stdio.h>
#include <tcl.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "psh.h"
#include "ipv6.h"

int
set8(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	int8_t value;

	Tcl_ResetResult(interp);
	(void) sscanf(argv[4], "%c", &value);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset + offset,
	    &value, size);
	return (TCL_OK);
}

int
setu8(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	uint8_t value;

	Tcl_ResetResult(interp);
	(void) sscanf(argv[4], "%c", &value);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset + offset,
	    &value, size);
	return (TCL_OK);
}

int
set16(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	int16_t value;

	Tcl_ResetResult(interp);
	(void) sscanf(argv[4], "%hd", &value);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset + offset,
	    &value, size);
	return (TCL_OK);
}

int
setu16(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	uint16_t value;

	Tcl_ResetResult(interp);
	(void) sscanf(argv[4], "%hd", &value);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset + offset,
	    &value, size);
	return (TCL_OK);
}

int
set32(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	int32_t value;

	Tcl_ResetResult(interp);
	(void) sscanf(argv[4], "%d", &value);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset + offset,
	    &value, size);
	return (TCL_OK);
}

int
setu32(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	uint32_t value;

	Tcl_ResetResult(interp);
	(void) sscanf(argv[4], "%u", &value);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset + offset,
	    &value, size);
	return (TCL_OK);
}

int
set64(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	int64_t value;

	Tcl_ResetResult(interp);
	(void) sscanf(argv[4], "%lld", &value);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset + offset,
	    &value, size);
	return (TCL_OK);
}

int
setu64(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	uint64_t value;

	Tcl_ResetResult(interp);
	(void) sscanf(argv[4], "%lld", &value);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset + offset,
	    &value, size);
	return (TCL_OK);
}


int
get8(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	int8_t value;
	char tmp[BUFSIZ];

	Tcl_ResetResult(interp);
	(void) memcpy(&value, pckt->p_buf + pckt->p_tbl[element].pt_offset +
	    offset, size);
	(void) sprintf(tmp, "%c", value);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}

int
getu8(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	uint8_t value;
	char tmp[BUFSIZ];

	Tcl_ResetResult(interp);
	(void) memcpy(&value, pckt->p_buf + pckt->p_tbl[element].pt_offset +
	    offset, size);
	(void) sprintf(tmp, "%c", value);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}

int
get16(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	int16_t value;
	char tmp[BUFSIZ];

	Tcl_ResetResult(interp);
	(void) memcpy(&value, pckt->p_buf + pckt->p_tbl[element].pt_offset +
	    offset, size);
	(void) sprintf(tmp, "%d", value);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}

int
getu16(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	uint16_t value;
	char tmp[BUFSIZ];

	Tcl_ResetResult(interp);
	(void) memcpy(&value, pckt->p_buf + pckt->p_tbl[element].pt_offset +
	    offset, size);
	(void) sprintf(tmp, "%d", value);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}

int
get32(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	int32_t value;
	char tmp[BUFSIZ];

	Tcl_ResetResult(interp);
	(void) memcpy(&value, pckt->p_buf + pckt->p_tbl[element].pt_offset +
	    offset, size);
	(void) sprintf(tmp, "%d", value);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}

int
getu32(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	uint32_t value;
	char tmp[BUFSIZ];

	Tcl_ResetResult(interp);
	(void) memcpy(&value, pckt->p_buf + pckt->p_tbl[element].pt_offset +
	    offset, size);
	(void) sprintf(tmp, "%d", value);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}

int
get64(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	int64_t value;
	char tmp[BUFSIZ];

	Tcl_ResetResult(interp);
	(void) memcpy(&value, pckt->p_buf + pckt->p_tbl[element].pt_offset +
	    offset, size);
	(void) sprintf(tmp, "%lld", value);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}

int
getu64(ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int element, int offset, int size, int argc, char *argv[])
{
	uint64_t value;
	char tmp[BUFSIZ];

	Tcl_ResetResult(interp);
	(void) memcpy(&value, pckt->p_buf + pckt->p_tbl[element].pt_offset +
	    offset, size);
	(void) sprintf(tmp, "%lld", value);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}

static Tcl_HashTable ip_protos_byname;
static Tcl_HashTable ip_protos_bynumber;

void
init_ip_protos()
{
	struct protoent *protoptr;
	Tcl_HashEntry *entryPtr;
	int protoexists;
	char *protoname;

	Tcl_InitHashTable(&ip_protos_byname, TCL_STRING_KEYS);
	Tcl_InitHashTable(&ip_protos_bynumber, TCL_ONE_WORD_KEYS);

	(void) setprotoent(0);
	while ((protoptr = getprotoent()) != NULL) {
		/*
		 * skip "ip" since it isn't a valid IP protocol.  The
		 * protocol for IP (encapsulation) is not 0, and is not
		 * called "ip".
		 */
		if (strcasecmp(protoptr->p_name, "ip") == 0)
			continue;
		entryPtr = Tcl_CreateHashEntry(&ip_protos_byname,
		    protoptr->p_name, &protoexists);
		if (protoexists != TCL_OK) {
			Tcl_SetHashValue(entryPtr, protoptr->p_proto);
		}

		entryPtr = Tcl_CreateHashEntry(&ip_protos_bynumber,
		    (char *)protoptr->p_proto, &protoexists);
		if (protoexists != TCL_OK) {
			if ((protoname = strdup(protoptr->p_name)) == NULL) {
				break;
			}
			Tcl_SetHashValue(entryPtr, protoname);
		}
	}
	(void) endprotoent();
}

char *
ipproto_name_by_number(int n)
{
	Tcl_HashEntry *entryPtr;

	entryPtr = Tcl_FindHashEntry(&ip_protos_bynumber, (char *)n);
	if (entryPtr == NULL)
		return ("unknown");
	else
		return ((char *)Tcl_GetHashValue(entryPtr));
}

int
ipproto_number_by_name(char *name)
{
	Tcl_HashEntry *entryPtr;

	entryPtr = Tcl_FindHashEntry(&ip_protos_byname, name);
	if (entryPtr == NULL)
		return (-1);
	else
		return ((int)Tcl_GetHashValue(entryPtr));
}


void
ip_cksum(unsigned short *sump, unsigned char *buf, int len)
{
	unsigned long sum = *sump;
	unsigned short val;

	while (len > 1) {
		val = *buf++ << 8;
		val |= *buf++;
		sum += val;
		len -= 2;
	}

	if (len > 0) {
		sum += (*buf) << 8;
	}

	while (sum >> 16) {
		sum = (sum & 0xffff) + (sum >> 16);
	}
	*sump = ~sum;
}


static char *
hexconvert(const char *cp, ulong_t *wordp, int lastflag)
{
	char *next;
	ulong_t val;

	val = strtoul(cp, &next, 16);

	if (next == cp)
		return ((char *)-1);

	if (*next != ':')
		return ((char *)-1);
	next++;			/* skip over first colon */
	*wordp = val << 16;


	cp = next;
	val = strtoul(next, &next, 16);
	if (next == cp)
		return ((char *)-1);

	if (!lastflag) {
		if (*next != ':')
			return ((char *)-1);
		next++;			/* skip over second colon */
	}
	*wordp += val;
	return (next);
}

static int
get_colon_cnt(const char *cp)
{
	char *ptr;
	int cnt = 0;

	ptr = strchr(cp, ':');
	while (ptr) {
		cnt++;
		ptr = strchr(ptr + 1, ':');
	}
	return (cnt);
}

static int
convert_addr(const char *cp_orig, char *local_buf)
{
	char *double_colon, *more_dots;
	int i, mapped, before_double, after_double;
	int colon_missing, more_colons;
	int is_network_prefix = 0;
	char	cp[64];	/* big enough for an IPv6 addr */
	ulong_t ipv4_part = 0;

	(void) strcpy(cp, cp_orig);
	double_colon = (char *)strstr(cp, "::");
	if (!double_colon)
		return (-1);

	if (!*(double_colon + 2))
		is_network_prefix = 1;

	more_colons = get_colon_cnt(double_colon + 2);
	more_dots = (char *)strstr(double_colon + 2, ".");

	/* case: ipv4 compat or mapped address */
	if ((double_colon == cp) && (more_colons < 3 || more_dots)) {

		mapped = get_colon_cnt(double_colon + 2);
		for (i = 0; i < 6 - mapped; i++)
			(void) strcat(local_buf, "0:");

			if (mapped && !more_dots) {
				ipv4_part = strtoul(double_colon + 2, NULL, 16);
				if (ipv4_part > 65535) {
					(void) strcat(local_buf,
					    double_colon + 2);
				} else {
					(void) strcat(local_buf, "0:");
					(void) strcat(local_buf,
					    double_colon + 2);
				}
			} else
				(void) strcat(local_buf, double_colon + 2);

			return (0);
	}

	*double_colon = '\0';
	before_double = get_colon_cnt(cp);
	after_double = get_colon_cnt(double_colon + 2);
	colon_missing = 7 - (before_double + after_double);

	(void) strcpy(local_buf, cp);
	if (!*cp)
		(void) strcpy(local_buf, "0");

	(void) strcat(local_buf, ":");

	for (i = 0; i < colon_missing - 1; i++)
		(void) strcat(local_buf, "0:");

	if (is_network_prefix)
		(void) strcat(local_buf, "0");
	else
		(void) strcat(local_buf, double_colon + 2);

	return (0);

}

int
psh_ascii2addr(long af, const char *cp, void *ap)
{

	switch (af) {
	case AF_INET: {
		ulong_t addr;
		ulong_t *addrp = (ulong_t *)ap;

		addr = inet_addr(cp);
		if (addr == (unsigned)-1) {
			return (-1);
		} else {
			*addrp = addr;
			return (0);
		}
	}

	case AF_INET6: {
		ulong_t	v6addr[4];	/* temp storage for IPv6 address */
		const char *next = cp;
		int i, cnt_colon;
		char local_buf[64];	/* big enough for an IPv6 addr */
		char *double_colon;

		local_buf[0] = '\0';
		/* test for v4 address */
		if ((strchr(next, '.') != NULL) && (strchr(next, ':') ==
		    NULL)) {
			v6addr[0] = (ulong_t)0;
			v6addr[1] = (ulong_t)0;
			v6addr[2] = (ulong_t)0xffff;
			v6addr[3] = inet_addr(next);
			(void) memcpy(ap, (char *)v6addr, sizeof (v6addr));
			return (0);
		}

		double_colon = strstr(cp, "::");
		cnt_colon = get_colon_cnt(cp);
		if (double_colon || (cnt_colon != 7 && cnt_colon != 6)) {
			if (convert_addr(cp, local_buf) == -1)
				return (-1);
		} else {
			(void) strcpy(&local_buf[0], cp);
		}

		next = local_buf;
		/* first three long words should be in hex format */
		for (i = 0; i < 3; i++) {
			next = hexconvert(next, &v6addr[i], 0);
			if (next == (char *)-1)
				return (-1);
		}

		/* See if fourth word is in IPv4 dotted decimal format */
		v6addr[3] = inet_addr(next);
		if (v6addr[3] == (unsigned)-1) {
			/* See if fourth word is in hex format */
			next = hexconvert(next, &v6addr[3], 1);
			if (next == (char *)-1)
				return (-1);
			v6addr[3] = htonl(v6addr[3]);
		}

		v6addr[0] = htonl(v6addr[0]);
		v6addr[1] = htonl(v6addr[1]);
		v6addr[2] = htonl(v6addr[2]);
		(void) memcpy(ap, (char *)v6addr, sizeof (v6addr));
		return (0);
	}

	default:
		return (-1);
	}
}


/* Is IPv6 an IPv4-mapped address? */
#define	V6_IS_V4(v6)		((v6)[0] == 0 && (v6)[1] == 0 && \
			    (v6)[2] == 0xffff)

/* Is IPv6 an IPv4-compatible address? */
#define	V6_IS_V4COMPAT(v6)	((v6)[0] == 0 && (v6)[1] == 0 && \
			    (v6)[2] == 0x0)

#define	IN6ADDRSZ	sizeof (struct ip6_addr)
#define	INT16SZ		sizeof (uint16_t)

static void
convert2ascii(char *buf, uchar_t *addr)
{
	/*
	 * Note that int32_t and int16_t need only be "at least" large enough
	 * to contain a value of the specified size.  On some systems, like
	 * Crays, there is no such thing as an integer variable with 16 bits.
	 * Keep this in mind if you think this function should have been coded
	 * to use pointer overlays.  All the world's not a VAX.
	 */
	char tmp[sizeof ("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")], *tp;
	struct {
		int base;
		int len;
	} best, cur;
	uint_t words[IN6ADDRSZ / INT16SZ];
	int i;

	/*
	 * Preprocess:
	 *	Copy the input (bytewise) array into a wordwise array.
	 *	Find the longest run of 0x00's in src[] for :: shorthanding.
	 */
	memset(words, '\0', sizeof (words));
	for (i = 0; i < IN6ADDRSZ; i++)
		words[i / 2] |= (addr[i] << ((1 - (i % 2)) << 3));
	best.base = -1;
	cur.base = -1;
	for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) {
		if (words[i] == 0) {
			if (cur.base == -1)
				cur.base = i, cur.len = 1;
			else
				cur.len++;
		} else {
			if (cur.base != -1) {
				if (best.base == -1 || cur.len > best.len)
					best = cur;
				cur.base = -1;
			}
		}
	}
	if (cur.base != -1) {
		if (best.base == -1 || cur.len > best.len)
			best = cur;
	}
	if (best.base != -1 && best.len < 2)
		best.base = -1;

	/*
	 * Format the result.
	 */
	tp = tmp;
	for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) {
		/* Are we inside the best run of 0x00's? */
		if (best.base != -1 && i >= best.base &&
		    i < (best.base + best.len)) {
			if (i == best.base)
				*tp++ = ':';
			continue;
		}
		/* Are we following an initial run of 0x00s or any real hex? */
		if (i != 0)
			*tp++ = ':';
		tp += sprintf(tp, "%x", words[i]);
	}
	/* Was it a trailing run of 0x00's? */
	if (best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ))
		*tp++ = ':';
	*tp++ = '\0';

	strcpy(buf, tmp);
}

/*
 * addr2ascii -- Convert an IPv4 or IPv6 address in binary form into
 * printable form, and return a pointer to that string.	 Caller can
 * provide a buffer to store string into.
 */
char *
psh_addr2ascii(int af, const void *a, char *buf)
{
	static char local_buf[64];	/* big enough for an IPv6 addr */
	ulong_t	v6addr[4];
	const char *addr = a;

	if (buf == (char *)0)
		buf = local_buf;


#define	UC(b)	(((int)b)&0xff)
	switch (af) {
	case AF_INET:
		(void) sprintf(buf, "%d.%d.%d.%d", UC(addr[0]), UC(addr[1]),
			UC(addr[2]), UC(addr[3]));
		return (buf);

	case AF_INET6:
		(void) memcpy((char *)&v6addr, addr, sizeof (v6addr));
		if (V6_IS_V4(v6addr))
			(void) sprintf(buf, "::%x:%d.%d.%d.%d",
				0xffff, UC(addr[12]), UC(addr[13]),
				UC(addr[14]), UC(addr[15]));
		else if (V6_IS_V4COMPAT(v6addr)) {
			(void) sprintf(buf, "::%d.%d.%d.%d",
				UC(addr[12]), UC(addr[13]), UC(addr[14]),
				UC(addr[15]));
		}
		else
			convert2ascii(buf, (uchar_t *)&v6addr[0]);

		return (buf);

	default:
		return ((char *)0);
	}
#undef UC
}
