// 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/module_gateway.h"
#include "nel/net/module.h"
#include "nel/net/module_manager.h"
#include "nel/net/module_socket.h"
#include "nel/net/module_message.h"
#include "nel/net/unified_network.h"
#include "nel/net/service.h"
#include "nel/net/net_log.h"
using namespace std;
using namespace NLMISC;
namespace NLNET
{
typedef uint8 TL5TransportId;
struct TTransportDesc
{
TL5TransportId TransportId;
string SubNetName;
bool InResponse;
void serial(NLMISC::IStream &s)
{
s.serial(TransportId);
s.serial(SubNetName);
s.serial(InResponse);
}
};
/** the specialized route for l5 transport */
class CL5Route : public CGatewayRoute
{
public:
/// the service ID of the outbound service
TServiceId ServiceId;
/// The transport ID at the outbound
TL5TransportId ForeignTransportId;
CL5Route(IGatewayTransport *transport)
: CGatewayRoute(transport)
{
}
void sendMessage(const CMessage &message) const;
};
/** Utility class that generate 8bits unique transport id.
* The total L5 transport instance is limited to 256.
* This really should be enough or you have a problem in
* your design !
* The allocator keep released ID as long as possible
* and reallocated them only when all other ids
* have been used/allocated.
*/
class CTransportIdAllocator
{
NLMISC_SAFE_SINGLETON_DECL(CTransportIdAllocator);
private:
deque _FreeIds;
set _UsedIds;
CTransportIdAllocator()
{
// fill the list of free ids;
for (TL5TransportId i=0; i TRouteMap;
/// The table that keep track of all routes
TRouteMap _Routes;
typedef std::map TTransportDispatcher;
/// Global index of transport use to dispatch received message
static TTransportDispatcher _TransportDispatcher;
/// Constructor
CGatewayL5Transport(const IGatewayTransport::TCtorParam ¶m)
: IGatewayTransport(param),
_Open(false)
{
// allocate a transport unique ID
_TransportId = CTransportIdAllocator::getInstance().allocateId();
// store the transport in the dispatcher
_TransportDispatcher.insert(make_pair(_TransportId, this));
// L5 transport is always peer invisible
PeerInvisible = true;
}
~CGatewayL5Transport()
{
if (_Open)
{
// the transport is still open, close it before destruction
close();
}
// remove the transport from the dispatcher
nlassert(_TransportDispatcher.find(_TransportId) != _TransportDispatcher.end());
_TransportDispatcher.erase(_TransportId);
// release the unique id
CTransportIdAllocator::getInstance().releaseId(_TransportId);
}
const std::string &getClassName() const
{
static string className(LAYER5_CLASS_NAME);
return className;
}
virtual void update()
{
}
virtual uint32 getRouteCount() const
{
return (uint32)_Routes.size();
}
void dump(NLMISC::CLog &log) const
{
IModuleManager &mm = IModuleManager::getInstance();
log.displayNL(" NeL Net layer 5 transport");
if (!_Open)
{
log.displayNL(" The transport is currently closed.");
}
else
{
log.displayNL(" The transport is open and support %u routes :",
_Routes.size());
TRouteMap::const_iterator first(_Routes.begin()), last(_Routes.end());
for (; first != last; ++first)
{
TServiceId sid = first->first;
CL5Route *route = first->second;
log.displayNL(" + route to service %hu('%s'), %u entries in the proxy translation table :",
sid.get(),
CUnifiedNetwork::getInstance()->getServiceName(sid).c_str(),
route->ForeignToLocalIdx.getAToBMap().size());
{
CGatewayRoute::TForeignToLocalIdx::TAToBMap::const_iterator first(route->ForeignToLocalIdx.getAToBMap().begin()), last(route->ForeignToLocalIdx.getAToBMap().end());
for (; first != last; ++first)
{
IModuleProxy *modProx = mm.getModuleProxy(first->second);
log.displayNL(" - Proxy '%s' : local proxy id %u => foreign module id %u",
modProx != NULL ? modProx->getModuleName().c_str() : "ERROR, invalid module",
first->second,
first->first);
}
}
}
}
}
void onCommand(const CMessage &/* command */) throw (EInvalidCommand)
{
// nothing done for now
throw EInvalidCommand();
}
/// The gateway send a textual command to the transport
bool onCommand(const TParsedCommandLine &command) throw (EInvalidCommand)
{
if (command.SubParams.size() < 1)
throw EInvalidCommand();
const std::string &commandName = command.SubParams[0]->ParamName;
if (commandName == "open")
{
string subNetName;
/// look for an optional sub network name
const TParsedCommandLine *netName = command.SubParams[0]->getParam("SubNet");
if (netName != NULL)
{
subNetName = netName->ParamValue;
}
open(subNetName);
}
else if (commandName == "close")
{
close();
}
else
return false;
return true;
}
/// Open the server by establishing route with all known services
void open(const std::string &subNetName) throw (ETransportError)
{
H_AUTO(L5_open);
static TUnifiedCallbackItem L5TransportCallback[] =
{
{"GW_L5_MSG", CGatewayL5Transport::cbDispatchL5Message },
{"GW_L5_ADDTP", CGatewayL5Transport::cbL5AddTransport },
{"GW_L5_REMTP", CGatewayL5Transport::cbL5RemoveTransport },
};
if (_Open == true)
throw ETransportError("Transport already open");
_SubNetName = subNetName;
CUnifiedNetwork *un = CUnifiedNetwork::getInstance();
static bool callbackRegistered = false;
if (!callbackRegistered)
{
LNETL6_DEBUG("LNETL6: L5 transport open : registering callbacks");
// set the service con/disconnect callback
un->setServiceUpCallback("*", CGatewayL5Transport::cbOnServiceUp);
un->setServiceDownCallback("*", CGatewayL5Transport::cbOnServiceDown);
// set the message callback
un->addCallbackArray(L5TransportCallback, sizeof(L5TransportCallback) / sizeof(TUnifiedCallbackItem));
callbackRegistered = true;
}
// create route and open route for each existing service
const vector &connList = un->getConnectionList();
set uniqueService(connList.begin(), connList.end());
while (!uniqueService.empty())
{
TServiceId sid = *(uniqueService.begin());
uniqueService.erase(uniqueService.begin());
if ( un->isConnectionConnected(sid))
{
// send transport descriptor to other service
onServiceUp(un->getServiceName(sid), sid);
}
else
{
// the Connection is not established right now. We wait for the ServiceUp callback
}
}
_Open = true;
}
/// Close the server, this will close all route
void close()
{
H_AUTO(L5_close);
if (_Open == false)
throw ETransportError("closeServer : The server is not open");
// close all client connections
while (!_Routes.empty())
{
CL5Route *route = _Routes.begin()->second;
TServiceId sid = route->ServiceId;
onServiceDown(CUnifiedNetwork::getInstance()->getServiceName(sid), sid);
}
_Open = false;
}
/***************************************************/
/** Event management **/
/***************************************************/
void onServiceUp(const std::string &serviceName, TServiceId sid)
{
H_AUTO(L5_onServiceUp);
LNETL6_DEBUG("LNETL6: L5 transport onServiceUp('%s')", serviceName.c_str());
// send the transport descriptor to the new service
TTransportDesc desc;
desc.SubNetName = _SubNetName;
desc.TransportId = _TransportId;
desc.InResponse = false;
CMessage msg("GW_L5_ADDTP");
msg.serial(desc);
CUnifiedNetwork::getInstance()->send(sid, msg);
// the route will be created by receiving this message
}
void onServiceDown(const std::string &/* serviceName */, TServiceId sid)
{
H_AUTO(L5_onServicedown);
LNETL6_DEBUG("LNETL6: L5 transport onServiceDown('%hu')", sid.get());
// retrieve the route
TRouteMap::iterator it(_Routes.find(sid));
if (it == _Routes.end())
{
nlinfo("Transport L5 : service down, can't find a route for the service");
return;
}
CL5Route *route = it->second;
// warn the gateway
_Gateway->onRouteRemoved(route);
// release the route
_Routes.erase(it);
delete route;
}
// Called to dispatch an incoming message to the gateway
void onDispatchMessage(const CMessage &msgin, TServiceId sid)
{
H_AUTO(L5_onDispatchMessage);
LNETL6_DEBUG("LNETL6: L5 transport onDispatchMessage from service %hu", sid.get());
/// retrieve the route for dispatching
TRouteMap::iterator it(_Routes.find(sid));
if (it == _Routes.end())
{
nlwarning("Gateway '%s' : Can't find route for service %hu for dispatching, message is discarded",
_Gateway->getGatewayName().c_str(),
sid.get());
return;
}
// read the message size
uint32 msgLen;
nlRead(msgin, serial, msgLen);
// lock the sub message
msgin.lockSubMessage(msgLen);
_Gateway->onReceiveMessage(it->second, msgin);
// unlock the sub message
msgin.unlockSubMessage();
}
void onAddTransport(TServiceId sid, TTransportDesc &desc)
{
H_AUTO(L5_onAddTransport);
LNETL6_DEBUG("LNETL6: L5 transport onAddTransport from service %hu", sid.get());
// we need to create a route for this transport
// create a new route and send the route open message
if (_Routes.find(sid) != _Routes.end())
{
LNETL6_DEBUG("LNETL6: L5 transport onAddTransport a route for this service alredy exist");
return;
}
CL5Route *route = new CL5Route(this);
route->ServiceId = sid;
route->ForeignTransportId = desc.TransportId;
// store the route infos
_Routes.insert(make_pair(sid, route));
// notify the gateway about the new route
_Gateway->onRouteAdded(route);
if (desc.InResponse == false)
{
// we need to send back this transport info to this service
TTransportDesc desc;
desc.InResponse = true;
desc.SubNetName = _SubNetName;
desc.TransportId = _TransportId;
CMessage msg("GW_L5_ADDTP");
msg.serial(desc);
CUnifiedNetwork::getInstance()->send(sid, msg);
}
}
void onRemoveTransport(TServiceId sid, TTransportDesc &desc)
{
H_AUTO(L5_onRemoveTransport);
LNETL6_DEBUG("LNETL6: L5 transport onRemoveTransport from service %hu", sid.get());
// Remove the route
TRouteMap::iterator it(_Routes.find(sid));
if (it == _Routes.end())
{
nlwarning("onRemoveTransport : can't find a route to the transport %hu on service %u",
desc.TransportId,
sid.get());
return;
}
CL5Route *route = it->second;
// notify the gateway about the removed route
_Gateway->onRouteRemoved(route);
// erase the route info and delete the route
_Routes.erase(it);
delete route;
}
/***************************************************/
/** static callback forwarder **/
/***************************************************/
/// callback from layer 5
static void cbL5AddTransport(CMessage &msgin, const std::string &/* serviceName */, TServiceId sid)
{
LNETL6_DEBUG("LNETL6: L5 transport cbL5AddTransport from service %hu", sid.get());
// Receive a transport descriptor from another service, create
// a route for it
TTransportDesc desc;
msgin.serial(desc);
// for each existing transport here, check if they are in the
// same sub net, if so, callback them for route creation
TTransportDispatcher::iterator first(_TransportDispatcher.begin()), last(_TransportDispatcher.end());
for (; first != last; ++first)
{
CGatewayL5Transport *transport = first->second;
if (transport->_Open
&& transport->_SubNetName == desc.SubNetName
&& (sid != IService::getInstance()->getServiceId()
|| desc.TransportId != transport->_TransportId))
{
// this one is on the same subnet
transport->onAddTransport(sid, desc);
}
}
}
static void cbL5RemoveTransport(CMessage &msgin, const std::string &/* serviceName */, TServiceId sid)
{
LNETL6_DEBUG("LNETL6: L5 transport cbL5RemoveTransport from service %hu", sid.get());
// Receive a transport descriptor from another service, delete
// the route for it
TTransportDesc desc;
msgin.serial(desc);
// for each existing transport here, check if they are in the
// same sub net, if so, callback them for route creation
TTransportDispatcher::iterator first(_TransportDispatcher.begin()), last(_TransportDispatcher.end());
for (; first != last; ++first)
{
CGatewayL5Transport *transport = first->second;
if (transport->_Open
&& transport->_SubNetName == desc.SubNetName
&& desc.TransportId != transport->_TransportId)
{
// this one is on the same subnet
transport->onRemoveTransport(sid, desc);
}
}
}
static void cbDispatchL5Message (CMessage &msgin, const std::string &serviceName, TServiceId sid)
{
LNETL6_DEBUG("LNETL6: L5 transport cbDispatch called, receiving from %s", serviceName.c_str());
// dispatch the message to the route associated with the service
// the first info in the message is the transport id
TL5TransportId transportId;
msgin.serial(transportId);
// look for a corresponding transport
TTransportDispatcher::iterator it(_TransportDispatcher.find(transportId));
if (it == _TransportDispatcher.end())
{
nlwarning("ReceiveL5Message, can't find transport id %u for dispatching, message is discarded",
transportId);
return;
}
CGatewayL5Transport *transport = it->second;
transport->onDispatchMessage(msgin, sid);
}
static void cbOnServiceUp (const std::string &serviceName, TServiceId sid, void * /* arg */)
{
LNETL6_DEBUG("LNETL6: L5 transport cbOnServiceUp called, service up for %s", serviceName.c_str());
// callback all open transport about the new service
TTransportDispatcher::iterator first(_TransportDispatcher.begin()), last(_TransportDispatcher.end());
for (; first != last; ++first)
{
CGatewayL5Transport *transport = first->second;
if (transport->_Open)
transport->onServiceUp(serviceName, sid);
}
}
static void cbOnServiceDown (const std::string &serviceName, TServiceId sid, void * /* arg */)
{
LNETL6_DEBUG("LNETL6: L5 transport cbOnServicedown called, service down for %s", serviceName.c_str());
// callback all open transport about the removed service
TTransportDispatcher::iterator first(_TransportDispatcher.begin()), last(_TransportDispatcher.end());
for (; first != last; ++first)
{
CGatewayL5Transport *transport = first->second;
if (transport->_Open)
transport->onServiceDown(serviceName, sid);
}
}
};
CGatewayL5Transport::TTransportDispatcher CGatewayL5Transport::_TransportDispatcher;
// register this class in the transport factory
NLMISC_REGISTER_OBJECT(IGatewayTransport, CGatewayL5Transport, std::string, string(LAYER5_CLASS_NAME));
void CL5Route::sendMessage(const CMessage &message) const
{
NLNET_AUTO_DELTE_ASSERT;
H_AUTO(L5Route_sendMessage);
CGatewayL5Transport *trpt = static_cast(_Transport);
// create a transport message
CMessage wrapper("GW_L5_MSG");
// serial the transport identifier
wrapper.serial(trpt->_TransportId);;
// insert the message in the wrapper
nlWrite(wrapper, serialMessage, message);
// send the message
CUnifiedNetwork::getInstance()->send(ServiceId, wrapper);
}
void forceGatewayL5TransportLink()
{
}
} // namespace NLNET