2023-11-11 16:43:58 +02:00
/*
* NetworkConnection . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "NetworkConnection.h"
VCMI_LIB_NAMESPACE_BEGIN
2024-05-11 16:32:43 +02:00
NetworkConnection : : NetworkConnection ( INetworkConnectionListener & listener , const std : : shared_ptr < NetworkSocket > & socket , const std : : shared_ptr < NetworkContext > & context )
2023-11-11 16:43:58 +02:00
: socket ( socket )
2024-05-11 17:12:49 +02:00
, timer ( std : : make_shared < NetworkTimer > ( * context ) )
2023-11-12 13:27:22 +02:00
, listener ( listener )
2023-11-11 16:43:58 +02:00
{
2023-12-25 21:23:27 +02:00
socket - > set_option ( boost : : asio : : ip : : tcp : : no_delay ( true ) ) ;
2024-03-16 11:29:36 +02:00
// iOS throws exception on attempt to set buffer size
constexpr auto bufferSize = 4 * 1024 * 1024 ;
try
{
socket - > set_option ( boost : : asio : : socket_base : : send_buffer_size { bufferSize } ) ;
}
catch ( const boost : : system : : system_error & e )
{
logNetwork - > error ( " error setting 'send buffer size' socket option: %s " , e . what ( ) ) ;
}
try
{
socket - > set_option ( boost : : asio : : socket_base : : receive_buffer_size { bufferSize } ) ;
}
catch ( const boost : : system : : system_error & e )
{
logNetwork - > error ( " error setting 'receive buffer size' socket option: %s " , e . what ( ) ) ;
}
2023-11-11 16:43:58 +02:00
}
void NetworkConnection : : start ( )
{
2024-05-11 16:32:43 +02:00
heartbeat ( ) ;
2024-05-11 17:12:49 +02:00
startReceiving ( ) ;
}
2024-05-11 16:32:43 +02:00
2024-05-11 17:12:49 +02:00
void NetworkConnection : : startReceiving ( )
{
2023-11-11 16:43:58 +02:00
boost : : asio : : async_read ( * socket ,
readBuffer ,
boost : : asio : : transfer_exactly ( messageHeaderSize ) ,
2024-02-02 15:32:06 +02:00
[ self = shared_from_this ( ) ] ( const auto & ec , const auto & endpoint ) { self - > onHeaderReceived ( ec ) ; } ) ;
2023-11-11 16:43:58 +02:00
}
2024-05-11 16:32:43 +02:00
void NetworkConnection : : heartbeat ( )
{
constexpr auto heartbeatInterval = std : : chrono : : seconds ( 10 ) ;
2024-05-11 17:12:49 +02:00
timer - > expires_after ( heartbeatInterval ) ;
2024-05-12 21:13:17 +02:00
timer - > async_wait ( [ self = weak_from_this ( ) ] ( const auto & ec )
2024-05-11 16:32:43 +02:00
{
if ( ec )
return ;
2024-05-12 21:13:17 +02:00
auto locked = self . lock ( ) ;
if ( ! locked )
2024-05-11 16:32:43 +02:00
return ;
2024-05-12 21:13:17 +02:00
locked - > sendPacket ( { } ) ;
locked - > heartbeat ( ) ;
2024-05-11 16:32:43 +02:00
} ) ;
}
2024-02-03 22:59:56 +02:00
void NetworkConnection : : onHeaderReceived ( const boost : : system : : error_code & ecHeader )
2023-11-11 16:43:58 +02:00
{
2024-02-03 22:59:56 +02:00
if ( ecHeader )
2023-11-12 13:27:22 +02:00
{
2024-05-12 21:13:17 +02:00
onError ( ecHeader . message ( ) ) ;
2023-11-12 13:27:22 +02:00
return ;
}
2023-11-11 16:43:58 +02:00
if ( readBuffer . size ( ) < messageHeaderSize )
throw std : : runtime_error ( " Failed to read header! " ) ;
uint32_t messageSize ;
2024-02-02 01:27:19 +02:00
readBuffer . sgetn ( reinterpret_cast < char * > ( & messageSize ) , sizeof ( messageSize ) ) ;
2023-11-11 16:43:58 +02:00
if ( messageSize > messageMaxSize )
2024-02-03 22:59:56 +02:00
{
2024-05-12 21:13:17 +02:00
onError ( " Invalid packet size! " ) ;
2024-02-03 22:59:56 +02:00
return ;
}
2023-11-11 16:43:58 +02:00
2024-02-19 13:46:07 +02:00
if ( messageSize = = 0 )
{
2024-05-11 16:32:43 +02:00
//heartbeat package with no payload - wait for next packet
2024-05-11 17:12:49 +02:00
startReceiving ( ) ;
2024-02-19 13:46:07 +02:00
return ;
}
2024-02-03 22:59:56 +02:00
boost : : asio : : async_read ( * socket ,
readBuffer ,
boost : : asio : : transfer_exactly ( messageSize ) ,
[ self = shared_from_this ( ) , messageSize ] ( const auto & ecPayload , const auto & endpoint ) { self - > onPacketReceived ( ecPayload , messageSize ) ; } ) ;
2023-11-11 16:43:58 +02:00
}
void NetworkConnection : : onPacketReceived ( const boost : : system : : error_code & ec , uint32_t expectedPacketSize )
{
if ( ec )
{
2024-05-12 21:13:17 +02:00
onError ( ec . message ( ) ) ;
2023-11-12 13:27:22 +02:00
return ;
2023-11-11 16:43:58 +02:00
}
if ( readBuffer . size ( ) < expectedPacketSize )
{
2024-05-19 14:54:51 +02:00
// FIXME: figure out what causes this. This should not be possible without error set
std : : string errorMessage = " Failed to read packet! " + std : : to_string ( readBuffer . size ( ) ) + " bytes read, but " + std : : to_string ( expectedPacketSize ) + " bytes expected! " ;
onError ( errorMessage ) ;
2023-11-11 16:43:58 +02:00
}
2024-02-02 01:27:19 +02:00
std : : vector < std : : byte > message ( expectedPacketSize ) ;
readBuffer . sgetn ( reinterpret_cast < char * > ( message . data ( ) ) , expectedPacketSize ) ;
2023-11-12 13:27:22 +02:00
listener . onPacketReceived ( shared_from_this ( ) , message ) ;
2024-05-11 17:12:49 +02:00
startReceiving ( ) ;
2023-11-11 16:43:58 +02:00
}
2024-05-14 21:40:20 +02:00
void NetworkConnection : : setAsyncWritesEnabled ( bool on )
{
asyncWritesEnabled = on ;
}
2024-02-02 01:27:19 +02:00
void NetworkConnection : : sendPacket ( const std : : vector < std : : byte > & message )
2023-11-11 16:43:58 +02:00
{
2024-05-07 10:34:47 +02:00
std : : lock_guard < std : : mutex > lock ( writeMutex ) ;
2024-05-11 10:46:00 +02:00
std : : vector < std : : byte > headerVector ( sizeof ( uint32_t ) ) ;
uint32_t messageSize = message . size ( ) ;
std : : memcpy ( headerVector . data ( ) , & messageSize , sizeof ( uint32_t ) ) ;
2024-05-07 10:34:47 +02:00
2024-05-14 21:40:20 +02:00
// At the moment, vcmilobby *requires* async writes in order to handle multiple connections with different speeds and at optimal performance
// However server (and potentially - client) can not handle this mode and may shutdown either socket or entire asio service too early, before all writes are performed
if ( asyncWritesEnabled )
{
2023-11-11 16:43:58 +02:00
2024-05-14 21:40:20 +02:00
bool messageQueueEmpty = dataToSend . empty ( ) ;
dataToSend . push_back ( headerVector ) ;
if ( message . size ( ) > 0 )
dataToSend . push_back ( message ) ;
if ( messageQueueEmpty )
doSendData ( ) ;
//else - data sending loop is still active and still sending previous messages
}
else
{
boost : : system : : error_code ec ;
boost : : asio : : write ( * socket , boost : : asio : : buffer ( headerVector ) , ec ) ;
if ( message . size ( ) > 0 )
boost : : asio : : write ( * socket , boost : : asio : : buffer ( message ) , ec ) ;
}
2024-05-11 10:46:00 +02:00
}
2023-11-11 16:43:58 +02:00
2024-05-11 10:46:00 +02:00
void NetworkConnection : : doSendData ( )
{
if ( dataToSend . empty ( ) )
throw std : : runtime_error ( " Attempting to sent data but there is no data to send! " ) ;
boost : : asio : : async_write ( * socket , boost : : asio : : buffer ( dataToSend . front ( ) ) , [ self = shared_from_this ( ) ] ( const auto & error , const auto & )
{
self - > onDataSent ( error ) ;
} ) ;
}
void NetworkConnection : : onDataSent ( const boost : : system : : error_code & ec )
{
std : : lock_guard < std : : mutex > lock ( writeMutex ) ;
dataToSend . pop_front ( ) ;
if ( ec )
{
2024-05-12 21:13:17 +02:00
onError ( ec . message ( ) ) ;
2024-05-11 10:46:00 +02:00
return ;
}
2024-01-21 16:48:36 +02:00
2024-05-11 10:46:00 +02:00
if ( ! dataToSend . empty ( ) )
doSendData ( ) ;
2024-02-02 15:32:06 +02:00
}
2024-05-12 21:13:17 +02:00
void NetworkConnection : : onError ( const std : : string & message )
{
listener . onDisconnected ( shared_from_this ( ) , message ) ;
close ( ) ;
}
2024-02-02 15:32:06 +02:00
void NetworkConnection : : close ( )
{
boost : : system : : error_code ec ;
socket - > close ( ec ) ;
2024-05-11 17:12:49 +02:00
timer - > cancel ( ec ) ;
2024-02-02 15:32:06 +02:00
2024-02-04 19:56:04 +02:00
//NOTE: ignoring error code, intended
2023-11-11 16:43:58 +02:00
}
VCMI_LIB_NAMESPACE_END