// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdnet.h"
#include "nel/net/message_recorder.h"
#include "nel/net/inet_address.h"
using namespace NLMISC;
using namespace std;
namespace NLNET {
/// TNetworkEvent -> string
string EventToString( TNetworkEvent e )
{
switch ( e )
{
case Sending: return "SEND";
case Receiving: return "RECV";
case Connecting: return "CONN";
case ConnFailing: return "CNFL";
case Accepting: return "ACCP";
case Disconnecting: return "DISC";
default: nlstop; return "-ERR-";
}
}
/// string -> TNetworkEvent
TNetworkEvent StringToEvent( string& s )
{
if ( s == "RECV" )
return Receiving;
else if ( s == "SEND" )
return Sending;
else if ( s == "DISC" )
return Disconnecting;
else if ( s == "ACCP" )
return Accepting;
else if ( s == "CONN" )
return Connecting;
else if ( s == "CNFL" )
return ConnFailing;
else
{
nlstop;
return Error;
}
}
/*
* Constructor
*/
CMessageRecorder::CMessageRecorder() : _RecordAll(true)
{
#ifndef MESSAGES_PLAIN_TEXT
nlerror( "The message recorder works only with plain text messages. Please #define MESSAGES_PLAIN_TEXT" );
#endif
}
/*
* Destructor
*/
CMessageRecorder::~CMessageRecorder()
{
if ( _Filename != "" )
{
nldebug( "MR:%s: End of recording", _Filename.c_str() );
}
stopRecord();
stopReplay();
}
/*
* Start recording
*/
bool CMessageRecorder::startRecord( const std::string& filename, bool recordall )
{
_Filename = filename;
_File.open( _Filename.c_str(), ios_base::out );
_File << endl;
_RecordAll = recordall;
if ( _File.fail() )
{
nlwarning( "MR: Record: Cannot open file %s", _Filename.c_str() );
return false;
}
else
{
nldebug( "MR: Start recording into %s", _Filename.c_str() );
return true;
}
}
/*
* Same as stringFromVector() but assumes the vector contains only printable characters
*/
/*string stringFromTextVector( const vector& v )
{
string s;
// Copy contents
s.resize( v.size() );
memcpy( &*s.begin(), &*v.begin(), v.size() );
return s;
}*/
/*
* Add a record
*/
void CMessageRecorder::recordNext( sint64 updatecounter, TNetworkEvent event, TSockId sockid, CMessage& message )
{
nlassert( _File.is_open() );
if ( (_RecordAll) || (event != Sending) )
{
// Serial to stream
TMessageRecord rec ( event, sockid, message, updatecounter /*CTime::getLocalTime()*/ );
CMemStream stream ( false, true );
rec.serial( stream );
char c = '\0'; // end of cstring
stream.serial( c ); // added to the stream for _File << (char*)stream.buffer()
// Dump to file
nldebug( "MR:%s: Recording [%s]", _Filename.c_str(), stream.buffer() );
int len = (int)(stream.length()-2); // not the null character (and its separator) at the end of the buffer
_File << "* ";
_File << len; // if we put the expression directly, it makes an access violation ! Weird.
_File << " ";
_File << (char*)stream.buffer() << endl;
}
}
/*
* Stop recording
*/
void CMessageRecorder::stopRecord()
{
_File.close();
_Filename = "";
}
/*
* Start playback
*/
bool CMessageRecorder::startReplay( const std::string& filename )
{
_Filename = filename;
_File.open( _Filename.c_str(), ios_base::in );
if ( _File.fail() )
{
nlerror( "MR: Replay: Cannot open file %s", _Filename.c_str() );
return false;
}
else
{
nldebug( "MR: Start replaying from %s", _Filename.c_str() );
return true;
}
}
/*
* Get next record (throw EStreamOverflow)
*/
bool CMessageRecorder::loadNext( TMessageRecord& record )
{
// WARNING!!! This features doesn't work anymore becaues bufferAsVector() is not available with new CMemStream
nlstop;
return false;
nlassert( _File.is_open() );
// Dump from file
CMemStream stream ( true, true );
uint32 len;
char c;
_File >> c; // skip "* ";
_File >> (int&)len;
_File.ignore(); // skip delimiter
if ( ! _File.fail() )
{
_File.get( (char*)stream.bufferToFill( len+1 ), len+1, '\0' );
//stream.bufferAsVector().resize( len ); // cut end of cstring
nldebug( "MR:%s: Reading [%s]", _Filename.c_str(), stream.buffer() );
// Serial from stream
record.serial( stream ); // may throw EStreamOverflow if _File.fail()
}
return ! _File.fail(); // retest
}
/*
* Get the next record (from the preloaded records, or from the file)
*/
bool CMessageRecorder::getNext( TMessageRecord& record, sint64 updatecounter )
{
if ( ! _PreloadedRecords.empty() )
{
if ( _PreloadedRecords.front().UpdateCounter == updatecounter )
{
// The requested record is in the preload
record = _PreloadedRecords.front();
_PreloadedRecords.pop_front();
return true;
}
else
{
// The requested record is not in the file
nlassert( updatecounter < _PreloadedRecords.front().UpdateCounter ); // not >
return false;
}
}
else
{
if ( loadNext( record ) )
{
if ( record.UpdateCounter == updatecounter )
{
// The requested record has been loaded
return true;
}
else
{
// The next loaded record is a new one
nlassert( updatecounter < record.UpdateCounter ); // not >
_PreloadedRecords.push_back( record ); // when we read one too far
return false;
}
}
else
{
return false;
}
}
}
/*
* Push the received blocks for this counter into the receive queue
*/
void CMessageRecorder::replayNextDataAvailable( sint64 updatecounter )
{
TMessageRecord rec( true ); // input message
while ( getNext( rec, updatecounter ) )
{
switch ( rec.Event )
{
case Receiving :
case Accepting :
case Disconnecting :
ReceivedMessages.push( rec );
break;
case Sending :
break;
case Connecting :
case ConnFailing :
_ConnectionAttempts.push_back( rec );
break;
default :
nlstop;
}
}
}
/*
* Returns true and the event type if the counter of the next data is updatecounter
*/
TNetworkEvent CMessageRecorder::checkNextOne( sint64 updatecounter )
{
TMessageRecord record;
if ( getNext( record, updatecounter ) )
{
nldebug( "MR: Check next one: %s at update %"NL_I64"u", EventToString(record.Event).c_str(), updatecounter );
return record.Event;
}
else
{
return Error;
}
}
/*
* Get the first stored connection attempt corresponding to addr
*/
TNetworkEvent CMessageRecorder::replayConnectionAttempt( const CInetAddress& addr )
{
TNetworkEvent event;
deque::iterator ipr;
if ( ! _ConnectionAttempts.empty() )
{
// Search in the already processed connection attempts
for ( ipr=_ConnectionAttempts.begin(); ipr!=_ConnectionAttempts.end(); ++ipr )
{
CInetAddress stored_addr;
(*ipr).Message.serial( stored_addr );
if ( stored_addr == addr )
{
// Found
event = (*ipr).Event;
nldebug( "MR: Connection attempt found at update %"NL_I64"u", (*ipr).UpdateCounter );
_ConnectionAttempts.erase( ipr );
return event;
}
}
}
// Seek in the preloaded records
for ( ipr=_PreloadedRecords.begin(); ipr!=_PreloadedRecords.end(); ++ipr )
{
event = (*ipr).Event;
if ( (event == Connecting) || (event == ConnFailing) )
{
CInetAddress stored_addr;
(*ipr).Message.serial( stored_addr );
if ( stored_addr == addr )
{
// Found
nldebug( "MR: Connection attempt found at update %"NL_I64"u", (*ipr).UpdateCounter );
_PreloadedRecords.erase( ipr );
return event;
}
}
}
if ( ipr==_PreloadedRecords.end() )
{
// If not found, load next records until found !
TMessageRecord rec( true );
while ( loadNext( rec ) )
{
if ( ( rec.Event == Connecting ) || ( rec.Event == ConnFailing ) )
{
CInetAddress stored_addr;
rec.Message.serial( stored_addr );
if ( stored_addr == addr )
{
// Found
nldebug( "MR: Connection attempt found at update %"NL_I64"u", rec.UpdateCounter );
return rec.Event;
}
else
{
_PreloadedRecords.push_back( rec );
}
}
else
{
_PreloadedRecords.push_back( rec );
}
}
// Not found
nldebug( "MR: Connection attempt not found" );
return Error;
}
nlstop;
return Error;
}
/*
* Stop playback
*/
void CMessageRecorder::stopReplay()
{
_File.close();
_Filename = "";
}
} // NLNET