/*
 * tnmIcmp.c --
 *
 *	Extend a Tcl interpreter with an icmp command. This
 *	module depends only the platform independent part.
 *
 * Copyright (c) 1993-1996 Technical University of Braunschweig.
 * Copyright (c) 1996-1997 University of Twente.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tnmInt.h"
#include "tnmPort.h"

/*
 * Every Tcl interpreter has an associated IcmpControl record. It
 * keeps track of the default settings for this interpreter.
 */

static char tnmIcmpControl[] = "tnmIcmpControl";

typedef struct IcmpControl {
    int retries;		/* Default number of retries. */
    int timeout;		/* Default timeout in seconds. */
    int size;			/* Default size of the ICMP packet. */
    int delay;			/* Default delay between ICMP packets. */
    int window;			/* Default window of active ICMP packets. */
} IcmpControl;

/*
 * Forward declarations for procedures defined later in this file:
 */

static void
AssocDeleteProc	_ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp));

static int
IcmpRequest	_ANSI_ARGS_((Tcl_Interp *interp, char *hosts, 
			     TnmIcmpRequest *icmpPtr));

/*
 *----------------------------------------------------------------------
 *
 * AssocDeleteProc --
 *
 *	This procedure is called when a Tcl interpreter gets destroyed
 *	so that we can clean up the data associated with this interpreter.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
AssocDeleteProc(clientData, interp)
    ClientData clientData;
    Tcl_Interp *interp;
{
    IcmpControl *control = (IcmpControl *) clientData;

    if (control) {
	ckfree((char *) control);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * IcmpRequest --
 *
 *	This procedure is called to process a single ICMP request.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
IcmpRequest(interp, hosts, icmpPtr)
    Tcl_Interp *interp;
    char *hosts;
    TnmIcmpRequest *icmpPtr;
{
    int i, code, largc;
    char **largv, buf[20];
    struct sockaddr_in addr;
    static unsigned int lastTid = 1;
    Tcl_DString dst;
    
    code = Tcl_SplitList(interp, hosts, &largc, &largv);
    if (code != TCL_OK) {
	return TCL_ERROR;
    }

    icmpPtr->numTargets = largc;
    icmpPtr->targets = (TnmIcmpTarget *) ckalloc(largc*sizeof(TnmIcmpTarget));
    memset((char *) icmpPtr->targets, 0, largc * sizeof(TnmIcmpTarget));

    for (i = 0; i < icmpPtr->numTargets; i++) {
	TnmIcmpTarget *targetPtr = &(icmpPtr->targets[i]);
	code = TnmSetIPAddress(interp, largv[i], &addr);
	if (code != TCL_OK) {
	    ckfree((char *) largv);
	    ckfree((char *) icmpPtr->targets);
	    return TCL_ERROR;
	}
	targetPtr->tid = lastTid++;
	targetPtr->dst = addr.sin_addr;
	targetPtr->res = addr.sin_addr;
	targetPtr->res.s_addr = 0;
    }

    code = TnmIcmp(interp, icmpPtr);
    if (code != TCL_OK) {
	ckfree((char *) largv);
	ckfree((char *) icmpPtr->targets);
	return TCL_ERROR;
    }

    Tcl_DStringInit(&dst);
    for (i = 0; i < icmpPtr->numTargets; i++) {
	TnmIcmpTarget *targetPtr = &(icmpPtr->targets[i]);
	switch (icmpPtr->type) {
	case TNM_ICMP_TYPE_ECHO:
	case TNM_ICMP_TYPE_MASK:
	case TNM_ICMP_TYPE_TIMESTAMP:
	    Tcl_DStringAppendElement(&dst, largv[i]);
	    break;
	case TNM_ICMP_TYPE_TRACE:
	    if (icmpPtr->flags & TNM_ICMP_FLAG_LASTHOP 
		&& targetPtr->flags & TNM_ICMP_FLAG_LASTHOP) {
		Tcl_DStringAppendElement(&dst, inet_ntoa(targetPtr->dst));
	    } else {
		Tcl_DStringAppendElement(&dst, inet_ntoa(targetPtr->res));
	    }
	    break;
	}
	if (targetPtr->status == TNM_ICMP_STATUS_NOERROR) {
	    switch (icmpPtr->type) {
	    case TNM_ICMP_TYPE_ECHO:
	    case TNM_ICMP_TYPE_TRACE:
	        /* This is to be discussed: if we get ping-times below
		   1 ms reported as 0 ms, we silently adjust this. */
		sprintf(buf, "%u", targetPtr->u.rtt ? targetPtr->u.rtt : 1);
		break;
	    case TNM_ICMP_TYPE_MASK:
		sprintf(buf, "%u.%u.%u.%u",
			(targetPtr->u.mask >> 24) & 0xff, 
			(targetPtr->u.mask >> 16) & 0xff,
			(targetPtr->u.mask >> 8) & 0xff, 
			targetPtr->u.mask & 0xff);
		break;
	    case TNM_ICMP_TYPE_TIMESTAMP:
		sprintf(buf, "%d", targetPtr->u.tdiff);
		break;
	    }
	} else {
	    buf[0] = 0;
	}
	Tcl_DStringAppendElement(&dst, buf);
    }
    Tcl_DStringResult(interp, &dst);
    
    ckfree((char *) largv);
    ckfree((char *) icmpPtr->targets);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Tnm_IcmpCmd --
 *
 *	This procedure is invoked to process the "icmp" command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
Tnm_IcmpCmd(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int actTimeout = -1;	/* actually used timeout */
    int actRetries = -1;	/* actually used retries */
    int actSize = -1;		/* actually used size */
    int actDelay = -1;		/* actually used delay */
    int actWindow = -1;		/* actually used window size */

    int type = 0;		/* the request type */
    int ttl = -1;		/* the time to live field */
    int flags = 0;		/* the flags for this request */
    int code;

    char *cmdName = argv[0];
    TnmIcmpRequest *icmpPtr;

    IcmpControl *control = (IcmpControl *) 
	Tcl_GetAssocData(interp, tnmIcmpControl, NULL);

    if (! control) {
	control = (IcmpControl *) ckalloc(sizeof(IcmpControl));
	control->retries = 2;
	control->timeout = 5;
	control->size = 64;
	control->delay = 0;
	control->window = 10;
	Tcl_SetAssocData(interp, tnmIcmpControl, AssocDeleteProc, 
			 (ClientData) control);
    }

    if (argc == 1) {
      icmpWrongArgs:
	Tcl_AppendResult(interp, "wrong # args: should be \"", cmdName,
			 " ?-retries n? ?-timeout n? ?-size n? ?-delay n?",
			 " ?-window size? option ?arg? hosts\"",
			 (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Parse the options.
     */

    argc--; argv++;
    while (argc > 0 && (*argv[0] == '-')) {
	if (strcmp(argv[0], "-retries") == 0) {
	    argc--, argv++;
	    if (argc < 1) {
		sprintf(interp->result, "%d", control->retries);
                return TCL_OK;
	    }
	    if (TnmGetUnsigned(interp, argv[0], &actRetries) != TCL_OK)
	        return TCL_ERROR;
	    argc--, argv++;
	} else if (strcmp(argv[0], "-timeout") == 0) {
	    argc--, argv++;
	    if (argc < 1) {
		sprintf(interp->result, "%d", control->timeout);
                return TCL_OK;
	    }
	    if (TnmGetPositive(interp, argv[0], &actTimeout) != TCL_OK) {
                return TCL_ERROR;
	    }
	    argc--, argv++;
	} else if (strcmp(argv[0], "-size") == 0) {
	    argc--, argv++;
	    if (argc < 1) {
		sprintf(interp->result, "%d", control->size);
		return TCL_OK;
	    }
	    if (TnmGetUnsigned(interp, argv[0], &actSize) != TCL_OK) {
                return TCL_ERROR;
	    }
	    if (actSize < 44) actSize = 44;
	    if (actSize >= 65516) actSize = 65515;
	    argc--, argv++;
	} else if (strcmp(argv[0], "-delay") == 0) {
	    argc--, argv++;
	    if (argc < 1) {
		sprintf(interp->result, "%d", control->delay);
		return TCL_OK;
	    }
	    if (TnmGetUnsigned(interp, argv[0], &actDelay) != TCL_OK) {
                return TCL_ERROR;
	    }
	    if (actDelay > 255) {
	        Tcl_ResetResult(interp);
		Tcl_AppendResult(interp,
			 "expected delay less than 256 but got \"",
			 argv[0], "\"", (char *) NULL);
		return TCL_ERROR;
	    }
	    argc--, argv++;
	} else if (strcmp(argv[0], "-window") == 0) {
	    argc--; argv++;
	    if (argc < 1) {
		sprintf(interp->result, "%d", control->window);
		return TCL_OK;
	    }
	    if (TnmGetUnsigned(interp, argv[0], &actWindow) != TCL_OK) {
                return TCL_ERROR;
	    }
	    if (actWindow > 65535) {
	        Tcl_ResetResult(interp);
		Tcl_AppendResult(interp,
			 "expected window less than 65536 but got \"",
			 argv[0], "\"", (char *) NULL);
		return TCL_ERROR;
	}
	    argc--, argv++;
	} else {
	    Tcl_AppendResult(interp, "unknown option \"", argv [0], "\"",
			     (char *) NULL);
            return TCL_ERROR;
	}
    }

    /*
     * No arguments left? Set the default values and return.
     */

    if (argc == 0) {
        if (actRetries >= 0) {
            control->retries = actRetries;
        }
        if (actTimeout > 0) {
            control->timeout = actTimeout;
        }
	if (actSize > 0) {
	    control->size = actSize;
	}
	if (actDelay >= 0) {
	    control->delay = actDelay;
	}
	if (actWindow >= 0) {
	    control->window = actWindow;
	}
        return TCL_OK;
    }

    /*
     * Now we should have at least two arguments left!
     */

    if (argc < 2) {
	goto icmpWrongArgs;
    }

    actRetries = actRetries < 0 ? control->retries : actRetries;
    actTimeout = actTimeout < 0 ? control->timeout : actTimeout;
    actSize  = actSize  < 0 ? control->size  : actSize;
    actDelay = actDelay < 0 ? control->delay : actDelay;
    actWindow = actWindow < 0 ? control->window : actWindow;

    /*
     * Get the query type.
     */

    if (strcmp(argv [0], "echo") == 0) {
        type = TNM_ICMP_TYPE_ECHO;
    } else if (strcmp(argv [0], "mask") == 0) {
        type = TNM_ICMP_TYPE_MASK;
    } else if (strcmp(argv [0], "timestamp") == 0) {
        type = TNM_ICMP_TYPE_TIMESTAMP;
    } else if (strcmp(argv [0], "ttl") == 0) {
        type = TNM_ICMP_TYPE_TRACE;
	argc--, argv++;
	if (argc < 2) {
	    goto icmpWrongArgs;
	}
	if (TnmGetPositive(interp, argv[0], &ttl) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (ttl > 255) {
	    Tcl_ResetResult(interp);
	    Tcl_AppendResult(interp, "expected ttl less than 256 but got \"",
			 argv[0], "\"", (char *) NULL);
	    return TCL_ERROR;
	}
    } else if (strcmp(argv [0], "trace") == 0) {
        type = TNM_ICMP_TYPE_TRACE;
	flags |= TNM_ICMP_FLAG_LASTHOP;
	argc--, argv++;
	if (argc < 2) {
	    goto icmpWrongArgs;
	}
	if (TnmGetUnsigned(interp, argv[0], &ttl) != TCL_OK) {
            return TCL_ERROR;
        }
    } else {
	Tcl_AppendResult(interp, "bad option \"", argv[0], "\": should be ",
			 "echo, mask, timestamp, ttl, or trace",
			 (char *) NULL);
	return TCL_ERROR;
    }
    argc--, argv++;

    /*
     * There should be one argument left which contains the list
     * of target IP asdresses. 
     */

    if (argc != 1) {
	goto icmpWrongArgs;
    }

    /*
     * Create and initialize an ICMP request structure.
     */

    icmpPtr = (TnmIcmpRequest *) ckalloc(sizeof(TnmIcmpRequest));
    memset((char *) icmpPtr, 0, sizeof(TnmIcmpRequest));
    
    icmpPtr->type = type;
    icmpPtr->ttl = ttl;
    icmpPtr->timeout = actTimeout;
    icmpPtr->retries = actRetries;
    icmpPtr->delay = actDelay;
    icmpPtr->size = actSize;
    icmpPtr->window = actWindow;
    icmpPtr->flags = flags;

    code = IcmpRequest(interp, argv[0], icmpPtr);
    ckfree((char *) icmpPtr);
    return code;
}
