//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org> 
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwTest.h"
#include "cwObject.h"
#include "cwThread.h"
#include "cwText.h"

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>	
#include <netinet/tcp.h>
#include <arpa/inet.h>	
#include <fcntl.h>		
#include <unistd.h>  // close
#include <poll.h>

#include "cwSocket.h"

#define cwSOCKET_SYS_ERR     (-1)
#define cwSOCKET_NULL_SOCK   (-1)

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX  _POSIX_HOST_NAME_MAX
#endif


namespace cw
{
  namespace sock
  {

    enum
    {
     kIsConnectedFl = 0x01,
     kIsBlockingFl  = 0x02
    };
    
    typedef struct sock_str
    {
      unsigned           userId;
      unsigned           connId;
      int                sockH;
      unsigned           createFlags;
      unsigned           flags;
      callbackFunc_t     cbFunc;
      void*              cbArg;
      struct sockaddr_in localSockAddr;
      struct sockaddr_in remoteSockAddr;
      char               ntopBuf[ INET_ADDRSTRLEN+1 ]; // use INET6_ADDRSTRLEN for IPv6
      char               hnameBuf[ HOST_NAME_MAX+1 ];
      struct pollfd*     pollfd;
      unsigned           nextConnId;
      struct sock_str*   parent;     // pointer to this socket's parent socket
      struct sock_str*   children;   // pointer to this sockets children (parent==NULL), or sibling (parent!=NULL)
    } sock_t;
    
    typedef struct mgr_str
    { 
      uint8_t*       buf;       // buf[bufByteN]
      unsigned       bufByteN;  // size of buf[]
      
      struct pollfd* pollfdA;   // pollfdA[ sockMaxN ] poll array
      sock_t*        sockA;     //.sockA[ sockMaxN ] 
      unsigned       sockMaxN;  // sockMaxN count of elements in sockA[] and pollfdA[] 
      unsigned       sockN;     // sockA[ sockN ] sock record in use
      
    } mgr_t;

    mgr_t* _handleToPtr( handle_t h)
    { return handleToPtr<handle_t,mgr_t>(h); }

    sock_t* _idToSock( mgr_t* p, unsigned userId, bool showErrorFl=false )
    {
      for(unsigned i=0; i<p->sockN; ++i)
        if( p->sockA[i].userId == userId )
          return p->sockA + i;
      
      if( showErrorFl )
        cwLogError(kInvalidIdRC,"A socket with id:%i could not be found.",userId);
      
      return nullptr;
    }

    
    rc_t _getMgrAndSocket( handle_t h, unsigned userId, mgr_t*& p, sock_t*& s, bool showErrorFl = false )
    {
      p = _handleToPtr(h);
      if((s = _idToSock(p,userId,showErrorFl)) == nullptr )
      {
        if( showErrorFl )
          return cwLogError(kInvalidArgRC,"The socket associated with id '%i' could not be found.",userId);
      }
      
      return kOkRC;
    }

    bool _sockIsOpen( sock_t* p )
    { return p->sockH != cwSOCKET_NULL_SOCK; }

    void _unlinkChild( sock_t* s )
    {
      sock_t* ps  = s->parent;
      sock_t* cs0 = nullptr;
      for(sock_t* cs=ps->children; cs!=nullptr; cs=cs->children)
      {
        if( cs == s )
        {
          if( cs0 == nullptr )
            ps->children = cs->children;
          else
            cs0->children = cs->children;
          
          cs->children = nullptr;
          cs->parent   = nullptr;
          
          break;
        }
        
        cs0 = cs;
        
      }   
    }
    
    rc_t _closeSock( mgr_t* p, sock_t* s )
    {
      rc_t rc = kOkRC;
      
      // close the socket		
      if( s->sockH != cwSOCKET_NULL_SOCK )
      {
        errno = 0;
          
        if( ::close(s->sockH) != 0 )
          rc = cwLogSysError(kOpFailRC,errno,"The socket close failed." );
          
        s->sockH = cwSOCKET_NULL_SOCK;		
      }

      if( s->parent != nullptr )
        _unlinkChild( s );

      s->userId                    = kInvalidId;
      s->createFlags               = 0;
      s->flags                     = 0;
      s->pollfd->events            = 0;
      s->pollfd->fd                = cwSOCKET_NULL_SOCK; // poll() ignores records when fd < 0
      s->remoteSockAddr.sin_family = AF_UNSPEC;

      return rc;      
    }

    rc_t _destroyMgr( mgr_t* p )
    {
      rc_t rc = kOkRC;

      for(unsigned i=0; i<p->sockN; ++i)
      {
        rc_t rc0 = _closeSock(p,p->sockA + i);
        if( rc0 != kOkRC )
          rc = rc0;
      }
      
      mem::release(p->sockA);
      mem::release(p->pollfdA);
      mem::release(p->buf);
      mem::release(p);
      return rc;
    }

    // Locate an available slot in p->sockA and return the index of the slot in availIdxRef.
    rc_t  _locateAvailSlot( mgr_t* p, unsigned& availIdx_Ref )
    {
      availIdx_Ref = kInvalidIdx;
      
      // locate an avail. socket between 0:sockN
      for(unsigned i=0; i<p->sockN; ++i)
        if( !_sockIsOpen(p->sockA + i) )
        {
          availIdx_Ref = i;
          return kOkRC;
        }

      // Be sure all slots are not already in use.
      if( p->sockN >= p->sockMaxN )
        return cwLogError(kResourceNotAvailableRC,"All socket slots are in use.");

      // Expand the slot array to accommodate another socket
      availIdx_Ref  = p->sockN;
      p->sockN     += 1;

      return kOkRC;
    }

    void _callback( sock_t* s, cbOpId_t opId, const struct sockaddr_in* srcAddr=nullptr, const void* buf=nullptr, unsigned bufByteN=0 )
    {
      if( s->cbFunc != nullptr )
        s->cbFunc( s->cbArg, opId, s->userId, s->connId, buf, bufByteN, srcAddr );
    }

    rc_t _accept( mgr_t* p, sock_t* s )      
    {
      rc_t                    rc       = kOkRC;
      struct sockaddr_storage remoteAddr;                      
      socklen_t               sin_size = sizeof(remoteAddr);
      int                     fd;
      unsigned                sockIdx  = kInvalidIdx;
      sock_t*                 cs       = nullptr;
      
      // accept the incoming connection
      if((fd = accept(s->sockH, (struct sockaddr*)&remoteAddr, &sin_size )) < 0)
      {
        if( errno == EAGAIN || errno == EWOULDBLOCK )
          rc = kTimeOutRC;
        else
        {
          rc = cwLogSysError(kOpFailRC,errno,"Socket accept() failed.");
          goto errLabel;
        }        
      }

      // ... then find an available socket record
      if((rc =  _locateAvailSlot(p, sockIdx )) != kOkRC )
      {
        rc = cwLogError(rc,"There are no available slots to 'accept' a new socket connection.");
        goto errLabel;
      }

      // initialize the socket record
      cs = p->sockA + sockIdx;

      cs->userId           = s->userId;
      cs->connId           = s->nextConnId++;
      cs->sockH            = fd;
      cs->createFlags      = 0;
      cs->remoteSockAddr   = *(struct sockaddr_in*)&remoteAddr;
      cs->cbFunc           = s->cbFunc;
      cs->cbArg            = s->cbArg;
      cs->pollfd           = p->pollfdA + sockIdx;
      cs->pollfd->events   = POLLIN;
      cs->pollfd->fd       = fd;
      cs->nextConnId       = 0;
      cs->parent           = s;
      cs->children         = s->children;
      s->children          = cs;

      if((rc = addrToString( (const struct sockaddr_in*)&remoteAddr, cs->ntopBuf,  sizeof(cs->ntopBuf) )) != kOkRC )
        goto errLabel;      

      _callback( cs, kConnectCbId, (const struct sockaddr_in*)&remoteAddr);
      
    errLabel:
      if( rc != kOkRC )
        close(fd);
      
      return rc;
    }

    // Called to read the socket when data is known to be waiting.
    rc_t _receive( mgr_t* p, sock_t* s, unsigned& readN_Ref, void* buf=nullptr, unsigned bufByteN=0, struct sockaddr_in* fromAddr=nullptr )
    {
      rc_t     rc     = kOkRC;
      void*    b      = buf;
      unsigned bN     = bufByteN;
      ssize_t  bytesReadN = 0;
      struct sockaddr_in sockaddr;
      socklen_t sizeOfFromAddr = 0;

      memset(&sockaddr,0,sizeof(sockaddr));
      
      // clear the count of actual bytes read 
      readN_Ref = 0;     

      // if the socket is not open then there is nothing to do
      if( !_sockIsOpen(s) )
        return cwLogWarningRC( kResourceNotAvailableRC, "An attempt was made to read from a closed socket.");
            
      // if no  buffer was given then use the default buffer
      if( b ==nullptr || bufByteN == 0 )
      {
        b  = p->buf;
        bN = p->bufByteN;
      }
      
      // if no src address buffer was given and this is a UDP socket
      if( fromAddr == nullptr && cwIsNotFlag(s->createFlags,kTcpFl) )
      {
        fromAddr       = &sockaddr;        
        sizeOfFromAddr = sizeof(struct sockaddr_in);
      }
  
      errno = 0;

      // read the socket
      if((bytesReadN = recvfrom(s->sockH, b, bN, 0, (struct sockaddr*)fromAddr, &sizeOfFromAddr )) != cwSOCKET_SYS_ERR )
      {
        // return the count of actual bytes read
        readN_Ref = bytesReadN;

        // if no return buffer was given and the socket has a callback function - then call it
        if( bytesReadN > 0 && s->cbFunc != nullptr  && (buf==nullptr || bufByteN==0) )
        {
          // if no src addr was given (because this is a TCP socket) then use the connected remote socket
          if( fromAddr == nullptr && s->remoteSockAddr.sin_family != AF_UNSPEC)
            fromAddr = &s->remoteSockAddr;

          _callback( s, kReceiveCbId, fromAddr, b, bytesReadN );
        }
          
      }
      else
      {
        // an error occurred during the read operation
        switch( errno )
        {
          case EAGAIN:
            return kTimeOutRC;

          case ENOTCONN:
            if( cwIsFlag(s->flags,kIsConnectedFl ) )
              cwLogWarning("Socket Disconnected.");
        
            s->flags = cwClrFlag(s->flags,kIsConnectedFl);

            _callback( s, kDisconnectCbId );
        }
    
        return cwLogSysError(kOpFailRC,errno,"recvfrom() failed.");
      }
      
      return rc;
    }


    // Block on devices waiting for data on a port.
    // If userId is valid then wait for data on a specific port otherwise
    // wait for data on all ports.
    rc_t _poll( mgr_t* p, unsigned timeOutMs, unsigned& readN_Ref, unsigned userId=kInvalidId, void* buf=nullptr, unsigned bufByteN=0, struct sockaddr_in* fromAddr=nullptr )
    {
      rc_t rc = kOkRC;
      int sysRC;

      readN_Ref = 0;

      // if there are not ports then there is nothing to do
      if( p->sockN == 0 )
        return rc;

      struct pollfd* pfd  = p->pollfdA;  // first struct pollfd
      unsigned       pfdN = p->sockN;    // count of pollfd's
      sock_t*        s    = p->sockA;    // port assoc'd with first pollfd

      // if only one socket is to be read ...
      if( userId != kInvalidId )
      {
        // ... then locate it
        if((s = _idToSock(p,userId)) == nullptr )
          return kInvalidArgRC;
        
        pfd  = s->pollfd;
        pfdN = 1;
      }

      // A port to poll must exist
      if( p == nullptr )
        return cwLogError(kInvalidArgRC,"The port with id %i could not be found.",userId);
      

      // block waiting for data on one of the ports
      if((sysRC = ::poll(pfd,pfdN,timeOutMs)) == 0)
      {
        rc = kTimeOutRC;
      }
      else
      {
        // ::poll() encountered a system exception
        if( sysRC < 0 )
          return cwLogSysError(kReadFailRC,errno,"Poll failed on socket.");

        // store the current sockA[] size because p->sockN may change inside the loop
        unsigned sockN = p->sockN;
        
        // interate through the ports looking for the sockets which have data waiting ...
        for(unsigned i=0; i<sockN; ++i)
        {
          // if the socket was disconnected or is no longer valid
          if( cwIsFlag(p->sockA[i].pollfd->revents,POLLHUP) || cwIsFlag(p->sockA[i].pollfd->revents,POLLNVAL) )
          {
            _callback( p->sockA + i, kDisconnectCbId );
            
            _closeSock(p,p->sockA+i);
            continue;
          }

          // if an error occured on this socket
          if( p->sockA[i].pollfd->revents & POLLERR )
          {
            cwLogError(kOpFailRC,"ERROR on socket user id:%i conn id:%i\n",p->sockA[i].userId,p->sockA[i].connId);
            
            // TODO: should the socket be closed? marked as disconnected? 
          }
          
          if( p->sockA[i].pollfd->revents & POLLIN )
          {
            unsigned actualReadN = 0;

            sock_t* s = p->sockA + i;

            // If this is a listening/streaming socket then it is waiting for connections
            if( cwAllFlags(s->createFlags,kListenFl|kStreamFl) )
            {
              rc_t rc0;

              // accept a new connection to this socket
              if((rc0 =  _accept( p, s)) != kOkRC )
                rc = rc0;
              
            }
            else // otherwise it is a non-listening socket that is receiving data
            {                        
              if((rc = _receive( p, s, actualReadN, buf, bufByteN, fromAddr )) != kOkRC )
                return rc;
            }
            
            readN_Ref += actualReadN;
          }
        }
      }
  
      return rc;      
    }


    rc_t _initAddr( const char* addrStr, portNumber_t portNumber, struct sockaddr_in* retAddrPtr )
    {
      memset(retAddrPtr,0,sizeof(struct sockaddr_in));

      //if( portNumber == kInvalidPortNumber )
      //  return cwLogError(kInvalidArgRC,"The port number %i cannot be used.",kInvalidPortNumber);
	
      if( addrStr == nullptr )
        retAddrPtr->sin_addr.s_addr 	= htonl(INADDR_ANY);
      else
      {
        errno = 0;

        if(inet_pton(AF_INET,addrStr,&retAddrPtr->sin_addr) == 0 )
          return cwLogSysError(kOpFailRC,errno, "The network address string '%s' could not be converted to a netword address structure.",cwStringNullGuard(addrStr) );
      }
	
      //retAddrPtr->sin_len 			= sizeof(struct sockaddr_in);
      retAddrPtr->sin_family = AF_INET;
      if( portNumber != kInvalidPortNumber  )
        retAddrPtr->sin_port = htons(portNumber);
	
      return kOkRC;
    }

    rc_t _connect( sock_t* s, const char* remoteAddr, portNumber_t remotePort )
    {
      struct sockaddr_in addr;
      rc_t               rc;

      // create the remote address		
      if((rc = _initAddr( remoteAddr, remotePort,  &addr )) != kOkRC )
        return rc;
		
      errno = 0;

      // ... and connect this socket to the remote address/port
      if( connect(s->sockH, (struct sockaddr*)&addr, sizeof(addr)) == cwSOCKET_SYS_ERR )
      {
        if( cwIsNotFlag(s->createFlags, kBlockingFl) && errno == EINPROGRESS )
        {
          // if the socket is non-blocking the connection will complete asynchronously
        }
        else
        {
          return cwLogSysError(kOpFailRC, errno, "Socket connect to %s:%i failed.", cwStringNullGuard(remoteAddr), remotePort );
        }
      }
      
      s->flags = cwSetFlag(s->flags,kIsConnectedFl);

      s->remoteSockAddr = addr;

      return rc;
    }


    rc_t _setTimeOutMs( sock_t* s, unsigned timeOutMs )
    {
      rc_t rc = kOkRC;

      struct timeval 		timeOut;
    
      // set the socket time out 
      timeOut.tv_sec 	= timeOutMs/1000;
      timeOut.tv_usec = (timeOutMs - (timeOut.tv_sec * 1000)) * 1000;
        
      if( setsockopt( s->sockH, SOL_SOCKET, SO_RCVTIMEO, &timeOut, sizeof(timeOut) ) == cwSOCKET_SYS_ERR )
      {
        rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket timeout failed." );
        goto errLabel;
      }


    errLabel:
      return rc;
    }

    rc_t _get_info( int sockH, unsigned char outBuf[6], struct sockaddr_in* addr, const char* interfaceName )
    {
      cw::rc_t  rc   = kOkRC;
      struct ifreq  ifr;
      struct ifconf ifc;
      char buf[1024];
    
      ifc.ifc_len = sizeof(buf);
      ifc.ifc_buf = buf;
  
      if (ioctl(sockH, SIOCGIFCONF, &ifc) == -1)
      {
        rc = cwLogSysError(kOpFailRC,errno,"ioctl(SIOCGIFCONF) failed.");
        return rc;
      }

      struct ifreq*             it  = ifc.ifc_req;
      const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));

      for (; it != end; ++it)
      {
        if( strcmp(it->ifr_name,interfaceName ) == 0 )
        {
          strcpy(ifr.ifr_name, it->ifr_name);
        
          if (ioctl(sockH, SIOCGIFFLAGS, &ifr) != 0)
          {
            rc = cwLogSysError(kOpFailRC,errno,"ioctl(SIOCGIFCONF) failed.");
          }
          else
          {
            if (! (ifr.ifr_flags & IFF_LOOPBACK))
            {
              // don't count loopback
              if (ioctl(sockH, SIOCGIFHWADDR, &ifr) == 0)
              {
                memcpy(outBuf, ifr.ifr_hwaddr.sa_data, 6);

                if( addr != nullptr &&  ioctl(sockH, SIOCGIFADDR, &ifr) == 0)
                {
                  addr->sin_addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr;
                }
            
                return kOkRC;
              }
            }
          }
        }
      }
  
      return cwLogError(kInvalidArgRC,"The network interface information for '%s' could not be found.", interfaceName);
    }

    
  }
}



cw::rc_t cw::sock::createMgr( handle_t& hRef, unsigned recvBufByteN, unsigned maxSockN )
{
  rc_t rc;
  if((rc = destroyMgr(hRef)) != kOkRC )
    return rc;

  mgr_t* p    = mem::allocZ<mgr_t>();
  p->buf      = mem::allocZ<uint8_t>(recvBufByteN);
  p->bufByteN = recvBufByteN;
  p->sockA    = mem::allocZ<sock_t>( maxSockN );
  p->pollfdA  = mem::allocZ<struct pollfd>( maxSockN );
  p->sockMaxN = maxSockN;
  
  for(unsigned i=0; i<p->sockMaxN; ++i)
  {
    p->sockA[i].sockH                     = cwSOCKET_NULL_SOCK;
    p->sockA[i].remoteSockAddr.sin_family = AF_UNSPEC;
    p->sockA[i].pollfd                    = p->pollfdA + i;
  }
  
  hRef.set(p);
  return rc;
}

cw::rc_t cw::sock::destroyMgr( handle_t& hRef )
{
  rc_t rc = kOkRC;
  if( ! hRef.isValid() )
    return rc;

  mgr_t* p = _handleToPtr(hRef);

  if((rc = _destroyMgr(p)) != kOkRC )
    return rc;

  hRef.clear();
  return rc;
}

cw::rc_t cw::sock::create( handle_t h,
  unsigned       userId,
  short          port,
  unsigned       flags,
  unsigned       timeOutMs,
  callbackFunc_t cbFunc,
  void*          cbArg,
  const char*    remoteAddr,
  portNumber_t   remotePort,
  const char*    localAddr )
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;

  // if a userId is being reused
  _getMgrAndSocket(h, userId, p, s, false );

  if( s != nullptr )
    if((rc = _closeSock(p,s)) != kOkRC )
      return kInvalidArgRC;

  // if userId did not identify an existing socket ...
  if( s == nullptr )
  {
    unsigned sockIdx;
    // ... then find an available socket record
    if((rc =  _locateAvailSlot(p, sockIdx )) != kOkRC )
      return rc;

    s = p->sockA + sockIdx;    
  }

 
  int type     = cwIsFlag(flags,kStreamFl) ? SOCK_STREAM : SOCK_DGRAM;
  int protocol = cwIsFlag(flags,kTcpFl)    ? 0 : IPPROTO_UDP;
  
  // get a handle to the socket
  if(( s->sockH = ::socket( AF_INET, type, protocol ) ) == cwSOCKET_SYS_ERR )
    return cwLogSysError(kOpFailRC, errno, "Socket create failed." );
  
  s->userId         = userId;
  s->connId         = kInvalidId;
  s->createFlags    = flags;
  s->flags          = 0;
  s->cbFunc         = cbFunc;
  s->cbArg          = cbArg;
  s->pollfd         = p->pollfdA + (s - p->sockA);
  s->pollfd->events = POLLIN;
  s->pollfd->fd     = s->sockH;
	s->nextConnId     = 0;
  
  // if this socket should block
  if( cwIsFlag(flags,kBlockingFl))
  {
    if( timeOutMs > 0 )
    {
      _setTimeOutMs(s,timeOutMs);
    }
    
    s->flags = cwSetFlag(s->flags,kIsBlockingFl);
  }
  else // otherwise this is a non-blocking socket
  {
    int opts;
		
    // get the socket options flags
    if( (opts = fcntl(s->sockH,F_GETFL)) < 0 )
    {
      rc = cwLogSysError(kOpFailRC,errno, "Attempt to get the socket options flags failed." );
      goto errLabel;
    }
	    
    opts = (opts | O_NONBLOCK);
		
    // set the socket options flags
    if(fcntl(s->sockH,F_SETFL,opts) < 0) 
    {
      rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket to non-blocking failed." );
      goto errLabel;
    }	    

  }

  if( cwIsFlag(flags,kReuseAddrFl) )
  {
    unsigned int reuseaddr = 1;
    if( setsockopt(s->sockH, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseaddr, sizeof(reuseaddr)) == cwSOCKET_SYS_ERR )
    {
      rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'reuse address' attribute failed." );
      goto errLabel;      
    }
  }
  
#ifdef SO_REUSEPORT
  if( cwIsFlag(flags,kReusePortFl) )
  {
    unsigned int reuseaddr = 1;
    if(setsockopt(s->sockH, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuseaddr, sizeof(reuseaddr)) == cwSOCKET_SYS_ERR )
    {
      rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'reuse port' attribute failed." );
      goto errLabel;      
    }
  }
#endif

  if( cwIsFlag(flags,kMultiCastTtlFl) )
  {
    unsigned char ttl = 1;
    if( setsockopt(s->sockH, IPPROTO_IP, IP_MULTICAST_TTL, (const char*)&ttl, sizeof(ttl)) == cwSOCKET_SYS_ERR )
    {
      rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'multicast TTL' attribute failed." );
      goto errLabel;      
    }
  }

  if( cwIsFlag(flags,kMultiCastLoopFl) )
  {    
    unsigned char loopback = 1;
    if( setsockopt(s->sockH, IPPROTO_IP, IP_MULTICAST_LOOP, (const char*)&loopback, sizeof(loopback)) == cwSOCKET_SYS_ERR )
    {
      rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'reuse port' attribute failed." );
      goto errLabel;      
    }    
  }  

  // if broadcast option was requested.
  if( cwIsFlag(flags,kBroadcastFl) )
  {
    int bcastFl  = 1;
    if( setsockopt( s->sockH, SOL_SOCKET, SO_BROADCAST, &bcastFl, sizeof(bcastFl) ) == cwSOCKET_SYS_ERR )
    {
      rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket broadcast attribute failed." );
      goto errLabel;
    }
  }

  if( cwIsFlag(flags,kTcpFl) && cwIsFlag(flags,kTcpNoDelayFl) )
  {
    int nodelay_flag = 1;
    if( setsockopt( s->sockH, IPPROTO_TCP, TCP_NODELAY, (void *)&nodelay_flag, sizeof(nodelay_flag)) == cwSOCKET_SYS_ERR )
    {
      rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket NODELAY attribute failed." );
      goto errLabel;      
    }
    
  }
  
  // create the 32 bit local address		
  if((rc = _initAddr( localAddr, port,  &s->localSockAddr )) != kOkRC )
    goto errLabel;

  // bind the socket to a local address/port	
  if( (bind( s->sockH, (struct sockaddr*)&s->localSockAddr, sizeof(s->localSockAddr))) == cwSOCKET_SYS_ERR )
  {
    rc = cwLogSysError(kOpFailRC,errno,"Socket bind failed." );
    goto errLabel;
  }

  // get the local address as a string
  if((rc = addrToString( &s->localSockAddr, s->ntopBuf,  sizeof(s->ntopBuf) )) != kOkRC )
    goto errLabel;
  
  
  // if a remote addr was given connect this socket to it
  if( remoteAddr != nullptr )
    if((rc = _connect(s,remoteAddr,remotePort)) != kOkRC )
      goto errLabel;

  // if the socket should be marked for listening
  if( cwIsFlag(flags,kListenFl) )
  {
    if( ::listen(s->sockH, 10) != 0 )
    {
      rc = cwLogSysError(kOpFailRC,errno,"Socket listen() failed.");
      goto errLabel;
    }
  }
  
 errLabel:
  if(rc != kOkRC )
    _closeSock(p,s);

  return rc;
}

cw::rc_t cw::sock::destroy( handle_t h, unsigned userId )
{
  rc_t    rc = kOkRC;
  mgr_t*  p; 
  sock_t* s;

  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
  {
    cwLogWarning("Socket destroy failed. The socket %i could not be found.",userId);
    return rc;
  }
  
  if((rc = _closeSock(p,s)) != kOkRC )
    return rc;
  
  return rc;
}

cw::rc_t cw::sock::set_multicast_time_to_live( handle_t h, unsigned userId, unsigned seconds )
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;
   
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return rc;
  
  if( setsockopt( s->sockH, IPPROTO_IP, IP_MULTICAST_TTL, &seconds, sizeof(seconds) ) == cwSOCKET_SYS_ERR )
  {
    rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket multicast TTLfailed." );
  }

  return rc;
}

cw::rc_t cw::sock::join_multicast_group( handle_t h, unsigned userId, const char* addrStr )
{
  rc_t           rc = kOkRC;  
  mgr_t*         p;
  struct ip_mreq req;
  sock_t*        s;

	memset(&req, 0, sizeof(req));

  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return rc;

  if(inet_pton(AF_INET,addrStr,&req.imr_multiaddr.s_addr) == 0 )
  {
    rc = cwLogSysError(kOpFailRC,errno, "The network address string '%s' could not be converted to a netword address structure.",cwStringNullGuard(addrStr) );
    goto errLabel;
  }
  
	req.imr_interface.s_addr = INADDR_ANY;
  
	if(setsockopt(s->sockH, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&req, sizeof(req)) == cwSOCKET_SYS_ERR )
  {
    rc = cwLogSysError(kOpFailRC,errno, "Attempt to add socket to multicast group on '%s' failed.", cwStringNullGuard(addrStr) );
    goto errLabel;   
  }

 errLabel:
  return rc;
}


// Set a destination address for this socket. Once a destination address is set
// the caller may use send() to communicate with the specified remote socket
// without having to specify a destination address on each call.
cw::rc_t cw::sock::connect( handle_t h, unsigned userId, const char* remoteAddr, portNumber_t remotePort )
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;
   
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return rc;
  
  return _connect(s,remoteAddr,remotePort);  
}


bool cw::sock::isConnected( handle_t h, unsigned userId )
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;
   
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return false;
  
  return cwIsFlag(s->flags,kIsConnectedFl);
}
      
cw::rc_t cw::sock::send( handle_t h, unsigned userId, unsigned connId, const void* data, unsigned dataByteCnt )
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;

  // locate the socket to send on
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return rc;

  // If this a pre-connected socket ...
  if(  cwIsFlag(s->flags,kIsConnectedFl) )
  {
    if( ::send( s->sockH, data, dataByteCnt, 0 ) == cwSOCKET_SYS_ERR )
      rc = cwLogSysError(kOpFailRC,errno,"Send failed.");
  }
  else  // ... otherwise this is a listening socket with one or more child sockets
  {
    
    if( s->children == nullptr )
      return cwLogError(kInvalidOpRC,"socket::send() only works with connected sockets.");
  
    sock_t* cs = s->children;
    for(; cs != nullptr; cs=cs->children)
      if( connId==kInvalidId || cs->connId == connId )
      {
        errno = 0;
      
        if( ::send( cs->sockH, data, dataByteCnt, 0 ) == cwSOCKET_SYS_ERR )
          rc = cwLogSysError(kOpFailRC,errno,"Send failed.");

        if( cs->connId == connId )
          break;
      }
  
  }
  return rc;    
}
      
cw::rc_t cw::sock::send( handle_t h, unsigned userId, const void* data, unsigned dataByteCnt, const struct sockaddr_in* remoteAddr )
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;
   
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return rc;
  
  errno = 0;
   
  if( ::sendto(s->sockH, data, dataByteCnt, 0, (struct sockaddr*)remoteAddr, sizeof(*remoteAddr)) == cwSOCKET_SYS_ERR )
    return cwLogSysError(kOpFailRC,errno,"Send to remote addr. failed.");

  return kOkRC;
}

cw::rc_t cw::sock::send( handle_t h, unsigned userId, const void* data, unsigned dataByteCnt, const char* remoteAddr, portNumber_t remotePort )
{
  rc_t               rc = kOkRC;
  mgr_t*             p;
  sock_t*            s;
  struct sockaddr_in addr;
   
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return rc;

  if((rc = _initAddr(remoteAddr,remotePort,&addr)) != kOkRC )
    return rc;
  
  return send( h, userId, data, dataByteCnt, &addr );
}

cw::rc_t cw::sock::receive_all( handle_t h, unsigned timeOutMs, unsigned& readByteN_Ref )
{
  rc_t  rc = kOkRC;
  mgr_t* p = _handleToPtr(h);

  if((rc = _poll( p, timeOutMs, readByteN_Ref )) != kOkRC  && rc != kTimeOutRC )
    return cwLogError(rc,"Socket receive failed.");
  
  return rc;
  
}

cw::rc_t cw::sock::receive(handle_t h, unsigned userId, unsigned& readByteN_Ref, void* buf, unsigned bufByteN, struct sockaddr_in* fromAddr )  
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;

  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return rc;

  if((rc = _receive( p, s, readByteN_Ref, buf, bufByteN, fromAddr )) != kOkRC )
    return rc;

  return rc; 
}

cw::rc_t cw::sock::get_mac( handle_t h, unsigned userId, unsigned char outBuf[6], struct sockaddr_in* addr, const char* netInterfaceName )
{
  mgr_t*  p;
  sock_t* s;
  rc_t    rc;
  
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return rc;
  
  return _get_info(s->sockH, outBuf, addr, netInterfaceName );
}

cw::rc_t cw::sock::initAddr( const char* addrStr, portNumber_t portNumber, struct sockaddr_in* retAddrPtr )
{
  return _initAddr(addrStr,portNumber,retAddrPtr);
}
      
cw::rc_t cw::sock::addrToString( const struct sockaddr_in* addr, char* buf, unsigned bufN )
{
  rc_t rc = kOkRC;
  
  errno = 0;
  
  if( inet_ntop(AF_INET, &(addr->sin_addr),  buf, bufN) == nullptr)
  {
    rc = cwLogSysError(kOpFailRC,errno, "Network address to string conversion failed." );
    goto errLabel;
  }
  
  buf[bufN-1]=0;
 errLabel:
  return rc;
}
      
bool cw::sock::addrIsEqual( 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 char* cw::sock::hostName( handle_t h, unsigned userId )
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;
  
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return nullptr;

  errno = 0;

  if( gethostname(s->hnameBuf,HOST_NAME_MAX) != 0 )
  {
    cwLogSysError(kOpFailRC,errno, "gethostname() failed." );
    return nullptr;
  }
  
  s->hnameBuf[HOST_NAME_MAX] = 0;
  return s->hnameBuf;
}

const char* cw::sock::ipAddress( handle_t h, unsigned userId )
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;
  
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return nullptr;
  
  return s->ntopBuf;
}

unsigned    cw::sock::inetAddress( handle_t h, unsigned userId )
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;
  
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return 0;

  return  s->localSockAddr.sin_addr.s_addr;
}

cw::sock::portNumber_t    cw::sock::port( handle_t h, unsigned userId )
{
  rc_t    rc = kOkRC;
  mgr_t*  p;
  sock_t* s;
  
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return rc;
  
  return  ntohs(s->localSockAddr.sin_port);
}

cw::rc_t cw::sock::peername( handle_t h, unsigned userId, struct sockaddr_in* addr )
{
  rc_t      rc = kOkRC;
  socklen_t n  = sizeof(struct sockaddr_in);
  mgr_t*    p;
  sock_t*   s;
  
  if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC )
    return rc;
  
  if( getpeername(s->sockH, (struct sockaddr*)addr, &n)  == cwSOCKET_SYS_ERR )
    return cwLogSysError(kOpFailRC,errno,"Get peer name failed.");

  addr->sin_port = ntohs(addr->sin_port);

  return rc;
}

cw::rc_t cw::sock::get_info( const char* netInterfaceName, unsigned char mac[6], char* host, unsigned hostN, struct sockaddr_in* addr )
{
  rc_t rc = kOkRC;
  int sockH;

  if( host != nullptr )
    if( gethostname(host,hostN) != 0 )
      return cwLogSysError(kOpFailRC,errno,"Unable to get the local host name.");
  
  // get a handle to the socket
  if(( sockH = ::socket( AF_INET, SOCK_DGRAM, 0 ) ) == cwSOCKET_SYS_ERR )
    return cwLogSysError(kOpFailRC,errno,"Unable to create temporary socket.");

  if((rc = _get_info(sockH,mac,addr,netInterfaceName)) != kOkRC )
    goto errLabel;

 errLabel:
  close(sockH);
  return rc;
}


//===============================================================================================
namespace cw
{
  namespace socksrv
  {
    typedef struct socksrv_str
    {
      sock::handle_t   sockMgrH;
      thread::handle_t thH;
      unsigned         timeOutMs;
    } socksrv_t;

    socksrv_t* _handleToPtr( handle_t h )
    {
      return handleToPtr<handle_t,socksrv_t>(h);
    }

    rc_t _destroyMgrSrv( socksrv_t* p )
    {
      rc_t rc = kOkRC;

      // destroy the thread
      if((rc = destroy(p->thH)) != kOkRC )
        return rc;

      // destroy the socket manager
      if((rc = sock::destroyMgr(p->sockMgrH)) != kOkRC )
        goto errLabel;

      mem::release(p);

    errLabel:
      return rc;
    }

    bool _threadFunc( void* arg )
    {
      socksrv_t* p         = (socksrv_t*)arg;
      unsigned   readByteN = 0;
      rc_t       rc;

      // 
      if((rc = sock::receive_all( p->sockMgrH, p->timeOutMs, readByteN)) != kOkRC && rc != kTimeOutRC )
      {
        cwLogError(rc,"Socket Srv receive failed.");
        return false;
      }
      
      return true;
    }

    // Callback thread used by socksrv::test() below
    void _socketTestCbFunc( void* cbArg, sock::cbOpId_t cbId, unsigned userId, unsigned connId, const void* byteA, unsigned byteN, const struct sockaddr_in* srcAddr )
    {
      rc_t rc;
      char addr[ INET_ADDRSTRLEN+1 ];

      printf("type:%i user:%i conn:%i ", cbId, userId, connId );

      if( srcAddr != nullptr )
        if((rc = sock::addrToString( srcAddr, addr, INET_ADDRSTRLEN )) == kOkRC )
        {
          printf("from  %s ", addr  );
        }

      if( byteA != nullptr )
        printf(" : %s ", (const char*)byteA);

      printf("\n");
    }
     
  }
}

cw::rc_t cw::socksrv::createMgrSrv(  handle_t& hRef, unsigned timeOutMs, unsigned recvBufByteN, unsigned maxSocketN )
{
  cw::rc_t rc = kOkRC;
  if((rc = destroyMgrSrv(hRef)) != kOkRC )
    return rc;

  // allocate the object
  socksrv_t* p = mem::allocZ<socksrv_t>();

  // create the socket manager
  if((rc = createMgr(  p->sockMgrH, recvBufByteN, maxSocketN )) != kOkRC )
    goto errLabel;

  // create the thread
  if((rc = thread::create( p->thH, _threadFunc, p, "sock")) != kOkRC )
    goto errLabel;

  p->timeOutMs = timeOutMs;
  
  hRef.set(p);

 errLabel:
  return rc;
}

cw::rc_t cw::socksrv::destroyMgrSrv( handle_t& hRef )
{
  rc_t rc = kOkRC;
  if( !hRef.isValid() )
    return kOkRC;

  socksrv_t* p = _handleToPtr(hRef);
  if((rc = _destroyMgrSrv(p)) != kOkRC )
    return rc;

  hRef.clear();
  
  return rc;
}

cw::sock::handle_t cw::socksrv::mgrHandle( handle_t h )
{
  socksrv_t* p = _handleToPtr(h);
  return p->sockMgrH;
}

cw::rc_t cw::socksrv::start( handle_t h )
{
  socksrv_t* p = _handleToPtr(h);
  return thread::unpause( p->thH );
}

cw::rc_t cw::socksrv::stop(  handle_t h )
{
  socksrv_t* p = _handleToPtr(h);
  return thread::pause( p->thH );
}

cw::rc_t cw::socksrv::test(  const char*        localNicDevice,
                             sock::portNumber_t localPort,
                             const char*        remoteAddrIp,
                             sock::portNumber_t remotePort,
                             unsigned           flags )
{
  handle_t            h;
  rc_t                rc           = kOkRC;
  unsigned            timeOutMs    = 50;
  unsigned            recvBufByteN = 2048;
  unsigned            maxSocketN   = 10;
  unsigned            userId       = 10;
  unsigned            sockFlags    = sock::kNonBlockingFl | flags;
  bool                serverFl     = remoteAddrIp == nullptr;
  const char*         localIpAddrPtr = nullptr;
  char                localIpAddr[ INET_ADDRSTRLEN+1 ];
  const unsigned      sbufN        = 31;
  char                sbuf[ sbufN+1 ];

  // if a local device was given
  if( localNicDevice != nullptr )
  {
    struct sockaddr_in  localAddr;
    unsigned char      mac[6];

    // get the IP address of the device
    if((rc = sock::get_info(localNicDevice, mac, nullptr, 0, &localAddr )) != kOkRC )
      return cwLogError(rc,"Unable to obtain the local address information for the device:'%s'.",localNicDevice);

    // convert the IP address to a string
    if((rc = sock::addrToString( &localAddr, localIpAddr, INET_ADDRSTRLEN )) != kOkRC )
      return cwLogError(rc,"Unable to convert the local address to a string for device:'%s'.",localNicDevice);
    
    localIpAddrPtr = localIpAddr;
  }
  
  if( serverFl )
    printf("Server listening on %s port: %i\n", localIpAddrPtr==nullptr ? "" : localIpAddrPtr, localPort );
  else
    printf("Client connecting to server %s:%i\n", remoteAddrIp,remotePort);
    

  // create the socket manager
  if((rc = createMgrSrv(h, timeOutMs, recvBufByteN, maxSocketN )) != kOkRC )
    return cwLogError(rc,"Socket server create failed.");

  // start the socket manager
  if((rc = start(h)) != kOkRC )
  {
    cwLogError(rc,"Socker server start failed.");
    goto errLabel;
  }
  
  // create a socket
  if((rc = create( mgrHandle(h), userId, localPort, sockFlags, timeOutMs, _socketTestCbFunc, nullptr, remoteAddrIp, remotePort, localIpAddrPtr)) != kOkRC )
  {
    cwLogError(rc,"Socket server socket create failed.");
    goto errLabel;
  }

  printf("'quit' to exit\n");

  // readline loop
  while( true )
  {
    printf("? ");
    if( std::fgets(sbuf,sbufN,stdin) == sbuf )
    {
      printf("Sending:%s",sbuf);

      // send a message to the remote socket
      if( sock::send( mgrHandle(h), userId, -1, sbuf, strlen(sbuf)+1 ) != kOkRC )
        printf("Send failed.");

      if( strcmp(sbuf,"quit\n") == 0)
        break;
    }
  }
  
 errLabel:

  rc_t rc1 = kOkRC;

  // destroy the socket
  if( h.isValid() )
    rc1 = destroy( mgrHandle(h), userId );

  // destroy the socket manager
  rc_t rc2 = destroyMgrSrv(h);

  return rcSelect(rc,rc1,rc2);
}

cw::rc_t cw::socksrv::testMain( const object_t* cfg )
{
  rc_t               rc             = kOkRC;
  const char*        protocol_str   = nullptr;
  sock::portNumber_t localPort      = 5688;
  const char*        remoteAddrIp   = nullptr;
  sock::portNumber_t remotePort     = 5687;
  const char*        localNicDevice = nullptr;
  unsigned           flags          = 0;

  if((rc = cfg->getv("protocol",protocol_str,
                     "localPort",localPort )) != kOkRC )
  {
    rc = cwLogError(rc,"Required arg. parse failed.");
    goto errLabel;
  }

  if((rc = cfg->getv_opt("remoteAddr",remoteAddrIp,
                         "remotePort",remotePort,
                         "nicDev",localNicDevice )) != kOkRC )
  {
    rc = cwLogError(rc,"Optional arg. parse failed.");
    goto errLabel;
  }

  if( textIsEqual(protocol_str,"tcp") )
  {
    flags |= sock::kTcpFl | sock::kStreamFl | sock::kReuseAddrFl | sock::kReusePortFl;

    if( remoteAddrIp == nullptr )
      flags |= sock::kListenFl;
    
  }
    
  rc = test(localNicDevice,localPort,remoteAddrIp,remotePort,flags);

errLabel:
  
  return rc;
}
