/*
 * tnmUdp.c --
 *
 *	This is the implementation of the udp command that allows
 *	to send and receive udp datagrams. This implementation is
 *	supposed to be thread-safe.
 *
 * Copyright (c) 1993-1996 Technical University of Braunschweig.
 * Copyright (c) 1996-1997 University of Twente.
 * Copyright (c) 1997-1999 Technical University of Braunschweig.
 *
 * 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"

/*
 * A structure to describe an open UDP socket.
 */

typedef struct Socket {
    char name[12];		/* The name of the udp handle.	    */
    struct sockaddr_in client;  /* The client we are connected to.  */
    int sock;			/* The socket we are using.	    */
    char *readBinding;		/* Command to execute if readable.  */
    char *writeBinding;		/* Command to execute if writeable. */
    Tcl_Interp *interp;		/* Interpreter used for events.	    */
} Socket;
/* 
 * The hash table used to map udp handles to Socket pointer.
 */

static Tcl_HashTable udpTable;
static int initialized = 0;

/*
 * Mutex used to serialize access to static variables in this module.
 */

TCL_DECLARE_MUTEX(udpMutex)

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

static void
UdpEventProc	_ANSI_ARGS_((ClientData clientData, int mask));

static Socket*
UdpSocket	_ANSI_ARGS_((Tcl_Interp *interp, char *fileId));

static int
UdpOpen		_ANSI_ARGS_((Tcl_Interp *interp, int argc, char **argv));

static int
UdpConnect	_ANSI_ARGS_((Tcl_Interp *interp, int argc, char **argv));

static int
UdpSend		_ANSI_ARGS_((Tcl_Interp *interp, int argc, char **argv));

static int
UdpReceive	_ANSI_ARGS_((Tcl_Interp *interp, int argc, char **argv));

static int
UdpClose	_ANSI_ARGS_((Tcl_Interp *interp, int argc, char **argv));

#ifdef HAVE_MULTICAST
static int
UdpMulticast	_ANSI_ARGS_((Tcl_Interp *interp, int argc, char **argv));
#endif

static int
UdpInfo		_ANSI_ARGS_((Tcl_Interp *interp, int argc, char **argv));

static int
UdpBind		_ANSI_ARGS_((Tcl_Interp *interp, int argc, char **argv));

/*
 * Procedure to be called by the event dispatcher whenever a UDP 
 * socket gets readable or writable.
 */

static void
UdpEventProc(clientData, mask)
    ClientData clientData;
    int mask;
{
    Socket *usock = (Socket *) clientData;
    Tcl_Interp *interp = usock->interp;
    char **bindingPtr = NULL;
    int code;

    if (mask == TCL_READABLE && usock->readBinding) {
	bindingPtr = &(usock->readBinding);
    }

    if (mask == TCL_WRITABLE && usock->writeBinding) {
	bindingPtr = &(usock->writeBinding);
    }

    if (bindingPtr) {
	Tcl_AllowExceptions(interp);
	code = Tcl_GlobalEval(interp, *bindingPtr);
	if (code == TCL_ERROR) {
	    Tcl_AddErrorInfo(interp,
		"\n    (script bound to udp socket - binding deleted)");
	    Tcl_BackgroundError(interp);
	    TnmDeleteSocketHandler(usock->sock);
	}
    }
}

/*
 * Returns a Socket * pointer if name is a valid socket. We leave 
 * an error message in interp if name is not a valid socket.
 */

static Socket *
UdpSocket(interp, name)
    Tcl_Interp *interp;
    char *name;
{
    Tcl_HashEntry *entryPtr;
    Socket *usock;

    entryPtr = Tcl_FindHashEntry(&udpTable, name);
    if (entryPtr == NULL) {
	Tcl_AppendResult(interp, "bad udp handle \"", name, 
			  "\"", (char *) NULL);
	return (Socket *) NULL;
    }

    usock = (Socket *) Tcl_GetHashValue(entryPtr);

    return usock;
}

/*
 * Create a udp socket and create a tcl file handle for it.
 */

static int
UdpOpen(interp, argc, argv)
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int sock, isNew, code;
    struct sockaddr_in name;
    Socket *usock;
    char *port = "0";
    Tcl_HashEntry *entryPtr;

    if (argc < 2 || argc > 3) {
	TnmWrongNumArgs(interp, 2, argv, "?port?");
        return TCL_ERROR;
    }

    if (argc == 3) port = argv[2];

    name.sin_family = AF_INET;
    name.sin_addr.s_addr = htonl(INADDR_ANY);

    if (TnmSetIPPort(interp, "udp", port, &name) != TCL_OK) {
        return TCL_ERROR;
    }
	
    sock = TnmSocket(PF_INET, SOCK_DGRAM, 0);
    if (sock == TNM_SOCKET_ERROR) {
	Tcl_AppendResult(interp, "could not create socket: ", 
			 Tcl_PosixError(interp), (char *) NULL);
	return TCL_ERROR;
    }

    code = TnmSocketBind(sock, (struct sockaddr *) &name, sizeof(name));
    if (code == TNM_SOCKET_ERROR) {
	Tcl_AppendResult(interp, "can not bind socket on port \"", port,
			 "\": ", Tcl_PosixError(interp), (char *) NULL);
	TnmSocketClose(sock);
	return TCL_ERROR;
    }
    
    usock = (Socket *) ckalloc(sizeof(Socket));
    memset((char *) usock, 0, sizeof(Socket));

    sprintf(usock->name, "udp%d", initialized++);
    usock->client.sin_addr.s_addr = htonl(INADDR_ANY);
    usock->client.sin_port = 0;
    usock->sock = sock;

    entryPtr = Tcl_CreateHashEntry(&udpTable, usock->name, &isNew);
    Tcl_SetHashValue(entryPtr, (ClientData) usock);

    Tcl_SetResult(interp, usock->name, TCL_STATIC);
    return TCL_OK;
}

/*
 * Connect a udp socket to a remote server. This allows us to use tcl's
 * read, write and puts commands instead of the send and receive options
 * of the udp command.
 */

static int
UdpConnect(interp, argc, argv)
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int sock, isNew;
    Socket *usock;
    struct sockaddr_in name;
    Tcl_HashEntry *entryPtr;

    if (argc != 4) {
	TnmWrongNumArgs(interp, 2, argv, "host port");
        return TCL_ERROR;
    }

    if (TnmSetIPAddress(interp, argv[2], &name) != TCL_OK) {
	return TCL_ERROR;
    }

    if (TnmSetIPPort(interp, "udp", argv[3], &name) != TCL_OK) {
        return TCL_ERROR;
    }

    sock = TnmSocket(PF_INET, SOCK_DGRAM, 0);
    if (sock == TNM_SOCKET_ERROR) {
	Tcl_AppendResult(interp, "could not create socket: ", 
			 Tcl_PosixError(interp), (char *) NULL);
	return TCL_ERROR;
    }

    if (connect(sock, (struct sockaddr *) &name, sizeof(name)) < 0) {
	Tcl_AppendResult(interp, "can not connect to host \"", argv[2],
			 "\" using port \"", argv[3], "\": ", 
			 Tcl_PosixError(interp), (char *) NULL);
	TnmSocketClose(sock);
	return TCL_ERROR;
    }

    usock = (Socket *) ckalloc(sizeof(Socket));
    memset((char *) usock, 0, sizeof(Socket));

    sprintf(usock->name, "udp%d", initialized++);
    usock->client.sin_addr = name.sin_addr;
    usock->client.sin_port = name.sin_port;
    usock->sock = sock;

    entryPtr = Tcl_CreateHashEntry(&udpTable, usock->name, &isNew);
    Tcl_SetHashValue(entryPtr, (ClientData) usock);

    Tcl_SetResult(interp, usock->name, TCL_STATIC);
    return TCL_OK;
}

/*
 * Send a message using the udp socket file to a given host and port.
 */

static int
UdpSend(interp, argc, argv)
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    struct sockaddr_in name;
    Socket *usock;

    if (argc != 6 && argc != 4) {
	TnmWrongNumArgs(interp, 2, argv, "handle ?host port? string");
        return TCL_ERROR;
    }

    usock = UdpSocket(interp, argv[2]);
    if (usock == NULL) {
        return TCL_ERROR;
    }

    if (argc == 6 && usock->client.sin_addr.s_addr != htonl(INADDR_ANY)) {
	TnmWrongNumArgs(interp, 2, argv, "handle string");
        return TCL_ERROR;
    }

    if (argc == 4 && usock->client.sin_addr.s_addr == htonl(INADDR_ANY)) {
	TnmWrongNumArgs(interp, 2, argv, "handle host port string");
        return TCL_ERROR;
    }

    if (argc == 6) {

	int len;
	
	if (TnmSetIPAddress(interp, argv[3], &name) != TCL_OK) {
	    return TCL_ERROR;
	}

	if (TnmSetIPPort(interp, "udp", argv[4], &name) != TCL_OK) {
	    return TCL_ERROR;
	}

	len = TnmSocketSendTo(usock->sock, argv[5], strlen(argv[5]), 0, 
			      (struct sockaddr *) &name, sizeof(name));
	if (len == TNM_SOCKET_ERROR) {
	    Tcl_AppendResult(interp, "udp send to host \"", argv[3], 
			     "\" port \"", argv[4], "\" failed: ",
			     Tcl_PosixError(interp), (char *) NULL);
	    return TCL_ERROR;
	}

    } else {

	if (send(usock->sock, argv[3], strlen(argv[3]), 0) < 0) {
	    Tcl_AppendResult(interp, "udp send failed: ", 
			     Tcl_PosixError(interp), (char *) NULL);
	    return TCL_ERROR;
	}

    }
    

    return TCL_OK;
}

/*
 * Receive a message from a udp handle and return the sending hostname,
 * its port number and the message in a tcl list.
 */

static int
UdpReceive(interp, argc, argv)
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Socket *usock;
#define BUFSIZE 8192
    char msg[BUFSIZE];
    char *startPtr, *scanPtr;
    char buf[80];
    int clen, len;
    struct sockaddr_in client;
    Tcl_DString tclString;

    if (argc != 3) {
	TnmWrongNumArgs(interp, 2, argv, "handle");
        return TCL_ERROR;
    }

    usock = UdpSocket(interp, argv[2]);
    if (usock == NULL) {
        return TCL_ERROR;
    }

    clen = sizeof(client);
    len = TnmSocketRecvFrom(usock->sock, msg, BUFSIZE, 0, 
			    (struct sockaddr *) &client, &clen);
    if (len == TNM_SOCKET_ERROR) {
	Tcl_AppendResult(interp, "receive failed on \"", argv[2], "\": ", 
			 Tcl_PosixError(interp), (char *) NULL);
	return TCL_ERROR;
    }

    Tcl_DStringInit(&tclString);
    startPtr = msg;
    for (scanPtr = startPtr; scanPtr < msg + len; scanPtr++) {
	if ((isalnum(*scanPtr) || ispunct(*scanPtr) || isspace(*scanPtr))
	    && *scanPtr != '\\') {
	    continue;
	}
	Tcl_DStringAppend(&tclString, startPtr, scanPtr - startPtr);
        startPtr = scanPtr + 1;
	if (*scanPtr == '\\') {
	    Tcl_DStringAppend(&tclString, "\\\\", 2);
	} else {
	    Tcl_DStringAppend(&tclString, "\\x", 2);
	    sprintf(buf, "%02x", *scanPtr);
	    Tcl_DStringAppend(&tclString, buf, -1);
	}
    }
    Tcl_DStringAppend(&tclString, startPtr, scanPtr - startPtr);

    sprintf(buf, "%d", (int) ntohs(client.sin_port));
    Tcl_AppendElement(interp, inet_ntoa(client.sin_addr));
    Tcl_AppendElement(interp, buf);
    Tcl_AppendElement(interp, Tcl_DStringValue(&tclString));
    Tcl_DStringFree(&tclString);

    return TCL_OK;
}

/*
 * Close the udp socket. This done by removing the entry in our socket 
 * list and calling the tcl close command to do the dirty job.
 */

static int
UdpClose(interp, argc, argv)
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Tcl_HashEntry *entryPtr;
    Socket *usock;

    if (argc != 3) {
	TnmWrongNumArgs(interp, 2, argv, "handle");
        return TCL_ERROR;
    }
 
    usock = UdpSocket(interp, argv[2]);
    if (usock == NULL) {
        return TCL_ERROR;
    }

    entryPtr = Tcl_FindHashEntry(&udpTable, argv[2]);
    if (entryPtr == NULL) {
        Tcl_AppendResult(interp, "bad udp handle \"", argv[2], 
			 "\"", (char *) NULL);
	return TCL_ERROR;
    }
    Tcl_DeleteHashEntry(entryPtr);

    TnmDeleteSocketHandler(usock->sock);
    TnmSocketClose(usock->sock);
    if (usock->readBinding) {
	ckfree(usock->readBinding);
    }
    if (usock->writeBinding) {
	ckfree(usock->writeBinding);
    }
    ckfree((char *) usock);

    return TCL_OK;
}

/*
 * Get some information about all open udp sockets. If called with
 * no argument, return a list of all opened udp sockets. If called
 * with a tcl file handle, return detailed information regarding
 * this handle.
 */

static int
UdpInfo(interp, argc, argv)
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Socket *usock;

    if (argc < 2 || argc > 3) {
	TnmWrongNumArgs(interp, 2, argv, "?handle?");
        return TCL_ERROR;
    }

    if (argc == 2) {

        Tcl_HashEntry *entryPtr;
	Tcl_HashSearch search;

        entryPtr = Tcl_FirstHashEntry(&udpTable, &search);
	while (entryPtr) {
	    Tcl_AppendElement(interp, Tcl_GetHashKey(&udpTable, entryPtr));
	    entryPtr = Tcl_NextHashEntry(&search);
	}

    } else {
	
	int sock, rc;
	struct sockaddr_in server;
	int length = sizeof(server);
	Tcl_Obj *objPtr = Tcl_GetObjResult(interp);

        usock = UdpSocket(interp, argv[2]);
        if (usock == NULL) {
            return TCL_ERROR;
        }

	sock = usock->sock;
	rc = getsockname(sock, (struct sockaddr *) &server, &length);
	if (rc == 0) {
	    Tcl_ListObjAppendElement(interp, objPtr,
			     TnmNewIpAddressObj(&server.sin_addr));
	    Tcl_ListObjAppendElement(interp, objPtr,
			     Tcl_NewIntObj((int) ntohs(server.sin_port)));
	} else {
	    Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewObj());
	    Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewObj());
	}
	Tcl_ListObjAppendElement(interp, objPtr,
			 TnmNewIpAddressObj(&usock->client.sin_addr));
	Tcl_ListObjAppendElement(interp, objPtr,
			 Tcl_NewIntObj((int) ntohs(usock->client.sin_port)));
    }

    return TCL_OK;
}

/*
 * Bind a command to a UDP handle which will be executed if the
 * socket is readable or writeable.
 */

static int
UdpBind(interp, argc, argv)
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int mask = 0;
    Socket *usock;
    char **bindingPtr = NULL;

    if (argc < 4 || argc > 5) {
	TnmWrongNumArgs(interp, 2, argv, "handle event ?script?");
        return TCL_ERROR;
    }

    usock = UdpSocket(interp, argv[2]);
    if (usock == NULL) {
        return TCL_ERROR;
    }

    if (strcmp(argv[3], "readable") == 0) {
	mask = TCL_READABLE;
	bindingPtr = &(usock->readBinding);
    } else if (strcmp(argv[3], "writable") == 0) {
	mask = TCL_WRITABLE;
        bindingPtr = &(usock->writeBinding);
    } else {
	Tcl_AppendResult(interp, "unkown event \"", argv[3],
			 "\": should be readable or writable", (char *) NULL);
	return TCL_ERROR;
    }

    if (argc == 5) {
	if (*bindingPtr) {
	    ckfree(*bindingPtr);
	    *bindingPtr = NULL;
	}
	if (argv[4][0] != '\0') {
	    *bindingPtr = ckstrdup(argv[4]);
	}
    }
    if (*bindingPtr) {
	Tcl_SetResult(interp, *bindingPtr, TCL_STATIC);
    }

    if (argc == 5 && (usock->readBinding || usock->writeBinding)) {
	usock->interp = interp;
	TnmCreateSocketHandler(usock->sock, mask,
			       UdpEventProc, (ClientData) usock);
    } else {
	TnmDeleteSocketHandler(usock->sock);
    }

    return TCL_OK;
}

#ifdef HAVE_MULTICAST
static int
UdpMulticast(interp, argc, argv)
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Socket* usock;
    struct ip_mreq mreq;
    struct sockaddr_in name;
    int tmp, optlen, sock, isNew, code;
    unsigned char ttl;
    Tcl_HashEntry *entryPtr;
    
    if (argc < 3) {
	TnmWrongNumArgs(interp, 2, argv, "option ?args ...?");
        return TCL_ERROR;
    }

    if (strcmp(argv[2], "ttl") == 0) {

	if (argc < 4 || argc > 5) {
	    TnmWrongNumArgs(interp, 2, argv, "ttl handle ?value?");
	    return TCL_ERROR;
	}

        usock = UdpSocket(interp, argv[3]);
        if (usock == NULL) {
            return TCL_ERROR;
        }
	sock = usock->sock;
	if (argc == 5) {
            if (Tcl_GetInt(interp, argv[4], &tmp) != TCL_OK)
                return TCL_ERROR;
            ttl = tmp;
            if (setsockopt(usock->sock, IPPROTO_IP, IP_MULTICAST_TTL, 
			   (char*) &ttl, sizeof(ttl)) == -1) {
                Tcl_AppendResult(interp, "can't set multicast ttl: ",
                                 Tcl_PosixError(interp), (char *) NULL);
                return TCL_ERROR;
            }
            return TCL_OK;
        }

	optlen = sizeof(ttl);
	if (getsockopt(usock->sock, IPPROTO_IP, IP_MULTICAST_TTL, (char*) &ttl,
		       &optlen) == -1) {
	    Tcl_AppendResult(interp, "can't get multicast ttl: ",
			     Tcl_PosixError(interp), (char *) NULL);
	    return TCL_ERROR;
	}
	tmp = ttl;
	Tcl_SetIntObj(Tcl_GetObjResult(interp), tmp);
	return TCL_OK;

    } else if (strcmp(argv[2], "open") == 0) {

	if (argc < 5 || argc > 6) {
	    TnmWrongNumArgs(interp, 2, argv,
			    "open multicast-address port ?interface-address?");
	    return TCL_ERROR;
	}

	if (TnmSetIPAddress(interp, argv[3], &name) != TCL_OK) {
	    return TCL_ERROR;
	}

	if (TnmSetIPPort(interp, "udp", argv[4], &name) != TCL_OK) {
	    return TCL_ERROR;
	}

	mreq.imr_multiaddr.s_addr = name.sin_addr.s_addr;
	name.sin_addr.s_addr = htonl(INADDR_ANY);

	sock = TnmSocket(PF_INET, SOCK_DGRAM, 0);
	if (sock == TNM_SOCKET_ERROR) {
	    Tcl_AppendResult(interp, "could not create socket: ", 
			     Tcl_PosixError(interp), (char *) NULL);
	    return TCL_ERROR;
	}

	if ((mreq.imr_multiaddr.s_addr == -1 ||
	     !IN_MULTICAST(ntohl(mreq.imr_multiaddr.s_addr)))) {
	    Tcl_AppendResult(interp, "bad multicast address \"",
			     argv[3], "\"", (char *) NULL);
	    TnmSocketClose(sock);
	    return TCL_ERROR;
	}
	if (argc == 6) {
	    struct sockaddr_in ifaddr;
	    if (TnmSetIPAddress(interp, argv[5], &ifaddr) != TCL_OK) {
		TnmSocketClose(sock);
		return TCL_ERROR;
	    }
	    mreq.imr_interface.s_addr = ifaddr.sin_addr.s_addr;
	} else {
	    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
	}

	if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &mreq,
		       sizeof(mreq)) == -1) {
	    Tcl_AppendResult(interp, "multicast IP membership add failed: ",
			     Tcl_PosixError(interp), (char *) NULL);
	    TnmSocketClose(sock);
	    return TCL_ERROR;
	}

#ifdef SO_REUSEADDR

	/*
	 * Allow others to bind to the same UDP port.
	 */

	{
	    int one = 1;
	    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
		       (char *) &one, sizeof(one));
	}
#endif

	code = TnmSocketBind(sock, (struct sockaddr*) &name, sizeof(name));
	if (code == TNM_SOCKET_ERROR) {
 	    Tcl_AppendResult(interp, "can not bind socket: ",
			     Tcl_PosixError(interp), (char *) NULL);
	    TnmSocketClose(sock);
	    return TCL_ERROR;
	}

	usock = (Socket *) ckalloc(sizeof(Socket));
	memset((char *) usock, 0, sizeof(Socket));
	
	sprintf(usock->name, "udp%d", initialized++);
	usock->client.sin_addr.s_addr = htonl(INADDR_ANY);
	usock->client.sin_port = 0;
	usock->sock = sock;
	
	entryPtr = Tcl_CreateHashEntry(&udpTable, usock->name, &isNew);
	Tcl_SetHashValue(entryPtr, (ClientData) usock);
	
	Tcl_SetResult(interp, usock->name, TCL_STATIC);

	return TCL_OK;
    }

    Tcl_AppendResult(interp, "bad multicast option \"", argv[2],
		     "\": should be ttl, or open", (char *) NULL);
    return TCL_ERROR;
}
#endif

/*
 * This is the udp command as described in the scotty documentation.
 * It simply dispatches to the C functions implementing the options
 * understood by the udp command.
 */

int
Tnm_UdpCmd(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int cmd, result = TCL_OK;

    enum commands { 
	cmdBind, cmdClose, cmdConnect, cmdInfo, 
#ifdef HAVE_MULTICAST
	cmdMulticast,
#endif
	cmdOpen, cmdReceive, cmdSend
    };

    static TnmTable cmdTable[] = {
	{ cmdBind,	"bind" },
	{ cmdClose,	"close" },
	{ cmdConnect,	"connect" },
	{ cmdInfo,	"info" },
#ifdef HAVE_MULTICAST
	{ cmdMulticast,	"multicast" },
#endif
	{ cmdOpen,	"open" },
	{ cmdReceive,	"receive" },
	{ cmdSend,	"send" },
	{ 0, NULL }
    };

    if (argc < 2) {
	TnmWrongNumArgs(interp, 1, argv, "option ?arg arg ...?");
	return TCL_ERROR;
    }

    Tcl_MutexLock(&udpMutex);
    if (! initialized) {
	Tcl_InitHashTable(&udpTable, TCL_STRING_KEYS);
	initialized = 1;
    }
    Tcl_MutexUnlock(&udpMutex);

    cmd = TnmGetTableKey(cmdTable, argv[1]);
    if (cmd == -1) {
	TnmBadOption(interp, argv[1], TnmGetTableValues(cmdTable));
	return TCL_ERROR;
    }

    Tcl_MutexLock(&udpMutex);
    switch ((enum commands) cmd) {
    case cmdBind:
        result = UdpBind(interp, argc, argv);
	break;
    case cmdClose:
        result = UdpClose(interp, argc, argv);
	break;
    case cmdConnect:
        result = UdpConnect(interp, argc, argv);
	break;
    case cmdInfo:
        result = UdpInfo(interp, argc, argv);
	break;
#ifdef HAVE_MULTICAST
    case cmdMulticast:
	result = UdpMulticast(interp, argc, argv);
	break;
#endif
    case cmdOpen:
	result = UdpOpen(interp, argc, argv);
	break;
    case cmdReceive:
        result = UdpReceive(interp, argc, argv);
	break;
    case cmdSend:
        result = UdpSend(interp, argc, argv);
	break;
    }

    Tcl_MutexUnlock(&udpMutex);
    return result;
}
