mirror of
https://github.com/polserver/polserver
synced 2026-08-13 08:23:08 -04:00
808 lines
16 KiB
C++
808 lines
16 KiB
C++
/** @file
|
|
*
|
|
* @par History
|
|
*/
|
|
|
|
|
|
#include "wnsckt.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
#include "esignal.h"
|
|
#include "logfacility.h"
|
|
#include "passert.h"
|
|
#include "strutil.h"
|
|
|
|
#include "singlepoller.h"
|
|
|
|
#if defined( WINDOWS )
|
|
#define SOCKET_ERRNO( x ) WSA##x
|
|
#define socket_errno WSAGetLastError()
|
|
typedef int socklen_t;
|
|
|
|
#else
|
|
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
|
|
#define SOCKET_ERRNO( x ) x
|
|
#define socket_errno errno
|
|
|
|
#endif
|
|
|
|
#ifndef SCK_WATCH
|
|
#define SCK_WATCH 0
|
|
#endif
|
|
namespace Pol
|
|
{
|
|
namespace Clib
|
|
{
|
|
Socket::Socket() : _sck( INVALID_SOCKET ), _options( none )
|
|
{
|
|
memset( &_peer, 0, sizeof( _peer ) );
|
|
#ifdef _WIN32
|
|
static bool init;
|
|
if ( !init )
|
|
{
|
|
init = true;
|
|
WSADATA dummy;
|
|
WSAStartup( MAKEWORD( 1, 0 ), &dummy );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Socket::Socket( SOCKET sock ) : _sck( sock ), _options( none )
|
|
{
|
|
memset( &_peer, 0, sizeof( _peer ) );
|
|
}
|
|
|
|
Socket::Socket( Socket&& sock )
|
|
: _sck( std::move( sock._sck ) ),
|
|
_options( std::move( sock._options ) ),
|
|
_peer( std::move( sock._peer ) )
|
|
{
|
|
sock._sck = INVALID_SOCKET;
|
|
}
|
|
|
|
Socket& Socket::operator=( Socket&& sock )
|
|
{
|
|
_sck = std::move( sock._sck );
|
|
_options = std::move( sock._options );
|
|
_peer = std::move( sock._peer );
|
|
sock._sck = INVALID_SOCKET;
|
|
return *this;
|
|
}
|
|
|
|
Socket::~Socket()
|
|
{
|
|
close();
|
|
}
|
|
|
|
void Socket::setsocket( SOCKET sock )
|
|
{
|
|
close();
|
|
_sck = sock;
|
|
}
|
|
|
|
void Socket::set_options( option opt )
|
|
{
|
|
_options = opt;
|
|
}
|
|
|
|
void Socket::setpeer( struct sockaddr peer )
|
|
{
|
|
_peer = peer;
|
|
}
|
|
|
|
std::string Socket::getpeername() const
|
|
{
|
|
struct sockaddr client_addr; // inet_addr
|
|
socklen_t addrlen = sizeof client_addr;
|
|
if ( ::getpeername( _sck, &client_addr, &addrlen ) == 0 )
|
|
{
|
|
struct sockaddr_in* in_addr = (struct sockaddr_in*)&client_addr;
|
|
if ( client_addr.sa_family == AF_INET )
|
|
return inet_ntoa( in_addr->sin_addr );
|
|
else
|
|
return "(display error)";
|
|
}
|
|
else
|
|
return "Error retrieving peer name";
|
|
}
|
|
struct sockaddr Socket::peer_address() const
|
|
{
|
|
return _peer;
|
|
}
|
|
|
|
SOCKET Socket::handle() const
|
|
{
|
|
return _sck;
|
|
}
|
|
|
|
SOCKET Socket::release_handle()
|
|
{
|
|
SOCKET s = _sck;
|
|
_sck = INVALID_SOCKET;
|
|
return s;
|
|
}
|
|
|
|
|
|
bool Socket::open( const char* ipaddr, unsigned short port )
|
|
{
|
|
close();
|
|
_sck = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
|
|
if ( _sck == INVALID_SOCKET
|
|
#ifndef WINDOWS
|
|
|| _sck < 0
|
|
#endif
|
|
)
|
|
{
|
|
INFO_PRINTLN( "Unable to open socket in Socket::open()" );
|
|
return false;
|
|
}
|
|
|
|
struct sockaddr_in sin;
|
|
memset( &sin, 0, sizeof sin );
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_addr.s_addr = inet_addr( ipaddr );
|
|
sin.sin_port = htons( port );
|
|
|
|
|
|
int res = connect( _sck, (struct sockaddr*)&sin, sizeof sin );
|
|
|
|
if ( res == 0 )
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
#ifdef _WIN32
|
|
closesocket( _sck );
|
|
#else
|
|
::close( _sck );
|
|
#endif
|
|
_sck = INVALID_SOCKET;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Socket::disable_nagle()
|
|
{
|
|
if ( _sck == INVALID_SOCKET )
|
|
return;
|
|
|
|
int tcp_nodelay = 1;
|
|
int res = setsockopt( _sck, IPPROTO_TCP, TCP_NODELAY, (const char*)&tcp_nodelay,
|
|
sizeof( tcp_nodelay ) );
|
|
if ( res < 0 )
|
|
{
|
|
throw std::runtime_error( "Unable to setsockopt (TCP_NODELAY) on socket, res=" +
|
|
Clib::tostring( res ) );
|
|
}
|
|
}
|
|
|
|
void Socket::apply_socket_options( SOCKET sck )
|
|
{
|
|
if ( _options & nonblocking )
|
|
{
|
|
#ifdef _WIN32
|
|
u_long nonblocking = 1;
|
|
int res = ioctlsocket( sck, FIONBIO, &nonblocking );
|
|
#else
|
|
int flags = fcntl( sck, F_GETFL );
|
|
if ( flags == -1 )
|
|
flags = O_NONBLOCK;
|
|
else
|
|
flags |= O_NONBLOCK;
|
|
int res = fcntl( sck, F_SETFL, flags );
|
|
#endif
|
|
if ( res < 0 )
|
|
{
|
|
throw std::runtime_error( "Unable to set listening socket to nonblocking mode, res=" +
|
|
tostring( res ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void Socket::apply_prebind_socket_options( SOCKET sck )
|
|
{
|
|
if ( sck != INVALID_SOCKET && _options & reuseaddr )
|
|
{
|
|
#ifndef WIN32
|
|
int reuse_opt = 1;
|
|
int res =
|
|
setsockopt( sck, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_opt, sizeof( reuse_opt ) );
|
|
if ( res < 0 )
|
|
{
|
|
throw std::runtime_error( "Unable to setsockopt (SO_REUSEADDR) on listening socket, res = " +
|
|
tostring( res ) );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool Socket::listen( unsigned short port )
|
|
{
|
|
struct sockaddr_in local;
|
|
|
|
close();
|
|
local.sin_family = AF_INET;
|
|
local.sin_addr.s_addr = INADDR_ANY;
|
|
/* Port MUST be in Network Byte Order */
|
|
local.sin_port = htons( port );
|
|
memset( local.sin_zero, 0, sizeof( local.sin_zero ) ); // not needed, but for completeness
|
|
|
|
_sck = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
|
|
if ( _sck == INVALID_SOCKET )
|
|
{
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
apply_socket_options( _sck );
|
|
apply_prebind_socket_options( _sck );
|
|
|
|
if ( bind( _sck, (struct sockaddr*)&local, sizeof( local ) ) == -1 )
|
|
{
|
|
HandleError();
|
|
return false;
|
|
}
|
|
if ( ::listen( _sck, SOMAXCONN ) == -1 )
|
|
{
|
|
HandleError();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Socket::has_incoming_data( unsigned int waitms, int* result )
|
|
{
|
|
SinglePoller poller( _sck );
|
|
poller.set_timeout( 0, waitms * 1000 );
|
|
|
|
if ( !poller.prepare( false ) )
|
|
{
|
|
if ( result )
|
|
*result = -1;
|
|
throw std::runtime_error( "Unable to poll socket=" + tostring( _sck ) );
|
|
}
|
|
|
|
int res = -1;
|
|
do
|
|
{
|
|
res = poller.wait_for_events();
|
|
} while ( res < 0 && !exit_signalled && socket_errno == SOCKET_ERRNO( EINTR ) );
|
|
|
|
if ( result )
|
|
*result = res;
|
|
|
|
return poller.incoming();
|
|
}
|
|
|
|
bool Socket::accept( SOCKET* s, unsigned int /*mstimeout*/ )
|
|
{
|
|
*s = ::accept( _sck, nullptr, nullptr );
|
|
if ( *s != INVALID_SOCKET )
|
|
{
|
|
apply_socket_options( *s );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
*s = INVALID_SOCKET;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Socket::accept( Socket* newsocket )
|
|
{
|
|
struct sockaddr client_addr;
|
|
socklen_t addrlen = sizeof client_addr;
|
|
SOCKET s = ::accept( _sck, &client_addr, &addrlen );
|
|
if ( s != INVALID_SOCKET )
|
|
{
|
|
apply_socket_options( s );
|
|
newsocket->setsocket( s );
|
|
newsocket->setpeer( client_addr );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Socket::connected() const
|
|
{
|
|
return ( _sck != INVALID_SOCKET );
|
|
}
|
|
|
|
/* Read and clear the error value */
|
|
void Socket::HandleError()
|
|
{
|
|
#ifdef _WIN32
|
|
int ErrVal;
|
|
#if SCK_WATCH
|
|
static char ErrorBuffer[80];
|
|
#endif
|
|
|
|
ErrVal = WSAGetLastError();
|
|
WSASetLastError( 0 );
|
|
|
|
switch ( ErrVal )
|
|
{
|
|
case WSAENOTSOCK: /* Software caused connection to abort */
|
|
case WSAECONNRESET: /* Arg list too long */
|
|
close();
|
|
break;
|
|
|
|
default:
|
|
close(); /*
|
|
gee, we'll close here,too.
|
|
if you want to _not_ close for _specific_ error codes,
|
|
feel more than free to make exceptions; but the general
|
|
rule should be, close on error.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
#if SCK_WATCH
|
|
if ( FormatMessage( FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandle( TEXT( "wsock32" ) ), ErrVal,
|
|
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), ErrorBuffer, sizeof ErrorBuffer,
|
|
nullptr ) == 0 )
|
|
{
|
|
sprintf( ErrorBuffer, "Unknown error code 0x%08x", ErrVal );
|
|
}
|
|
INFO_PRINTLN( std::string( ErrorBuffer ) );
|
|
#endif
|
|
#else
|
|
close();
|
|
#endif
|
|
}
|
|
|
|
bool Socket::recvbyte( unsigned char* ch, unsigned int waitms )
|
|
{
|
|
if ( !connected() )
|
|
return false;
|
|
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{L;1}" );
|
|
#endif
|
|
|
|
int res;
|
|
if ( !has_incoming_data( waitms, &res ) )
|
|
{
|
|
if ( res == -1 )
|
|
{
|
|
HandleError();
|
|
close();
|
|
}
|
|
else if ( res == 0 )
|
|
{
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{TO}" );
|
|
#endif
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
res = recv( _sck, (char*)ch, 1, 0 );
|
|
if ( res == 1 )
|
|
{
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{{{:#x}}}", *ch );
|
|
#endif
|
|
return true;
|
|
}
|
|
else if ( res == 0 )
|
|
{
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{CLOSE}" );
|
|
#endif
|
|
close();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* Can't time out here this is an ERROR! */
|
|
HandleError();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Socket::recvdata_nowait( char* pdest, unsigned len, int* bytes_read )
|
|
{
|
|
if ( bytes_read )
|
|
*bytes_read = -1;
|
|
|
|
if ( !connected() )
|
|
return false;
|
|
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{{L:{}}}", len );
|
|
#endif
|
|
|
|
int res;
|
|
res = ::recv( _sck, pdest, len, 0 );
|
|
|
|
if ( bytes_read )
|
|
*bytes_read = res;
|
|
|
|
if ( res < 0 )
|
|
{
|
|
/* Can't time out here this is an ERROR! */
|
|
HandleError();
|
|
return false;
|
|
}
|
|
|
|
if ( res == 0 )
|
|
{
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{CLOSE}" );
|
|
#endif
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Socket::recvdata( void* vdest, unsigned len, unsigned int waitms )
|
|
{
|
|
char* pdest = (char*)vdest;
|
|
|
|
while ( len )
|
|
{
|
|
if ( !connected() )
|
|
return false;
|
|
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{{L:{}}}", len );
|
|
#endif
|
|
int res;
|
|
if ( !has_incoming_data( waitms, &res ) )
|
|
{
|
|
if ( res == -1 )
|
|
{
|
|
HandleError();
|
|
close();
|
|
}
|
|
else if ( res == 0 )
|
|
{
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{TO}" );
|
|
#endif
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
res = ::recv( _sck, pdest, len, 0 );
|
|
if ( res > 0 )
|
|
{
|
|
#if SCK_WATCH
|
|
char* tmp = pdest;
|
|
INFO_PRINTLN( "{{R:{}[{}]}}", res, len );
|
|
#endif
|
|
len -= res;
|
|
pdest += res;
|
|
|
|
#if SCK_WATCH
|
|
while ( res-- )
|
|
INFO_PRINTLN( "{{{:#x}}}", (unsigned char)( *tmp++ ) );
|
|
#endif
|
|
}
|
|
else if ( res == 0 )
|
|
{
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{CLOSE}" );
|
|
#endif
|
|
close();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* Can't time out here this is an ERROR! */
|
|
HandleError();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
unsigned Socket::peek( void* vdest, unsigned len, unsigned int wait_sec )
|
|
{
|
|
;
|
|
char* pdest = (char*)vdest;
|
|
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{{L:{}}}", len );
|
|
#endif
|
|
|
|
int res;
|
|
if ( !has_incoming_data( 1000 * wait_sec, &res ) )
|
|
{
|
|
if ( res == -1 )
|
|
{
|
|
HandleError();
|
|
close();
|
|
}
|
|
else if ( res == 0 )
|
|
{
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{TO}" );
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
res = ::recv( _sck, pdest, len, MSG_PEEK );
|
|
if ( res > 0 )
|
|
{
|
|
return res;
|
|
}
|
|
else if ( res == 0 )
|
|
{
|
|
#if SCK_WATCH
|
|
INFO_PRINTLN( "{CLOSE}" );
|
|
#endif
|
|
close();
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
/* Can't time out here this is an ERROR! */
|
|
HandleError();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void Socket::send( const void* vdata, unsigned datalen )
|
|
{
|
|
const char* cdata = static_cast<const char*>( vdata );
|
|
|
|
while ( datalen )
|
|
{
|
|
int res = ::send( _sck, cdata, datalen, 0 );
|
|
if ( res < 0 )
|
|
{
|
|
int sckerr = socket_errno;
|
|
if ( sckerr == SOCKET_ERRNO( EWOULDBLOCK ) )
|
|
{
|
|
// FIXME sleep
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
INFO_PRINTLN( "Socket::send() error: {}", sckerr );
|
|
HandleError();
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
datalen -= res;
|
|
cdata += res;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Socket::send_nowait( const void* vdata, unsigned datalen, unsigned* nsent )
|
|
{
|
|
const char* cdata = static_cast<const char*>( vdata );
|
|
|
|
*nsent = 0;
|
|
|
|
while ( datalen )
|
|
{
|
|
int res = ::send( _sck, cdata, datalen, 0 );
|
|
if ( res < 0 )
|
|
{
|
|
int sckerr = socket_errno;
|
|
if ( sckerr == SOCKET_ERRNO( EWOULDBLOCK ) )
|
|
{
|
|
// FIXME sleep
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
INFO_PRINTLN( "Socket::send_nowait() error: {}", sckerr );
|
|
HandleError();
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
datalen -= res;
|
|
cdata += res;
|
|
*nsent += res;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Socket::write( const std::string& s )
|
|
{
|
|
send( (void*)s.c_str(), static_cast<unsigned int>( s.length() ) );
|
|
}
|
|
|
|
|
|
void Socket::close()
|
|
{
|
|
if ( _sck != INVALID_SOCKET )
|
|
{
|
|
#ifdef _WIN32
|
|
shutdown( _sck, 2 ); // 2 is both sides. defined in winsock2.h ...
|
|
closesocket( _sck );
|
|
#else
|
|
shutdown( _sck, SHUT_RDWR );
|
|
::close( _sck );
|
|
#endif
|
|
_sck = INVALID_SOCKET;
|
|
}
|
|
}
|
|
|
|
bool Socket::is_local() const
|
|
{
|
|
std::string s = getpeername();
|
|
return ( s == "127.0.0.1" );
|
|
}
|
|
|
|
bool is_invalid_readline_char( unsigned char c )
|
|
{
|
|
return !isprint( c ) && c != '\n';
|
|
}
|
|
|
|
bool SocketLineReader::try_read( std::string& out, bool* timed_out )
|
|
{
|
|
if ( timed_out )
|
|
*timed_out = false;
|
|
|
|
// check if there is already a line in the buffer
|
|
auto pos_newline = _currentLine.find_first_of( "\n" );
|
|
|
|
// If not, try to read more data
|
|
if ( pos_newline == std::string::npos )
|
|
{
|
|
std::array<char, 4096> buffer;
|
|
|
|
int res = -1;
|
|
if ( !_socket.has_incoming_data( _waitms, &res ) )
|
|
{
|
|
if ( timed_out )
|
|
*timed_out = true;
|
|
return false;
|
|
}
|
|
int bytes_read = -1;
|
|
if ( !_socket.recvdata_nowait( buffer.data(), static_cast<unsigned>( buffer.size() ),
|
|
&bytes_read ) )
|
|
return false;
|
|
|
|
// store current line size so we don't need to search from the beginning again
|
|
size_t oldSize = _currentLine.size();
|
|
|
|
// remove invalid characters from buffer
|
|
auto buffer_end =
|
|
std::remove_if( buffer.begin(), buffer.begin() + bytes_read, is_invalid_readline_char );
|
|
|
|
auto valid_char_count = std::distance( buffer.begin(), buffer_end );
|
|
|
|
// nothing gained from these bytes
|
|
if ( !valid_char_count )
|
|
return false;
|
|
|
|
// append only the valid characters to the buffer
|
|
_currentLine.append( buffer.data(), valid_char_count );
|
|
|
|
// update position
|
|
pos_newline = _currentLine.find_first_of( "\n", oldSize );
|
|
}
|
|
|
|
// note that std::string::npos is larger than any other number, so the conditon below will be
|
|
// false only if there is a newline before the maximum line length or if the current line is still
|
|
// small.
|
|
if ( _maxLinelength > 0 && pos_newline > _maxLinelength && _currentLine.size() > _maxLinelength )
|
|
{
|
|
out = _currentLine;
|
|
_currentLine.clear();
|
|
return false;
|
|
}
|
|
|
|
// Haven't found it yet
|
|
if ( pos_newline == std::string::npos )
|
|
return false;
|
|
|
|
auto end_newline = pos_newline + 1;
|
|
|
|
out = _currentLine.substr( 0, pos_newline );
|
|
_currentLine.erase( 0, end_newline );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SocketByteReader::try_read( std::string& out, bool* timed_out )
|
|
{
|
|
if ( timed_out )
|
|
*timed_out = false;
|
|
|
|
std::array<char, 4096> buffer;
|
|
|
|
int res = -1;
|
|
if ( !_socket.has_incoming_data( _waitms, &res ) )
|
|
{
|
|
if ( timed_out )
|
|
*timed_out = true;
|
|
return false;
|
|
}
|
|
int bytes_read = -1;
|
|
if ( !_socket.recvdata_nowait( buffer.data(), static_cast<unsigned>( buffer.size() ),
|
|
&bytes_read ) )
|
|
return false;
|
|
|
|
out = std::string( buffer.data(), bytes_read );
|
|
|
|
return true;
|
|
}
|
|
|
|
// Blocks until a whole line is received, waitms are over or maxlen is reached
|
|
bool SocketReader::read( std::string& out, bool* timed_out )
|
|
{
|
|
out = "";
|
|
if ( timed_out )
|
|
*timed_out = false;
|
|
|
|
const int max_timeouts = ( _timeout_secs * 1000 ) / _waitms;
|
|
bool single_timed_out = false;
|
|
|
|
int timeout_left = max_timeouts;
|
|
while ( !Clib::exit_signalled && _socket.connected() )
|
|
{
|
|
if ( try_read( out, &single_timed_out ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// if try_read() is false, string "out" should be empty unless the maxlen was reached.
|
|
if ( !out.empty() )
|
|
{
|
|
_socket.close();
|
|
return false;
|
|
}
|
|
|
|
if ( _timeout_secs > 0 && single_timed_out )
|
|
{
|
|
timeout_left--;
|
|
if ( timeout_left <= 0 )
|
|
{
|
|
if ( _disconnect_on_timeout || !timed_out )
|
|
_socket.close();
|
|
|
|
if ( timed_out )
|
|
*timed_out = true;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// refresh timeout counter
|
|
timeout_left = max_timeouts;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
} // namespace Clib
|
|
} // namespace Pol
|