378 lines
12 KiB
C++
Executable File
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 */
|
|
|