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