nodescore/oscgroups/OscGroupServer.cpp

378 lines
12 KiB
C++
Executable File

/*
OSCgroups -- open sound control groupcasting infrastructure
Copyright (C) 2005 Ross Bencina
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
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "OscGroupServer.h"
#include <cassert>
#include <cstring>
#include <ctime>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include "osc/OscReceivedElements.h"
#include "osc/OscOutboundPacketStream.h"
#include "ip/UdpSocket.h"
#include "osc/OscPacketListener.h"
#include "ip/TimerListener.h"
#include "GroupServer.h"
#if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug
namespace std {
using ::__strcpy__; // avoid error: E2316 '__strcpy__' is not a member of 'std'.
using ::__strcmp__; // avoid error: E2316 '__strcmp__' is not a member of 'std'.
}
#endif
static std::ostream& Log()
{
std::time_t t;
std::time( &t );
// ctime() returns a constant width 26 char string including trailing \0
// the fields are all constant width.
char s[26];
std::strcpy( s, std::ctime( &t ) );
s[24] = '\0'; // remove trailing null
std::cout << s << ": ";
return std::cout;
}
static const char *UserStatusToString( GroupServer::UserStatus userStatus )
{
switch( userStatus ){
case GroupServer::USER_STATUS_OK:
return "ok";
case GroupServer::USER_STATUS_WRONG_PASSWORD:
return "wrong password";
case GroupServer::USER_STATUS_SERVER_LIMIT_REACHED:
return "user limit reached";
case GroupServer::USER_STATUS_UNKNOWN:
/* FALLTHROUGH */ ;
}
return "unknown";
}
class OscGroupServerListener
: public osc::OscPacketListener
, public TimerListener
{
GroupServer groupServer_;
UdpSocket& externalSocket_;
#define IP_MTU_SIZE 1536
char buffer_[IP_MTU_SIZE];
osc::OutboundPacketStream resultStream_;
std::size_t emptyResultSize_;
void user_alive( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
// /groupserver/user_alive
// userName password
// privateIpAddress privatePort
// groupName0 groupPassword0 groupName1 groupPassword1 ...
osc::ReceivedMessageArgumentStream args = m.ArgumentStream();
const char *userName, *userPassword;
osc::int32 privateAddress, privatePort;
args >> userName >> userPassword >> privateAddress >> privatePort;
// addresses are transmitted as ones complement (bit inverse)
// to avoid problems with buggy NATs trying to re-write addresses
privateAddress = ~privateAddress;
IpEndpointName privateEndpoint( privateAddress, privatePort );
int groupCount = (m.ArgumentCount() - 4) / 2;
const char **groupNamesAndPasswords = 0;
GroupServer::UserStatus *userGroupsStatus = 0;
if( groupCount > 0 ){
groupNamesAndPasswords = new const char*[ groupCount * 2 ];
int i = 0;
while( !args.Eos() ){
args >> groupNamesAndPasswords[i++]; // group name
args >> groupNamesAndPasswords[i++]; // group password
}
userGroupsStatus = new GroupServer::UserStatus[ groupCount ];
for( int j=0; j < groupCount; ++j )
userGroupsStatus[j] = GroupServer::USER_STATUS_UNKNOWN;
}
GroupServer::UserStatus userStatus =
groupServer_.UserAlive( userName, userPassword,
privateEndpoint, remoteEndpoint,
groupNamesAndPasswords, userGroupsStatus, groupCount );
resultStream_ << osc::BeginMessage( "/groupclient/user_alive_status" )
<< userName
<< userPassword
<< UserStatusToString( userStatus )
<< osc::EndMessage;
if( userStatus == GroupServer::USER_STATUS_OK ){
for( int i=0; i < groupCount; ++i ){
const char *groupName = groupNamesAndPasswords[i*2];
const char *groupPassword = groupNamesAndPasswords[i*2 + 1];
resultStream_
<< osc::BeginMessage( "/groupclient/user_group_status" )
<< userName
<< userPassword
<< groupName
<< groupPassword
<< UserStatusToString( userGroupsStatus[i] )
<< osc::EndMessage;
}
}
delete [] groupNamesAndPasswords;
delete [] userGroupsStatus;
}
void MakeUserInfoMessage( osc::OutboundPacketStream& p,
User *user, std::time_t currentTime )
{
// addresses are transmitted as ones complement (bit inverse)
// to avoid problems with buggy NATs trying to re-write addresses
p << osc::BeginMessage( "/groupclient/user_info" )
<< user->name.c_str()
<< ~((osc::int32)user->privateEndpoint.address)
<< (osc::int32)user->privateEndpoint.port
<< ~((osc::int32)user->publicEndpoint.address)
<< (osc::int32)user->publicEndpoint.port
<< (osc::int32)user->SecondsSinceLastAliveReceived( currentTime );
for( User::const_group_iterator i = user->groups_begin();
i != user->groups_end(); ++i )
p << (*i)->name.c_str();
p << osc::EndMessage;
}
void get_group_users_info( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
(void) remoteEndpoint; // suppress unused parameter warning
// /groupserver/get_group_users_info group-name group-password
osc::ReceivedMessageArgumentStream args = m.ArgumentStream();
const char *groupName, *groupPassword;
args >> groupName >> groupPassword >> osc::EndMessage;
Group *group = groupServer_.FindGroup( groupName );
if( group && group->password.compare( groupPassword ) == 0 ){
std::time_t currentTime = time(0);
for( Group::const_user_iterator i = group->users_begin();
i != group->users_end(); ++i )
MakeUserInfoMessage( resultStream_, *i, currentTime );
}
// we don't return a result to the client in the case where the group
// doesn't exist, or the password is wrong because legitimate clients
// will have already successfully joined the group, or they will have
// received an error message when they tried to join.
}
protected:
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
try{
if( std::strcmp( m.AddressPattern(), "/groupserver/user_alive" ) == 0 ){
user_alive( m, remoteEndpoint );
}else if( std::strcmp( m.AddressPattern(), "/groupserver/get_group_users_info" ) == 0 ){
get_group_users_info( m, remoteEndpoint );
}
}catch( osc::Exception& e ){
Log() << "error while parsing message: " << e.what() << std::endl;
}
}
public:
OscGroupServerListener( int timeoutSeconds, int maxUsers, int maxGroups,
UdpSocket& externalSocket )
: groupServer_( timeoutSeconds, maxUsers, maxGroups )
, externalSocket_( externalSocket )
, resultStream_( buffer_, IP_MTU_SIZE )
{
resultStream_ << osc::BeginBundle();
resultStream_ << osc::EndBundle;
emptyResultSize_ = resultStream_.Size();
}
virtual void ProcessPacket( const char *data, int size,
const IpEndpointName& remoteEndpoint )
{
resultStream_.Clear();
resultStream_ << osc::BeginBundle();
osc::OscPacketListener::ProcessPacket( data, size, remoteEndpoint );
resultStream_ << osc::EndBundle;
assert( resultStream_.IsReady() );
if( resultStream_.Size() != emptyResultSize_ ){
char addressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ];
remoteEndpoint.AddressAndPortAsString( addressString );
Log() << "responding to request from " << addressString << "." << std::endl;
externalSocket_.SendTo(
remoteEndpoint, resultStream_.Data(), resultStream_.Size() );
}
}
virtual void TimerExpired()
{
groupServer_.PurgeStaleUsers();
}
};
void RunOscGroupServerUntilSigInt(
int port, int timeoutSeconds, int maxUsers, int maxGroups, const char *logFile )
{
std::ofstream *logStream = 0;
std::streambuf *oldCoutRdbuf = std::cout.rdbuf();
try{
if( logFile ){
std::cout << "oscgroupserver redirecting output to " << logFile << std::endl;
logStream = new std::ofstream( logFile, std::ios_base::app );
std::cout.rdbuf( logStream->rdbuf() );
}
UdpReceiveSocket externalSocket(
IpEndpointName( IpEndpointName::ANY_ADDRESS, port ) );
OscGroupServerListener externalSocketListener(
timeoutSeconds, maxUsers, maxGroups, externalSocket );
SocketReceiveMultiplexer mux;
mux.AttachSocketListener( &externalSocket, &externalSocketListener );
// timer is used for purging inactive users
mux.AttachPeriodicTimerListener( (timeoutSeconds * 1000) / 10, &externalSocketListener );
Log() << "oscgroupserver listening on port " << port << "...\n"
<< "timeoutSeconds=" << timeoutSeconds << ", "
<< "maxUsers=" << maxUsers << ", "
<< "maxGroups=" << maxGroups << ", "
<< "logFile=" << ((logFile) ? logFile : "stdout") << "\n"
<< "press ctrl-c to end" << std::endl;
mux.RunUntilSigInt();
Log() << "finishing." << std::endl;
}catch( std::exception& e ){
Log() << e.what() << std::endl;
Log() << "finishing." << std::endl;
}
std::cout.rdbuf( oldCoutRdbuf );
delete logStream;
}
static void usage()
{
std::cerr << "usage: oscgroupserver [-p port] [-t timeoutSeconds] [-u maxUsers] [-g maxGroups] [-l logfile]\n";
}
int oscgroupserver_main(int argc, char* argv[])
{
if( argc % 2 != 1 ){
usage();
return 0;
}
int port = 22242;
int timeoutSeconds = 60;
int maxUsers = 100;
int maxGroups = 50;
const char *logFile = 0;
int argIndex = 1;
while( argIndex < argc ){
const char *selector = argv[argIndex];
const char *value = argv[argIndex + 1];
if( selector[0] == '-' && selector[1] != '\0' && selector[2] == '\0' ){
switch( selector[1] ){
case 'p':
port = std::atoi( value );
break;
case 't':
timeoutSeconds = std::atoi( value );
break;
case 'u':
maxUsers = std::atoi( value );
break;
case 'g':
maxGroups = std::atoi( value );
break;
case 'l':
logFile = value;
break;
default:
usage();
return 0;
}
}else{
usage();
return 0;
}
argIndex += 2;
}
RunOscGroupServerUntilSigInt(
port, timeoutSeconds, maxUsers, maxGroups, logFile );
return 0;
}
#ifndef NO_MAIN
int main(int argc, char* argv[])
{
return oscgroupserver_main( argc, argv );
}
#endif /* NO_MAIN */