/*
 * $Id: hbinet.c 13326 2009-12-20 04:01:34Z vszakats $
 */

/*
 * xHarbour Project source code:
 *    The internet protocol / TCP support
 *
 * Copyright 2002 Giancarlo Niccolai [gian@niccolai.ws]
 *                Ron Pinkas [Ron@RonPinkas.com]
 *                Marcelo Lombardo [marcelo.lombardo@newage-software.com.br]
 * www - http://www.xharbour.org
 *
 * Copyright 2007 Przemyslaw Czerpak <druzus / at / priv.onet.pl>
 *    updated and ported to Harbour
 * www - http://www.harbour-project.org
 *
 * Copyright 2008 Miguel Angel marchuet <miguelangel@marchuet.net>
 *    added dinamic system buffer
 * www - http://www.harbour-project.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/).
 *
 * As a special exception, the Harbour Project gives permission for
 * additional uses of the text contained in its release of Harbour.
 *
 * The exception is that, if you link the Harbour libraries with other
 * files to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * Your use of that executable is in no way restricted on account of
 * linking the Harbour library code into it.
 *
 * This exception does not however invalidate any other reasons why
 * the executable file might be covered by the GNU General Public License.
 *
 * This exception applies only to the code released by the Harbour
 * Project under the name Harbour.  If you copy code from other
 * Harbour Project or Free Software Foundation releases into a copy of
 * Harbour, as the General Public License permits, the exception does
 * not apply to the code that you add in this way.  To avoid misleading
 * anyone as to the status of such modified files, you must delete
 * this exception notice from them.
 *
 * If you write modifications of your own for Harbour, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.
 *
 */

#include "hbapi.h"
#include "hbapiitm.h"
#include "hbsocket.h"
#include "hbapierr.h"
#include "hbvm.h"
#include "hbthread.h"

#define HB_INET_ERR_OK           0
#define HB_INET_ERR_TIMEOUT      ( -1 )
#define HB_INET_ERR_CLOSEDCONN   ( -2 )
#define HB_INET_ERR_BUFFOVERRUN  ( -3 )
#define HB_INET_ERR_CLOSEDSOCKET ( -4 )

typedef struct
{
   HB_SOCKET sd;
   void *    remote;
   unsigned  remotelen;
   char *    buffer;
   long      inbuffer;
   long      posbuffer;
   long      readahead;
   int       iError;
   int       iCount;
   int       iTimeout;
   int       iTimeLimit;
   PHB_ITEM  pPeriodicBlock;
} HB_SOCKET_STRUCT, * PHB_SOCKET_STRUCT;

#define HB_INET_BUFFER_LEN    256

#define HB_INET_INITIALIZE()  if( s_initialize ) hb_inetAutoInit()

#define HB_PARSOCKET( n )     ( ( PHB_SOCKET_STRUCT ) hb_parptrGC( &s_gcInetFuncs, n ) )

#define HB_SOCKET_INIT( s, p ) \
      do { \
         HB_INET_INITIALIZE(); \
         s = ( PHB_SOCKET_STRUCT ) hb_gcAllocate( sizeof( HB_SOCKET_STRUCT ), &s_gcInetFuncs ); \
         memset( s, 0, sizeof( HB_SOCKET_STRUCT ) ); \
         s->sd = HB_NO_SOCKET; \
         s->readahead = HB_INET_BUFFER_LEN; \
         s->iTimeout = -1; \
         s->iTimeLimit = -1; \
         s->iError = HB_INET_ERR_OK; \
         p = hb_itemPutPtrGC( p, s ); \
      } while( 0 )

static const char * s_inetCRLF = "\r\n";

static HB_COUNTER s_initialize = 1;

#if defined( HB_OS_LINUX )
/* #define HB_INET_LINUX_INTERRUPT     SIGUSR1 + 90 */
#  ifdef HB_INET_LINUX_INTERRUPT
#     include <signal.h>

static void hb_inetLinuxSigusrHandle( int sig )
{
   /* nothing to do */
   HB_SYMBOL_UNUSED( sig );
}
#  endif
#endif

static void hb_inetErrRT( void )
{
   hb_errRT_BASE_SubstR( EG_ARG, 3012, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
}

static BOOL hb_inetIsOpen( PHB_SOCKET_STRUCT socket )
{
   if( socket->sd == HB_NO_SOCKET )
   {
      socket->iError = HB_INET_ERR_CLOSEDSOCKET;
      return FALSE;
   }
   return TRUE;
}

static int hb_inetCloseSocket( PHB_SOCKET_STRUCT socket )
{
   int ret = hb_socketClose( socket->sd );
   socket->sd = HB_NO_SOCKET;
   socket->inbuffer = 0;
   return ret;
}

static HB_GARBAGE_FUNC( hb_inetSocketFinalize )
{
   PHB_SOCKET_STRUCT socket = ( PHB_SOCKET_STRUCT ) Cargo;

   if( socket->sd != HB_NO_SOCKET )
   {
      hb_socketShutdown( socket->sd, HB_SOCKET_SHUT_RDWR );
      hb_inetCloseSocket( socket );
   }

   if( socket->pPeriodicBlock )
   {
      hb_itemRelease( socket->pPeriodicBlock );
      socket->pPeriodicBlock = NULL;
   }
   if( socket->remote )
   {
      hb_xfree( socket->remote );
      socket->remote = NULL;
   }
   if( socket->buffer )
   {
      hb_xfree( socket->buffer );
      socket->buffer = NULL;
   }
}

static HB_GARBAGE_FUNC( hb_inetSocketMark )
{
   PHB_SOCKET_STRUCT socket = ( PHB_SOCKET_STRUCT ) Cargo;

   if( socket->pPeriodicBlock )
      hb_gcMark( socket->pPeriodicBlock );
}

static const HB_GC_FUNCS s_gcInetFuncs =
{
   hb_inetSocketFinalize,
   hb_inetSocketMark
};

/*****************************************************
* Socket Initialization
***/

static void hb_inetAutoInit( void )
{
   if( s_initialize )
   {
      if( hb_atomic_dec( &s_initialize ) )
      {
         hb_socketInit();
#if defined( HB_INET_LINUX_INTERRUPT )
         signal( HB_INET_LINUX_INTERRUPT, hb_inetLinuxSigusrHandle );
#endif
      }
   }
}

HB_FUNC( HB_INETINIT )
{
   int ret;
   hb_atomic_set( &s_initialize, 0 );
   ret = hb_socketInit();
   if( ret == 0 )
   {
#if defined( HB_INET_LINUX_INTERRUPT )
      signal( HB_INET_LINUX_INTERRUPT, hb_inetLinuxSigusrHandle );
#endif
   }
   hb_retl( ret == 0 );
}

HB_FUNC( HB_INETCLEANUP )
{
   hb_socketCleanup();
}

/*****************************************************
* Socket Creation and destruction
***/

HB_FUNC( HB_INETCREATE )
{
   PHB_ITEM pSocket = NULL;
   PHB_SOCKET_STRUCT socket;
   HB_SOCKET_INIT( socket, pSocket );

   if( HB_ISNUM( 1 ) )
      socket->iTimeout = hb_parni( 1 );

   hb_itemReturnRelease( pSocket );
}

HB_FUNC( HB_INETCLOSE )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      if( socket->sd != HB_NO_SOCKET )
      {
         hb_socketShutdown( socket->sd, HB_SOCKET_SHUT_RDWR );
         hb_retni( hb_inetCloseSocket( socket ) );
#ifdef HB_INET_LINUX_INTERRUPT
         kill( 0, HB_INET_LINUX_INTERRUPT );
#endif
      }
      else
         hb_retni( -1 );
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETFD )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      hb_retnint( socket->sd );

      if( hb_parl( 2 ) )
         socket->sd = HB_NO_SOCKET;
   }
   else
      hb_inetErrRT();
}

/************************************************
* Socket data access & management
***/

HB_FUNC( HB_INETSTATUS )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
      hb_retni( socket->sd == HB_NO_SOCKET ? -1 : 1 ); /* TODO: hb_retni( socket->status ); */
   else
      hb_inetErrRT();
}

/* Prepared, but still not used; being in wait for comments
HB_FUNC( HB_INETSTATUSDESC )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      switch( socket->status )
      {
         case 0:  hb_retc_const( "Connection not opened" ); return;
         case 1:  hb_retc_const( "Connection alive" ); return;
         case 2:  hb_retc_const( "Last operation error" ); return;
         case 3:  hb_retc_const( "Last operation timeout" ); return;
         default: hb_retc_const( "unknown" );
      }
   }
   else
      hb_inetErrRT();
}
*/

HB_FUNC( HB_INETERRORCODE )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
      hb_retni( socket->iError );
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETERRORDESC )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      switch( socket->iError )
      {
         case HB_INET_ERR_OK           : hb_retc_null(); return;
         case HB_INET_ERR_TIMEOUT      : hb_retc_const( "Timeout" ); return;
         case HB_INET_ERR_CLOSEDCONN   : hb_retc_const( "Connection closed" ); return;
         case HB_INET_ERR_CLOSEDSOCKET : hb_retc_const( "Closed socket" ); return;
         case HB_INET_ERR_BUFFOVERRUN  : hb_retc_const( "Buffer overrun" ); return;
         default:
            hb_retc( hb_socketErrorStr( socket->iError ) );
      }
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETCLEARERROR )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
      socket->iError = HB_INET_ERR_OK;
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETCOUNT )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
      hb_retni( socket->iCount );
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETADDRESS )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      char * szAddr = socket->remote ?
            hb_socketAddrGetName( socket->remote, socket->remotelen ) : NULL;
      if( szAddr )
         hb_retc_buffer( szAddr );
      else
         hb_retc_null();
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETPORT )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
      hb_retni( socket->remote ?
                hb_socketAddrGetPort( socket->remote, socket->remotelen ) : 0 );
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETTIMEOUT )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      hb_retni( socket->iTimeout );

      if( HB_ISNUM( 2 ) )
         socket->iTimeout = hb_parni( 2 );
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETCLEARTIMEOUT )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
      socket->iTimeout = -1;
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETTIMELIMIT )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      hb_retnl( socket->iTimeLimit );

      if( HB_ISNUM( 2 ) )
         socket->iTimeLimit = hb_parnl( 2 );
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETCLEARTIMELIMIT )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
      socket->iTimeLimit = -1;
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETPERIODCALLBACK )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      PHB_ITEM pExec = hb_param( 2, HB_IT_ARRAY | HB_IT_BLOCK | HB_IT_SYMBOL );

      if( socket->pPeriodicBlock )
         hb_itemReturn( socket->pPeriodicBlock );

      if( pExec )
      {
         if( socket->pPeriodicBlock )
            hb_itemRelease( socket->pPeriodicBlock );
         socket->pPeriodicBlock = hb_itemClone( pExec );
         hb_gcUnlock( socket->pPeriodicBlock );
      }
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETCLEARPERIODCALLBACK )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      if( socket->pPeriodicBlock )
      {
         hb_itemRelease( socket->pPeriodicBlock );
         socket->pPeriodicBlock = NULL;
      }
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETGETSNDBUFSIZE )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      int iSize = -1;
      if( hb_inetIsOpen( socket ) )
      {
         if( hb_socketGetSndBufSize( socket->sd, &iSize ) != 0 )
            iSize = -1;
      }
      hb_retni( iSize );
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETGETRCVBUFSIZE )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      int iSize = -1;
      if( hb_inetIsOpen( socket ) )
      {
         if( hb_socketGetRcvBufSize( socket->sd, &iSize ) != 0 )
            iSize = -1;
      }
      hb_retni( iSize );
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETSETSNDBUFSIZE )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      int iSize = -1;
      if( hb_inetIsOpen( socket ) )
      {
         iSize = hb_parni( 2 );
         hb_socketSetSndBufSize( socket->sd, iSize );
      }
      hb_retni( iSize );
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETSETRCVBUFSIZE )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket )
   {
      int iSize = -1;
      if( hb_inetIsOpen( socket ) )
      {
         iSize = hb_parni( 2 );
         hb_socketSetRcvBufSize( socket->sd, iSize );
      }
      hb_retni( iSize );
   }
   else
      hb_inetErrRT();
}



/********************************************************************
* TCP receive and send functions
***/

static long s_inetRecv( PHB_SOCKET_STRUCT socket, char * buffer, long size, BOOL readahead )
{
   long rec = 0;

   if( readahead && socket->inbuffer == 0 && socket->readahead > size )
   {
      if( socket->buffer == NULL )
         socket->buffer = ( char * ) hb_xgrab( socket->readahead );
      socket->posbuffer = 0;
      rec = hb_socketRecv( socket->sd, socket->buffer, socket->readahead,
                           0, socket->iTimeout );
      socket->inbuffer = HB_MAX( 0, rec );
   }
   else
      readahead = FALSE;

   if( socket->inbuffer > 0 )
   {
      rec = HB_MIN( size, socket->inbuffer );
      memcpy( buffer, socket->buffer + socket->posbuffer, rec );
      socket->posbuffer += rec;
      socket->inbuffer -= rec;
      if( size > rec && !readahead )
      {
         size = hb_socketRecv( socket->sd, buffer + rec, size - rec, 0, 0 );
         if( size > 0 )
            rec += size;
      }
   }
   else if( !readahead )
      rec = hb_socketRecv( socket->sd, buffer, size, 0, socket->iTimeout );

   return rec;
}

static void s_inetRecvInternal( int iMode )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );
   PHB_ITEM pBuffer = hb_param( 2, HB_IT_STRING );
   char * buffer;
   ULONG ulLen;
   int iLen, iMaxLen, iReceived;
   int iTimeElapsed;

   if( socket == NULL || pBuffer == NULL || ! HB_ISBYREF( 2 ) )
      hb_inetErrRT();
   else if( ! hb_inetIsOpen( socket ) )
      hb_retni( -1 );
   else
   {
      if( hb_itemGetWriteCL( pBuffer, &buffer, &ulLen ) )
         iLen = ( int ) ulLen;
      else
      {
         iLen = 0;
         buffer = NULL;
      }

      if( HB_ISNUM( 3 ) )
      {
         iMaxLen = hb_parni( 3 );
         if( iMaxLen < 0 )
            iMaxLen = 0;
         else if( iLen < iMaxLen )
            iMaxLen = iLen;
      }
      else
         iMaxLen = iLen;

      iReceived = 0;
      iTimeElapsed = 0;
      socket->iError = HB_INET_ERR_OK;
      do
      {
         iLen = s_inetRecv( socket, buffer + iReceived, iMaxLen - iReceived, FALSE );
         if( iLen >= 0 )
         {
            iReceived += iLen;
            if( iMode == 0 ) /* Called from InetRecv()? */
               break;
         }
         else if( iLen == -1 && hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT )
         {
            /* timed out; let's see if we have to run a cb routine */
            iTimeElapsed += socket->iTimeout;
            /* if we have a pPeriodicBlock, timeLimit is our REAL timeout */
            if( socket->pPeriodicBlock )
            {
               hb_execFromArray( socket->pPeriodicBlock );
               /* do we continue? */
               if( hb_parl( -1 ) && hb_vmRequestQuery() == 0 &&
                   ( socket->iTimeLimit == -1 || iTimeElapsed < socket->iTimeLimit ) )
                  iLen = 1;   /* Declare success to continue loop */
            }
         }
      }
      while( iReceived < iMaxLen && iLen > 0 );

      socket->iCount = iReceived;

      if( iLen == 0 )
         socket->iError = HB_INET_ERR_CLOSEDCONN;
      else if( iLen < 0 )
      {
         if( hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT )
            socket->iError = HB_INET_ERR_TIMEOUT;
         else
            socket->iError = hb_socketGetError();
      }
      hb_retni( iReceived > 0 ? iReceived : iLen );
   }
}

HB_FUNC( HB_INETRECV )
{
   s_inetRecvInternal( 0 );
}


HB_FUNC( HB_INETRECVALL )
{
   s_inetRecvInternal( 1 );
}


static void s_inetRecvPattern( const char ** patterns, int * patternsizes,
                               int iPatternsCount, int iParam )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );
   PHB_ITEM pResult         = hb_param( iParam, HB_IT_BYREF );
   PHB_ITEM pMaxSize        = hb_param( iParam + 1, HB_IT_NUMERIC );
   PHB_ITEM pBufferSize     = hb_param( iParam + 2, HB_IT_NUMERIC );

   char cChar = '\0';
   char * buffer;
   int iPaternFound = 0;
   int iTimeElapsed = 0;
   int iPos = 0;
   int iLen;
   int iAllocated, iBufferSize, iMax;
   int i;

   if( socket == NULL )
   {
      hb_inetErrRT();
      return;
   }
   else if( ! hb_inetIsOpen( socket ) )
   {
      if( pResult )
         hb_itemPutNI( pResult, -1 );
      hb_retc_null();
      return;
   }


   iBufferSize = pBufferSize ? hb_itemGetNI( pBufferSize ) : 80;
   iMax = pMaxSize ? hb_itemGetNI( pMaxSize ) : 0;

   socket->iError = HB_INET_ERR_OK;

   buffer = ( char * ) hb_xgrab( iBufferSize );
   iAllocated = iBufferSize;

   do
   {
      if( iPos == iAllocated - 1 )
      {
         iAllocated += iBufferSize;
         buffer = ( char * ) hb_xrealloc( buffer, iAllocated );
      }

      iLen = s_inetRecv( socket, &cChar, 1, TRUE );
      if( iLen == -1 && hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT )
      {
         iLen = -2;     /* this signals timeout */
         iTimeElapsed += socket->iTimeout;
         if( socket->pPeriodicBlock )
         {
            BOOL fResult;

            hb_execFromArray( socket->pPeriodicBlock );
            fResult = hb_parl( -1 ) && hb_vmRequestQuery() == 0;

            if( fResult &&
                ( socket->iTimeLimit == -1 || iTimeElapsed < socket->iTimeLimit ) )
               iLen = 1;
         }
      }
      else if( iLen > 0 )
      {
         buffer[ iPos++ ] = cChar;
         for( i = 0; i < iPatternsCount; ++i )
         {
            if( patternsizes[ i ] <= iPos &&
                cChar == patterns[ i ][ patternsizes[ i ] - 1 ] )
            {
               if( memcmp( buffer + iPos - patternsizes[ i ],
                           patterns[ i ], patternsizes[ i ] ) == 0 )
               {
                  iPaternFound = i + 1;
                  break;
               }
            }
         }
      }
   }
   while( iLen > 0 && iPaternFound == 0 && ( iMax == 0 || iPos < iMax ) );

   if( iPaternFound )
   {
      socket->iCount = iPos;
      if( pResult )
         hb_itemPutNI( pResult, iPos );
      hb_retclen_buffer( buffer, iPos - patternsizes[ iPaternFound - 1 ] );
   }
   else
   {
      if( iLen == 0 )
         socket->iError = HB_INET_ERR_CLOSEDCONN;
      else if( iLen < 0 )
      {
         if( hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT )
            socket->iError = HB_INET_ERR_TIMEOUT;
         else
            socket->iError = hb_socketGetError();
      }
      else
      {
         socket->iError = HB_INET_ERR_BUFFOVERRUN;
         iLen = -1;
      }
      if( pResult )
         hb_itemPutNI( pResult, iLen );
      hb_xfree( buffer );
      hb_retc_null();
   }
}

HB_FUNC( HB_INETRECVLINE )
{
   int iEolLen = ( int ) strlen( s_inetCRLF );

   s_inetRecvPattern( &s_inetCRLF, &iEolLen, 1, 2 );
}

#define HB_PATERN_BUF_SIZE    16

HB_FUNC( HB_INETRECVENDBLOCK )
{
   PHB_ITEM pProto = hb_param( 2, HB_IT_ARRAY | HB_IT_STRING );
   const char * patterns_buf[ HB_PATERN_BUF_SIZE ];
   const char ** patterns = patterns_buf;
   int patternsizes_buf[ HB_PATERN_BUF_SIZE ];
   int * patternsizes = patternsizes_buf;
   int iPatternsCount = 0;
   int iLen;

   if( pProto && HB_IS_ARRAY( pProto ) )
   {
      int iPatternsMax = ( int ) hb_arrayLen( pProto ), i;

      for( i = 1; i <= iPatternsMax; i++ )
      {
         iLen = ( int ) hb_arrayGetCLen( pProto, i );
         if( iLen > 0 )
            ++iPatternsCount;
      }
      if( iPatternsCount > 0 )
      {
         if( iPatternsCount > HB_PATERN_BUF_SIZE )
         {
            patterns = ( const char ** ) hb_xgrab( sizeof( char * ) * iPatternsCount );
            patternsizes = ( int * ) hb_xgrab( sizeof( int ) * iPatternsCount );
         }
         iPatternsCount = 0;
         for( i = 1; i <= iPatternsMax; i++ )
         {
            iLen = ( int ) hb_arrayGetCLen( pProto, i );
            if( iLen > 0 )
            {
               patterns[ iPatternsCount ]     = hb_arrayGetCPtr( pProto, i );
               patternsizes[ iPatternsCount ] = iLen;
               ++iPatternsCount;
            }
         }
      }
   }

   if( iPatternsCount == 0 )
   {
      iLen = ( int ) hb_itemGetCLen( pProto );
      if( iLen > 0 )
      {
         patterns[ 0 ]     = hb_itemGetCPtr( pProto );
         patternsizes[ 0 ] = iLen;
      }
      else
      {
         patterns[ 0 ]     = s_inetCRLF;
         patternsizes[ 0 ] = strlen( s_inetCRLF );
      }
      iPatternsCount = 1;
   }

   s_inetRecvPattern( patterns, patternsizes, iPatternsCount, 3 );

   if( iPatternsCount > HB_PATERN_BUF_SIZE )
   {
      hb_xfree( ( void * ) patterns );
      hb_xfree( patternsizes );
   }
}


HB_FUNC( HB_INETDATAREADY )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );
   int iVal;

   if( socket == NULL || ( hb_pcount() >= 2 && ! HB_ISNUM( 2 ) ) )
      hb_inetErrRT();
   else if( ! hb_inetIsOpen( socket ) )
      hb_retni( -1 );
   else
   {
      socket->iError = HB_INET_ERR_OK;
      if( socket->inbuffer > 0 )
         iVal = 1;
      else
      {
         iVal = hb_socketSelectRead( socket->sd, HB_ISNUM( 2 ) ? hb_parnint( 2 ) : 0 );
         if( iVal < 0 )
            socket->iError = hb_socketGetError();
      }
      hb_retni( iVal );
   }
}


static void s_inetSendInternal( BOOL lAll )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );
   PHB_ITEM pBuffer = hb_param( 2, HB_IT_STRING );
   const char * buffer;
   int iLen, iSent, iSend;

   if( socket == NULL || pBuffer == NULL )
      hb_inetErrRT();
   else if( ! hb_inetIsOpen( socket ) )
      hb_retni( -1 );
   else
   {
      buffer = hb_itemGetCPtr( pBuffer );
      iSend = ( int ) hb_itemGetCLen( pBuffer );
      if( HB_ISNUM( 3 ) )
      {
         iLen = hb_parni( 3 );
         if( iLen < iSend )
            iSend = iLen;
      }

      socket->iError = HB_INET_ERR_OK;

      iSent = iLen = 0;
      while( iSent < iSend )
      {
         iLen = hb_socketSend( socket->sd, buffer + iSent, iSend - iSent, 0,
                               socket->iTimeout );
         if( iLen > 0 )
         {
            iSent += iLen;
            if( ! lAll )
               break;
         }
         else
         {
            if( iLen == -1 && hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT )
               socket->iError = HB_INET_ERR_TIMEOUT;
            else
               socket->iError = hb_socketGetError();
            break;
         }
      }
      socket->iCount = iSent;
      hb_retni( iSent > 0 ? iSent : iLen );
   }
}

HB_FUNC( HB_INETSEND )
{
   s_inetSendInternal( FALSE );
}

HB_FUNC( HB_INETSENDALL )
{
   s_inetSendInternal( TRUE );
}


/*******************************************
* Name resolution interface functions
***/

HB_FUNC( HB_INETGETHOSTS )
{
   const char * szHost = hb_parc( 1 );

   if( szHost )
   {
      PHB_ITEM pHosts;

      HB_INET_INITIALIZE();
      pHosts = hb_socketGetHosts( szHost, HB_SOCKET_PF_INET );
      if( pHosts )
         hb_itemReturnRelease( pHosts );
      else
         hb_reta( 0 );
   }
   else
      hb_inetErrRT();
}

HB_FUNC( HB_INETGETALIAS )
{
   const char * szHost = hb_parc( 1 );

   if( szHost )
   {
      PHB_ITEM pHosts;

      HB_INET_INITIALIZE();
      pHosts = hb_socketGetAliases( szHost, HB_SOCKET_PF_INET );
      if( pHosts )
         hb_itemReturnRelease( pHosts );
      else
         hb_reta( 0 );
   }
   else
      hb_inetErrRT();
}


/**********************************************
* Server Specific functions
****/

HB_FUNC( HB_INETSERVER )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 2 );
   PHB_ITEM pSocket = NULL;

   if( ! HB_ISNUM( 1 ) || ( socket == NULL && ! HB_ISNIL( 2 ) ) )
   {
      hb_inetErrRT();
      return;
   }

   if( !socket )
      HB_SOCKET_INIT( socket, pSocket );
   else if( socket->sd != HB_NO_SOCKET )
      hb_inetCloseSocket( socket );
   socket->sd = hb_socketOpen( HB_SOCKET_PF_INET, HB_SOCKET_PT_STREAM, 0 );
   if( socket->sd == HB_NO_SOCKET )
      socket->iError = hb_socketGetError();
   else
   {
      int iPort = hb_parni( 1 );
      const char * szAddress = hb_parc( 2 );
      int iListen = hb_parnidef( 3, 10 );

      if( socket->remote )
         hb_xfree( socket->remote );
      if( !hb_socketInetAddr( &socket->remote, &socket->remotelen, szAddress, iPort ) ||
          hb_socketBind( socket->sd, socket->remote, socket->remotelen ) != 0 ||
          hb_socketListen( socket->sd, iListen ) != 0 )
      {
         socket->iError = hb_socketGetError();
         hb_inetCloseSocket( socket );
      }
      else
         socket->iError = HB_INET_ERR_OK;
   }
   if( pSocket )
      hb_itemReturnRelease( pSocket );
   else
      hb_itemReturn( hb_param( 2, HB_IT_ANY ) );
}

HB_FUNC( HB_INETACCEPT )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );

   if( socket == NULL )
      hb_inetErrRT();
   else if( hb_inetIsOpen( socket ) )
   {
      void * sa;
      unsigned len;
      HB_SOCKET incoming = hb_socketAccept( socket->sd, &sa, &len, socket->iTimeout );

      if( incoming == HB_NO_SOCKET )
      {
         if( hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT )
            socket->iError = HB_INET_ERR_TIMEOUT;
         else
            socket->iError = hb_socketGetError();
      }
      else
      {
         PHB_SOCKET_STRUCT new_socket;
         PHB_ITEM pSocket = NULL;
         HB_SOCKET_INIT( new_socket, pSocket );
         new_socket->remote = sa;
         new_socket->remotelen = len;
         new_socket->sd = incoming;
         hb_itemReturnRelease( pSocket );
         socket->iError = HB_INET_ERR_OK;
      }
   }
}

/**********************************************
* Client specific (connection functions)
****/

static void hb_inetConnectInternal( BOOL fResolve )
{
   const char * szHost = hb_parc( 1 );
   char * szAddr = NULL;
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 3 );
   PHB_ITEM pSocket = NULL;
   int iPort = hb_parni( 2 );

   if( szHost == NULL || iPort == 0 || ( socket == NULL && ! HB_ISNIL( 3 ) ) )
      hb_inetErrRT();
   else
   {
      if( !socket )
         HB_SOCKET_INIT( socket, pSocket );
      else if( socket->sd != HB_NO_SOCKET )
         hb_inetCloseSocket( socket );

      if( fResolve )
         szHost = szAddr = hb_socketResolveAddr( szHost, HB_SOCKET_AF_INET );

      if( fResolve && !szAddr )
         socket->iError = hb_socketGetError();
      else
      {
         /* Creates comm socket */
         socket->sd = hb_socketOpen( HB_SOCKET_PF_INET, HB_SOCKET_PT_STREAM, 0 );
         if( socket->sd == HB_NO_SOCKET )
            socket->iError = hb_socketGetError();
         else
         {
            if( socket->remote )
               hb_xfree( socket->remote );
            if( hb_socketInetAddr( &socket->remote, &socket->remotelen,
                                   szHost, iPort ) )
            {
               hb_socketSetKeepAlive( socket->sd, TRUE );
               if( hb_socketConnect( socket->sd, socket->remote, socket->remotelen,
                                     socket->iTimeout ) != 0 )
                  socket->iError = hb_socketGetError();
               else
                  socket->iError = HB_INET_ERR_OK;
            }
            else
               socket->iError = hb_socketGetError();
         }
         if( szAddr )
            hb_xfree( szAddr );
      }
      if( pSocket )
         hb_itemReturnRelease( pSocket );
      else
         hb_itemReturn( hb_param( 3, HB_IT_ANY ) );
   }
}

HB_FUNC( HB_INETCONNECT )
{
   hb_inetConnectInternal( TRUE );
}

HB_FUNC( HB_INETCONNECTIP )
{
   hb_inetConnectInternal( FALSE );
}

/***********************************************************
* Datagram functions
************************************************************/

HB_FUNC( HB_INETDGRAMBIND )
{
   PHB_SOCKET_STRUCT socket;
   PHB_ITEM pSocket = NULL;
   int iPort = hb_parni( 1 );
   const char * szAddress;

   /* Parameter error checking */
   if( iPort == 0 || ( hb_pcount() >= 4 && ! HB_ISCHAR( 4 ) ) )
   {
      hb_inetErrRT();
      return;
   }

   HB_SOCKET_INIT( socket, pSocket );

   /* Creates comm socket */
   socket->sd = hb_socketOpen( HB_SOCKET_PF_INET, HB_SOCKET_PT_DGRAM, HB_SOCKET_IPPROTO_UDP );
   if( socket->sd == HB_NO_SOCKET )
   {
      socket->iError = hb_socketGetError();
      hb_itemReturnRelease( pSocket );
      return;
   }

   /* Setting broadcast if needed. */
   if( hb_parl( 3 ) )
      hb_socketSetBroadcast( socket->sd, TRUE );

   szAddress = hb_parc( 2 );
   if( socket->remote )
      hb_xfree( socket->remote );
   if( !hb_socketInetAddr( &socket->remote, &socket->remotelen,
                           szAddress, iPort ) ||
       hb_socketBind( socket->sd, socket->remote, socket->remotelen ) != 0 )
   {
      socket->iError = hb_socketGetError();
      hb_inetCloseSocket( socket );
   }
   else if( hb_pcount() >= 4 )
   {
      if( hb_socketSetMulticast( socket->sd, HB_SOCKET_PF_INET, hb_parc( 4 ) ) != 0 )
         socket->iError = hb_socketGetError();
   }

   hb_itemReturnRelease( pSocket );
}

HB_FUNC( HB_INETDGRAM )
{
   PHB_SOCKET_STRUCT socket;
   PHB_ITEM pSocket = NULL;

   HB_SOCKET_INIT( socket, pSocket );

   /* Creates comm socket */
   socket->sd = hb_socketOpen( HB_SOCKET_PF_INET, HB_SOCKET_PT_DGRAM, HB_SOCKET_IPPROTO_UDP );
   if( socket->sd == HB_NO_SOCKET )
   {
      socket->iError = hb_socketGetError();
      hb_itemReturnRelease( pSocket );
      return;
   }

   /* Setting broadcast if needed. */
   if( hb_parl( 1 ) )
      hb_socketSetBroadcast( socket->sd, TRUE );

   hb_itemReturnRelease( pSocket );
}

HB_FUNC( HB_INETDGRAMSEND )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );
   const char * szAddress = hb_parc( 2 );
   int iPort = hb_parni( 3 );
   PHB_ITEM pBuffer = hb_param( 4, HB_IT_STRING );
   int iLen;
   const char * szBuffer;

   if( socket == NULL || szAddress == NULL || iPort == 0 || pBuffer == NULL )
      hb_inetErrRT();
   else if( ! hb_inetIsOpen( socket ) )
   {
      socket->iCount = 0;
      hb_retni( -1 );
   }
   else
   {
      socket->iCount = 0;
      if( socket->remote )
         hb_xfree( socket->remote );
      if( !hb_socketInetAddr( &socket->remote, &socket->remotelen, szAddress, iPort ) )
      {
         socket->iError = hb_socketGetError();
         iLen = -1;
      }
      else
      {
         szBuffer = hb_itemGetCPtr( pBuffer );
         iLen = ( int ) hb_itemGetCLen( pBuffer );
         if( HB_ISNUM( 5 ) )
         {
            int iMaxLen = hb_parni( 5 );
            if( iMaxLen < iLen )
               iLen = HB_MAX( iMaxLen, 0 );
         }
         iLen = hb_socketSendTo( socket->sd, szBuffer, iLen, 0,
                                 socket->remote, socket->remotelen,
                                 socket->iTimeout );
         if( iLen == -1 )
         {
            if( hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT )
               socket->iError = HB_INET_ERR_TIMEOUT;
            else
               socket->iError = hb_socketGetError();
         }
         else
         {
            socket->iError = HB_INET_ERR_OK;
            socket->iCount = iLen;
         }
      }
      hb_retni( iLen );
   }
}

HB_FUNC( HB_INETDGRAMRECV )
{
   PHB_SOCKET_STRUCT socket = HB_PARSOCKET( 1 );
   PHB_ITEM pBuffer = hb_param( 2, HB_IT_STRING );
   int iTimeElapsed = 0;
   int iLen = 0, iMax;
   char * buffer = NULL;
   ULONG ulLen;
   BOOL fRepeat;

   if( socket == NULL || pBuffer == NULL || ! HB_ISBYREF( 2 ) )
      hb_inetErrRT();
   else if( ! hb_inetIsOpen( socket ) )
   {
      socket->iCount = 0;
      hb_retni( -1 );
   }
   else
   {
      socket->iCount = 0;
      if( hb_itemGetWriteCL( pBuffer, &buffer, &ulLen ) )
         iLen = ( int ) ulLen;
      if( HB_ISNUM( 3 ) )
      {
         iMax = hb_parni( 3 );
         if( iMax < iLen )
            iLen = HB_MAX( iMax, 0 );
      }

      do
      {
         fRepeat = FALSE;
         if( socket->remote )
            hb_xfree( socket->remote );
         iMax = hb_socketRecvFrom( socket->sd, buffer, iLen, 0,
                                   &socket->remote, &socket->remotelen,
                                   socket->iTimeout );
         iTimeElapsed += socket->iTimeout;
         if( socket->pPeriodicBlock )
         {
            hb_execFromArray( socket->pPeriodicBlock );
            /* do we continue? */
            fRepeat = hb_parl( -1 ) && hb_vmRequestQuery() == 0 &&
                      ( socket->iTimeLimit == -1 || iTimeElapsed < socket->iTimeLimit );
         }
      }
      while( fRepeat );

      if( iMax == 0 )
         socket->iError = HB_INET_ERR_CLOSEDCONN;
      else if( iMax < 0 )
      {
         if( hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT )
            socket->iError = HB_INET_ERR_TIMEOUT;
         else
            socket->iError = hb_socketGetError();
      }
      else
         socket->iError = HB_INET_ERR_OK;
      hb_retni( iMax );
   }
}


/***********************************************************
* Generic utility(?) functions
************************************************************/

HB_FUNC( HB_INETCRLF )
{
   hb_retc_const( s_inetCRLF );
}

HB_FUNC( HB_INETISSOCKET )
{
   hb_retl( HB_PARSOCKET( 1 ) != NULL );
}
