#include "cmPrefix.h"
#include "cmGlobal.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmThread.h"

#include <sys/socket.h> 
#include <netinet/in.h>	
#include <arpa/inet.h>	
#include <fcntl.h>		
#include <unistd.h>  // close

#include "cmUdpPort.h"

#define cmUdp_SYS_ERR (-1)
#define cmUdp_NULL_SOCK (-1)

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX  _POSIX_HOST_NAME_MAX
#endif

enum
{
  kIsConnectedUdpFl = 0x01,
  kIsBlockingUdpFl  = 0x02,
  kQueueingUdpFl    = 0x04
};


typedef struct 
{
  cmErr_t         err;
  int             sockH;
  cmUdpCallback_t cbFunc;
  void*           cbArg;
  unsigned        timeOutMs;
  unsigned        flags;
  cmThreadH_t     thH;
  cmTs1p1cH_t     qH;
  unsigned        recvBufByteCnt;
  char*           tempBuf;
  unsigned        timeOutCnt;
  unsigned        recvCnt;
  unsigned        queCbCnt;
  unsigned        errCnt;
  struct sockaddr_in sockaddr;
  cmChar_t        ntopBuf[ INET_ADDRSTRLEN+1 ]; // use INET6_ADDRSTRLEN for IPv6
  cmChar_t        hnameBuf[ HOST_NAME_MAX+1 ];
} cmUdp_t;

cmUdpH_t cmUdpNullHandle = cmSTATIC_NULL_HANDLE;

#define _cmUdpClear_errno() errno = 0

cmUdp_t* _cmUdpHandleToPtr( cmUdpH_t h )
{
  cmUdp_t* p = (cmUdp_t*)h.h;
  assert(p != NULL);
  return p;
}

cmUdpRC_t _cmUdpFinal( cmUdp_t* p )
{
  cmUdpRC_t rc = kOkUdpRC;

  if( cmThreadIsValid(p->thH) )
    if( cmThreadDestroy(&p->thH) != kOkThRC )
      return cmErrMsg(&p->err,kThreadFailUdpRC,"Listener thread destroy failed.");

  if( cmTs1p1cIsValid(p->qH) )
    if( cmTs1p1cDestroy(&p->qH) != kOkThRC )
      cmErrMsg(&p->err,kQueueFailUdpRC,"Receive data queue destroy failed.");

  cmMemPtrFree(&p->tempBuf);

  // close the socket		
	if( p->sockH != cmUdp_NULL_SOCK )
	{
    _cmUdpClear_errno();
		if( close(p->sockH) != 0 )
			cmErrSysMsg(&p->err,kSockCloseFailUdpRC,errno,"The socket close failed." );
			
		p->sockH = cmUdp_NULL_SOCK;		
	}


  return rc;
}

cmUdpRC_t _cmUdpFree( cmUdp_t* p )
{
  cmUdpRC_t rc;
  if((rc = _cmUdpFinal(p)) != kOkUdpRC )
    return rc;

  cmMemFree(p);
  return rc;  
}

cmUdpRC_t _cmUdpInitAddr( cmUdp_t* p, const char* addrStr, cmUdpPort_t portNumber, struct sockaddr_in* retAddrPtr )
{
	memset(retAddrPtr,0,sizeof(struct sockaddr_in));

  if( portNumber == kInvalidUdpPortNumber )
    return cmErrMsg(&p->err,kInvalidPortNumbUdpRC,"The port number %i cannot be used.",kInvalidUdpPortNumber);
	
	if( addrStr == NULL )
		retAddrPtr->sin_addr.s_addr 	= htonl(INADDR_ANY);
	else
	{
    _cmUdpClear_errno();

		if(inet_pton(AF_INET,addrStr,&retAddrPtr->sin_addr) == 0 )
      //if(( retAddrPtr->sin_addr.s_addr 	= inet_addr(addrStr)) == INADDR_NONE )
			return cmErrSysMsg(&p->err,kPtoNFailUdpRC,errno, "The network address string '%s' could not be converted to a netword address structure.",cmStringNullGuard(addrStr) );
	}
	
	//retAddrPtr->sin_len 			= sizeof(struct sockaddr_in);
	retAddrPtr->sin_family 		= AF_INET;
	retAddrPtr->sin_port 			= htons(portNumber);
	
	return kOkUdpRC;	
}

cmUdpRC_t _cmUdpConnect( cmUdp_t* p, const char* remoteAddr, cmUdpPort_t remotePort )
{
  struct sockaddr_in addr;
  cmUdpRC_t          rc;

  // create the remote address		
  if((rc = _cmUdpInitAddr(p, remoteAddr, remotePort,  &addr )) != kOkUdpRC )
    return rc;
		
  _cmUdpClear_errno();

  // ... and connect this socket to the remote address/port
  if( connect(p->sockH, (struct sockaddr*)&addr, sizeof(addr)) == cmUdp_SYS_ERR )
    return cmErrSysMsg(&p->err,kSockConnectFailUdpRC, errno, "Socket connect failed." );

  p->flags = cmSetFlag(p->flags,kIsConnectedUdpFl);

  return rc;
}

cmUdpRC_t cmUdpAlloc( cmCtx_t* ctx, cmUdpH_t* hp )
{
  cmUdpRC_t rc;

  if((rc = cmUdpFree(hp)) != kOkUdpRC )
    return rc;

  cmUdp_t* p = cmMemAllocZ(cmUdp_t,1);

  cmErrSetup(&p->err,&ctx->rpt,"UDP Port");

  p->sockH = cmUdp_NULL_SOCK;

  hp->h = p;

  return rc;
}

cmUdpRC_t cmUdpFree( cmUdpH_t* hp )
{
  cmUdpRC_t rc = kOkUdpRC;
   
  if( hp == NULL || cmUdpIsValid(*hp)==false)
    return rc;

  cmUdp_t* p = _cmUdpHandleToPtr(*hp);

  if((rc = _cmUdpFree(p)) != kOkUdpRC )
    return rc;

  hp->h = NULL;
    
  return rc;
}



cmUdpRC_t cmUdpInit( 
  cmUdpH_t       h, 
  cmUdpPort_t     port,
  unsigned        flags,
  cmUdpCallback_t cbFunc, 
  void*           cbArg,
  const char*     remoteAddr,
  cmUdpPort_t     remotePort,
  unsigned        recvBufByteCnt,
  unsigned        timeOutMs )
{
  cmUdpRC_t rc;

  cmUdp_t* p = _cmUdpHandleToPtr(h);

  if((rc = _cmUdpFinal(p)) != kOkUdpRC )
    return rc;

  _cmUdpClear_errno();

  // get a handle to the socket
  if(( p->sockH = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == cmUdp_SYS_ERR )
    return cmErrSysMsg(&p->err, kSockCreateFailUdpRC, errno, "Socket create failed." );	 
	
  // create the local address		
  if((rc = _cmUdpInitAddr(p, NULL, port,  &p->sockaddr )) != kOkUdpRC )
    goto errLabel;
			
  // bind the socket to a local address/port	
  if( (bind( p->sockH, (struct sockaddr*)&p->sockaddr, sizeof(p->sockaddr))) == cmUdp_SYS_ERR )
  {
    rc = cmErrSysMsg(&p->err,kSockBindFailUdpRC,errno,"Socket bind failed." );
    goto errLabel;
  }

  // if a remote addr was given connect this socket to it
  if( remoteAddr != NULL )
    if((rc = _cmUdpConnect(p,remoteAddr,remotePort)) != kOkUdpRC )
      goto errLabel;

  // if this socket should block
  if( cmIsFlag(flags,kBlockingUdpFl) )
  {
    struct timeval 		timeOut;
    
    // set the socket time out 
    timeOut.tv_sec 	= timeOutMs/1000;
    timeOut.tv_usec = (timeOutMs - (timeOut.tv_sec * 1000)) * 1000;
		
    if( setsockopt( p->sockH, SOL_SOCKET, SO_RCVTIMEO, &timeOut, sizeof(timeOut) ) == cmUdp_SYS_ERR )
    {
      rc = cmErrSysMsg(&p->err,kSockOptSetFailUdpRC,errno, "Attempt to set the socket timeout failed." );
      goto errLabel;
    }

    p->flags = cmSetFlag(p->flags,kIsBlockingUdpFl);

  }
  else
  {

    int opts;
		
    // get the socket options flags
    if( (opts = fcntl(p->sockH,F_GETFL)) < 0 )
    {
      rc = cmErrSysMsg(&p->err,kSockOptSetFailUdpRC,errno, "Attempt to get the socket options flags failed." );
      goto errLabel;
    }
	    
    opts = (opts | O_NONBLOCK);
		
    // set the socket options flags
    if(fcntl(p->sockH,F_SETFL,opts) < 0) 
    {
      rc = cmErrSysMsg(&p->err,kSockOptSetFailUdpRC,errno, "Attempt to set the socket to non-blocking failed." );
      goto errLabel;
    }	    

  }

  // if broadcast option was requested.
  if( cmIsFlag(flags,kBroadcastUdpFl) )
  {
    int bcastFl = 1;
    if( setsockopt( p->sockH, SOL_SOCKET, SO_BROADCAST, &bcastFl, sizeof(bcastFl) ) == cmUdp_SYS_ERR )
    {
      rc = cmErrSysMsg(&p->err,kSockOptSetFailUdpRC,errno, "Attempt to set the socket broadcast attribute failed." );
      goto errLabel;
    }

  }

  if( recvBufByteCnt != 0 )
    p->tempBuf = cmMemAlloc(char,recvBufByteCnt );

  p->timeOutMs      = timeOutMs;
  p->cbFunc         = cbFunc;
  p->cbArg          = cbArg;
  p->recvBufByteCnt = recvBufByteCnt;
  p->timeOutCnt     = 0;
  p->recvCnt        = 0;
  p->queCbCnt       = 0;
  p->errCnt         = 0;

  if( cmIsFlag(flags,kNoQueueUdpFl) == false )
    p->flags = cmSetFlag(p->flags,kQueueingUdpFl);

 errLabel:
  if( rc != kOkUdpRC )
    _cmUdpFree(p);

  return rc;
}

cmUdpRC_t cmUdpFinal( cmUdpH_t h )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);
  return _cmUdpFinal(p);
}

bool      cmUdpIsValid( cmUdpH_t h )
{ return h.h != NULL; }

const struct sockaddr_in* cmUdpLocalAddr( cmUdpH_t h )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);
  return &p->sockaddr;
}



cmUdpRC_t cmUdpConnect( cmUdpH_t h, const char* remoteAddr, cmUdpPort_t remotePort )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);
  return _cmUdpConnect(p,remoteAddr,remotePort);
}

cmUdpRC_t cmUdpSend(    cmUdpH_t h, const char* data, unsigned dataByteCnt )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);

  _cmUdpClear_errno();

  if( cmIsFlag(p->flags,kIsConnectedUdpFl) == false )
    return cmErrMsg(&p->err,kNotConnectedUdpRC,"cmUdpSend() only works with connected sockets.");

  if( send( p->sockH, data, dataByteCnt, 0 ) == cmUdp_SYS_ERR )
    return cmErrSysMsg(&p->err,kSockSendFailUdpRC,errno,"Send failed.");

  return kOkUdpRC;
}

cmUdpRC_t cmUdpSendTo(  cmUdpH_t h, const char* data, unsigned dataByteCnt, const struct sockaddr_in* remoteAddr )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);
  
  _cmUdpClear_errno();
   
  if( sendto(p->sockH, data, dataByteCnt, 0, (struct sockaddr*)remoteAddr, sizeof(*remoteAddr)) == cmUdp_SYS_ERR )
    return cmErrSysMsg(&p->err,kSockSendFailUdpRC,errno,"SendTo failed.");

  return kOkUdpRC;
}

cmUdpRC_t cmUdpSend2(  cmUdpH_t h, const char* data, unsigned dataByteCnt, const char* remoteAddr, cmUdpPort_t remotePort )
{
  cmUdpRC_t rc;
  cmUdp_t* p = _cmUdpHandleToPtr(h);
  struct sockaddr_in addr;

  if((rc = _cmUdpInitAddr(p,remoteAddr,remotePort,&addr)) != kOkUdpRC )
    return rc;
  
  return cmUdpSendTo( h, data, dataByteCnt, &addr );
}

cmUdpRC_t cmUdpRecv(    cmUdpH_t h, char* data, unsigned dataByteCnt, struct sockaddr_in* fromAddr, unsigned* recvByteCntPtr )
{
  cmUdp_t*  p                = _cmUdpHandleToPtr(h);
  cmUdpRC_t rc               = kOkUdpRC;
  ssize_t   retVal           = 0;
	socklen_t sizeOfRemoteAddr = fromAddr==NULL ? 0 : sizeof(struct sockaddr_in);
	
  _cmUdpClear_errno();

  if( recvByteCntPtr != NULL )
    *recvByteCntPtr = 0;
	
	if((retVal = recvfrom(p->sockH, data, dataByteCnt, 0, (struct sockaddr*)fromAddr, &sizeOfRemoteAddr )) == cmUdp_SYS_ERR )
    return cmErrSysMsg(&p->err,kSockRecvFailUdpRC,errno,"recvFrom() failed.");
	
  if( recvByteCntPtr != NULL )
    *recvByteCntPtr = retVal;

  return rc;
}

bool _cmUdpThreadCb(void* param)
{
  cmUdp_t*              p = (cmUdp_t*)param;
	fd_set                rdSet;
	struct timeval        timeOut;

  // setup the select() call
  FD_ZERO(&rdSet);
  FD_SET(p->sockH, &rdSet );
		
  timeOut.tv_sec 	= p->timeOutMs/1000;
  timeOut.tv_usec = (p->timeOutMs - (timeOut.tv_sec * 1000)) * 1000;
	
  // NOTE; select() takes the highest socket value plus one of all the sockets in all the sets.
		
  switch( select(p->sockH+1,&rdSet,NULL,NULL,&timeOut) )
  {
    case -1: 	// error
      if( errno != EINTR )
        cmErrSysMsg(&p->err,kSockSelectFailUdpRC,errno,"Select failed.");
      ++p->errCnt;
      break;
			
    case 0: 	// select() timed out	
      ++p->timeOutCnt;
      break;
			
    case 1: 	// (> 0) count of ready descripters
      if( FD_ISSET(p->sockH,&rdSet) )
      {
        struct	sockaddr_in remoteAddr;
        socklen_t           addrByteCnt = sizeof(remoteAddr);
        ssize_t             retByteCnt;

        _cmUdpClear_errno();

        ++p->recvCnt;

        // recv the incoming msg into p->tempBuf[]
        if(( retByteCnt = recvfrom( p->sockH, p->tempBuf, p->recvBufByteCnt, 0, (struct sockaddr*)&remoteAddr, &addrByteCnt )) == cmUdp_SYS_ERR )
          cmErrSysMsg(&p->err,kSockRecvFailUdpRC,errno,"recvfrom() failed.");
        else
        {
          // check for overflow
          if( retByteCnt == p->recvBufByteCnt )
            cmErrMsg(&p->err,kRecvBufOverflowUdpRC,"The receive buffer requires more than %i bytes.",p->recvBufByteCnt);
          else
          {
            // if queueing is enabled
            if( cmIsFlag(p->flags,kQueueingUdpFl ) )
            {
              // enqueue the msg - with the source address appended after the data
              const void*    msgPtrArray[]     = { p->tempBuf, &remoteAddr, p->tempBuf };
              unsigned msgByteCntArray[] = { retByteCnt, sizeof(remoteAddr)  };
              if( cmTs1p1cEnqueueSegMsg( p->qH, msgPtrArray, msgByteCntArray, 2 ) != kOkThRC )
                cmErrMsg(&p->err,kQueueFailUdpRC,"A received msg containing %i bytes was not queued.",retByteCnt);
            }
            else // if queueing is not enabled - transmit the data directly via the callback
              if( p->cbFunc != NULL )
              {
                p->cbFunc(p->cbArg,p->tempBuf,retByteCnt,&remoteAddr);
              }
          }
        }					
      }	
      break;
			
    default:
      { assert(0); }
  } // switch


  return true;
}

cmRC_t _cmUdpQueueCb(void* userCbPtr, unsigned msgByteCnt, const void* msgDataPtr )
{
  cmUdp_t* p = (cmUdp_t*)userCbPtr;

  if( p->cbFunc != NULL )
  {
    struct sockaddr_in addr;

    assert( msgByteCnt >= sizeof(addr));

    const char* dataPtr = (const char*)msgDataPtr;

    // the address of the data source is apppended to the data bytes.
    const char* addrPtr = dataPtr + msgByteCnt - sizeof(addr);
    memcpy(&addr,addrPtr,sizeof(addr));  

    // make the receive callback
    p->cbFunc(p->cbArg,dataPtr,msgByteCnt-sizeof(addr),&addr);

    ++p->queCbCnt;
  }

  return cmOkRC;
}

cmUdpRC_t cmUdpEnableListen( cmUdpH_t h, bool enableFl )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);

  if( cmThreadIsValid(p->thH) == false && enableFl == true)
  {
    if(cmThreadCreate(&p->thH,_cmUdpThreadCb,p,p->err.rpt) != kOkThRC )
      return cmErrMsg(&p->err,kThreadFailUdpRC,"Listener thread create failed.");

    if(cmTs1p1cCreate(&p->qH,p->recvBufByteCnt,_cmUdpQueueCb,p,p->err.rpt) != kOkThRC )
      return cmErrMsg(&p->err,kQueueFailUdpRC,"Listener data queue create failed.");
  }

  if( cmThreadIsValid(p->thH) )
    if( cmThreadPause( p->thH, enableFl ? 0 : kPauseThFl ) != kOkThRC )
      return cmErrMsg(&p->err,kThreadFailUdpRC,"The listener thread failed to %s.", enableFl ? "pause" : "un-pause" );

  return kOkUdpRC;    
}

bool      cmUdpIsQueueEnabled( cmUdpH_t h )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);
  return cmIsFlag(p->flags,kQueueingUdpFl);
}

void      cmUdpQueueEnable( cmUdpH_t h, bool enableFl )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);
  p->flags = cmSetFlag(p->flags,kQueueingUdpFl);
}


unsigned  cmUdpAvailDataByteCount( cmUdpH_t h )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);
  return cmTs1p1cIsValid(p->qH) ? cmTs1p1cDequeueMsgByteCount( p->qH ) : 0;
}

cmUdpRC_t cmUdpGetAvailData( cmUdpH_t h, char* data, unsigned* dataByteCntPtr, struct sockaddr_in* fromAddr )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);

  unsigned availByteCnt;
    
  // if a received msg is queued
  if( (availByteCnt = cmTs1p1cAvailByteCount(p->qH)) > 0 )
  {
    // all msg's must have at least a source address
    assert( availByteCnt >= sizeof(*fromAddr) );

    // get the size of the return buffer (or 0 if there is no return buffer)
    unsigned dataByteCnt = (data != NULL && dataByteCntPtr != NULL) ? *dataByteCntPtr : 0;

    if( dataByteCnt == 0 )
      data = NULL;

    // dequeue the msg - if data==NULL then the data will be returned by
    // a call to the callback function provided in cmUdpAlloc().
    if( cmTs1p1cDequeueMsg(p->qH, data, dataByteCnt ) != kOkThRC )
      return cmErrMsg(&p->err,kQueueFailUdpRC,"Data dequeue failed.");
      
    // if a return buffer was given
    if( data != NULL )
    {
      assert( dataByteCntPtr != NULL );
        
      // the source address is appended to the end of the data 
      const char* addrPtr = data + availByteCnt - sizeof(*fromAddr);

      // copy out the source address
      if( fromAddr != NULL )
        memcpy(fromAddr,addrPtr,sizeof(*fromAddr));

      // subtract the address size from the total msg size
      *dataByteCntPtr = availByteCnt - sizeof(*fromAddr);
    }
  }
  return kOkUdpRC;
}

void      cmUdpReport( cmUdpH_t h, cmRpt_t* rpt )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);  
  cmRptPrintf(rpt,"time-out:%i recv:%i queue cb:%i\n",p->timeOutCnt,p->recvCnt,p->queCbCnt);
}

cmUdpRC_t cmUdpInitAddr( cmUdpH_t h, const char* addrStr, cmUdpPort_t portNumber, struct sockaddr_in* retAddrPtr )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);
  return _cmUdpInitAddr(p,addrStr,portNumber,retAddrPtr);
}

const cmChar_t* cmUdpAddrToString( cmUdpH_t h, const struct sockaddr_in* addr )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);

  _cmUdpClear_errno();
  
  if( inet_ntop(AF_INET, &(addr->sin_addr),  p->ntopBuf, INET_ADDRSTRLEN) == NULL)
  {
    cmErrSysMsg(&p->err,kNtoPFailUdpRC,errno, "Network address to string conversion failed." );
    return NULL;
  }
  
  p->ntopBuf[INET_ADDRSTRLEN]=0;
  return p->ntopBuf;
}

bool  cmUdpAddrIsEqual( const struct sockaddr_in* a0, const struct sockaddr_in* a1 )
{
  return a0->sin_family == a1->sin_family 
    &&   a0->sin_port   == a1->sin_port 
    &&   memcmp(&a0->sin_addr,&a1->sin_addr,sizeof(a0->sin_addr))==0;
}


const cmChar_t* cmUdpHostName( cmUdpH_t h )
{
  cmUdp_t* p = _cmUdpHandleToPtr(h);

  _cmUdpClear_errno();

  if( gethostname(p->hnameBuf,HOST_NAME_MAX) != 0 )
  {
     cmErrSysMsg(&p->err,kHostNameFailUdpRC,errno, "gethostname() failed." );
     return NULL;
  }
  
  p->hnameBuf[HOST_NAME_MAX] = 0;
  return p->hnameBuf;
}

//=======================================================================================
void cmUdpCallbackTest( void* cbArg, const char* data, unsigned dataByteCnt, const struct sockaddr_in* fromAddr )
{
  printf("%s\n",data);
}


cmUdpRC_t cmUdpTest( cmCtx_t* ctx, const char* remoteIpAddr, cmUdpPort_t port )
{
  cmUdpRC_t rc             = kOkUdpRC;
  cmUdpH_t  h              = cmUdpNullHandle;
  cmErr_t*  err            = &ctx->err;
  unsigned  recvBufByteCnt = 4096;
  unsigned  recvTimeOutMs  = 50;
  unsigned  udpFlags       = kNonBlockingUdpFl | kBroadcastUdpFl | kNoQueueUdpFl;
  unsigned  strN           = 1024;
  char      str[ strN ];
  struct sockaddr_in addr;

  // allocate the UDP port
  if((rc = cmUdpAlloc(ctx, &h )) != kOkUdpRC )
    return cmErrMsg(err,rc,"UDP port allocate failed.");

  // initialize the UDP port
  if((rc = cmUdpInit( h, port, udpFlags, cmUdpCallbackTest, NULL, NULL, 0, recvBufByteCnt, recvTimeOutMs )) != kOkUdpRC )
  {
    rc = cmErrMsg(err,rc,"UPD port initialzation failed.");
    goto errLabel;
  }

  // convert the IP address to a sockaddr_in
  if((rc = cmUdpInitAddr(h, remoteIpAddr, port, &addr )) != kOkUdpRC )
  {
    rc = cmErrMsg(err,rc,"IP address conversion failed.");
    goto errLabel;
  }


  // start up the UDP port listening thread
  if((rc = cmUdpEnableListen(h, true )) != kOkUdpRC )
  {
    rc = cmErrMsg(err,rc,"UDP port switch to listen mode failed.");
    goto errLabel;
  }

  printf("q=quit");

  while(1)
  {
    printf("$");
    fflush(stdout);
    scanf("%s",str);

    if( strlen(str) > 0 )
    {
      if( strcmp(str,"q")==0 )
        break;

      if((rc = cmUdpSendTo(h,str,strlen(str)+1,&addr)) != kOkUdpRC )
      {
        rc = cmErrMsg(err,rc,"UDP send failed.");
        goto errLabel;
      }
    }
  }


  errLabel:
    if((rc = cmUdpFree(&h)) != kOkUdpRC )
      rc = cmErrMsg(err,rc,"UDP port free failed.");

    return rc;
}


cmUdpRC_t cmUdpTestV( cmCtx_t* ctx, unsigned argc, const char* argv[])
{
  if( argc != 2 )
    return cmErrMsg(&ctx->err,kTestFailUdpRC,"cmUdpTestV usage: argv[0]=<IP Addr> argv[1]=<Socket Port Number>");

  const char* ipAddr = argv[0];
  cmUdpPort_t port   = atoi(argv[1]);

  cmRptPrintf(&ctx->rpt,"UDP Test Sending To: %s port:%i\n",ipAddr,port);

  return cmUdpTest(ctx,ipAddr,port);
}
