root/oscgroups/trunk/OscGroupServer.cpp

Revision 31, 11.6 kB (checked in by ross, 4 years ago)

updated to use new osc/... ip/... include directory conventions from oscpack

  • Property svn:eol-style set to native
Line 
1 /*
2 OSCgroups -- open sound control groupcasting infrastructure
3 Copyright (C) 2005  Ross Bencina
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 */
19
20 #include "OscGroupServer.h"
21
22 #include <string.h>
23 #include <iostream>
24 #include <fstream>
25 #include <cassert>
26 #include <time.h>
27
28 #include "osc/OscReceivedElements.h"
29 #include "osc/OscOutboundPacketStream.h"
30
31 #include "ip/UdpSocket.h"
32 #include "osc/OscPacketListener.h"
33 #include "ip/TimerListener.h"
34
35 #include "GroupServer.h"
36
37
38 static std::ostream& Log()
39 {
40     std::time_t t;
41     time( &t );
42
43     // ctime() returns a constant width 26 char string including trailing \0
44     // the fields are all constant width.
45     char s[26];
46     strcpy( s, std::ctime( &t ) );
47     s[24] = '\0'; // remove trailing null
48
49     std::cout << s << ": ";
50     return std::cout;
51 }
52
53
54 static const char *UserStatusToString( GroupServer::UserStatus userStatus )
55 {
56     switch( userStatus ){
57         case GroupServer::USER_STATUS_OK:
58             return "ok";
59         case GroupServer::USER_STATUS_WRONG_PASSWORD:
60             return "wrong password";
61         case GroupServer::USER_STATUS_SERVER_LIMIT_REACHED:
62             return "user limit reached";
63         case GroupServer::USER_STATUS_UNKNOWN:
64             /* FALLTHROUGH */ ;
65     }
66
67     return "unknown";
68 }
69
70
71 class OscGroupServerListener
72     : public osc::OscPacketListener
73     , public TimerListener
74 {
75
76     GroupServer groupServer_;
77     UdpSocket& externalSocket_;
78
79     #define IP_MTU_SIZE 1536
80     char buffer_[IP_MTU_SIZE];
81     osc::OutboundPacketStream resultStream_;
82     unsigned int emptyResultSize_;
83
84     void user_alive( const osc::ReceivedMessage& m,
85                                 const IpEndpointName& remoteEndpoint )
86     {
87         // /groupserver/user_alive
88         //      userName password
89         //      privateIpAddress privatePort
90         //      groupName0 groupPassword0 groupName1 groupPassword1 ...
91
92         osc::ReceivedMessageArgumentStream args = m.ArgumentStream();
93         const char *userName, *userPassword;
94         long privateAddress, privatePort;
95
96         args >> userName >> userPassword >> privateAddress >> privatePort;
97
98         // addresses are transmitted as ones complement (bit inverse)
99         // to avoid problems with buggy NATs trying to re-write addresses
100         privateAddress = ~privateAddress;
101
102                 IpEndpointName privateEndpoint( privateAddress, privatePort );
103
104         int groupCount = (m.ArgumentCount() - 4) / 2;
105         const char **groupNamesAndPasswords = 0;
106         GroupServer::UserStatus *userGroupsStatus = 0;
107         if( groupCount > 0 ){
108             groupNamesAndPasswords = new const char*[ groupCount * 2 ];
109             int i = 0;
110             while( !args.Eos() ){
111                 args >> groupNamesAndPasswords[i++];    // group name
112                 args >> groupNamesAndPasswords[i++];    // group password
113             }
114
115             userGroupsStatus = new GroupServer::UserStatus[ groupCount ];
116             for( int j=0; j < groupCount; ++j )
117                 userGroupsStatus[j] = GroupServer::USER_STATUS_UNKNOWN;
118         }
119
120         GroupServer::UserStatus userStatus =
121                 groupServer_.UserAlive( userName, userPassword,
122                         privateEndpoint, remoteEndpoint,
123                         groupNamesAndPasswords, userGroupsStatus, groupCount );
124
125         resultStream_ << osc::BeginMessage( "/groupclient/user_alive_status" )
126             << userName
127             << userPassword
128             << UserStatusToString( userStatus )               
129             << osc::EndMessage;
130
131         if( userStatus == GroupServer::USER_STATUS_OK ){
132             for( int i=0; i < groupCount; ++i ){
133                 const char *groupName = groupNamesAndPasswords[i*2];
134                 const char *groupPassword = groupNamesAndPasswords[i*2 + 1];
135
136                 resultStream_
137                     << osc::BeginMessage( "/groupclient/user_group_status" )
138                     << userName
139                     << userPassword
140                     << groupName
141                     << groupPassword
142                     << UserStatusToString( userGroupsStatus[i] )
143                     << osc::EndMessage;
144             }
145         }
146
147         delete [] groupNamesAndPasswords;
148         delete [] userGroupsStatus;
149     }
150
151     void MakeUserInfoMessage( osc::OutboundPacketStream& p,
152             User *user, unsigned long currentTime )
153     {
154         // addresses are transmitted as ones complement (bit inverse)
155         // to avoid problems with buggy NATs trying to re-write addresses
156         
157         p << osc::BeginMessage( "/groupclient/user_info" )
158             << user->name.c_str()
159             << ~((long)user->privateEndpoint.address)
160             << user->privateEndpoint.port
161             << ~((long)user->publicEndpoint.address)
162             << user->publicEndpoint.port
163             << (long)user->SecondsSinceLastAliveReceived( currentTime );
164
165         for( User::const_group_iterator i = user->groups_begin();
166                 i != user->groups_end(); ++i )
167             p << (*i)->name.c_str();
168                
169         p << osc::EndMessage;
170     }
171
172     void get_group_users_info( const osc::ReceivedMessage& m,
173             const IpEndpointName& remoteEndpoint )
174     {
175         // /groupserver/get_group_users_info group-name group-password
176
177         osc::ReceivedMessageArgumentStream args = m.ArgumentStream();
178         const char *groupName, *groupPassword;
179
180         args >> groupName >> groupPassword >> osc::EndMessage;
181
182         Group *group = groupServer_.FindGroup( groupName );
183         if( group && group->password.compare( groupPassword ) == 0 ){
184             unsigned long currentTime = time(0);
185            
186             for( Group::const_user_iterator i = group->users_begin();
187                     i != group->users_end(); ++i )
188                 MakeUserInfoMessage( resultStream_, *i, currentTime );
189         }
190
191         // we don't return a result to the client in the case where the group
192         // doesn't exist, or the password is wrong because legitimate clients
193         // will have already successfully joined the group, or they will have
194         // received an error message when they tried to join.
195     }
196
197 protected:
198    
199     virtual void ProcessMessage( const osc::ReceivedMessage& m,
200                         const IpEndpointName& remoteEndpoint )
201     {
202         try{
203
204             if( strcmp( m.AddressPattern(), "/groupserver/user_alive" ) == 0 ){
205                 user_alive( m, remoteEndpoint );
206             }else if( strcmp( m.AddressPattern(), "/groupserver/get_group_users_info" ) == 0 ){
207                 get_group_users_info( m, remoteEndpoint );
208             }
209
210         }catch( osc::Exception& e ){
211             Log() << "error while parsing message: " << e.what() << std::endl;
212         }
213     }
214
215 public:
216     OscGroupServerListener( int timeoutSeconds, int maxUsers, int maxGroups,
217                 UdpSocket& externalSocket )
218         : groupServer_( timeoutSeconds, maxUsers, maxGroups )
219         , externalSocket_( externalSocket )
220         , resultStream_( buffer_, IP_MTU_SIZE )
221     {
222         resultStream_ << osc::BeginBundle();
223         resultStream_ << osc::EndBundle;
224         emptyResultSize_ = resultStream_.Size();
225     }
226
227     virtual void ProcessPacket( const char *data, int size,
228                         const IpEndpointName& remoteEndpoint )
229     {
230         resultStream_.Clear();
231         resultStream_ << osc::BeginBundle();
232
233         osc::OscPacketListener::ProcessPacket( data, size, remoteEndpoint );
234
235         resultStream_ << osc::EndBundle;
236
237         assert( resultStream_.IsReady() );
238
239         if( resultStream_.Size() != emptyResultSize_ ){
240             char addressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ];
241             remoteEndpoint.AddressAndPortAsString( addressString );
242                     Log() << "responding to request from " << addressString << "." << std::endl;
243            
244                     externalSocket_.SendTo(
245                     remoteEndpoint, resultStream_.Data(), resultStream_.Size() );
246         }
247     }
248
249     virtual void TimerExpired()
250     {
251         groupServer_.PurgeStaleUsers();
252     }
253 };
254
255
256 void RunOscGroupServerUntilSigInt(
257         int port, int timeoutSeconds, int maxUsers, int maxGroups, const char *logFile )
258 {
259     std::ofstream *logStream = 0;
260     std::streambuf *oldCoutRdbuf = std::cout.rdbuf();
261
262     try{
263         if( logFile ){
264             std::cout << "oscgroupserver redirecting output to " << logFile << std::endl;
265             logStream = new std::ofstream( logFile, std::ios_base::app );
266             std::cout.rdbuf( logStream->rdbuf() );
267         }
268    
269         UdpReceiveSocket externalSocket(
270                 IpEndpointName( IpEndpointName::ANY_ADDRESS, port ) );
271         OscGroupServerListener externalSocketListener(
272                 timeoutSeconds, maxUsers, maxGroups, externalSocket );
273
274         SocketReceiveMultiplexer mux;
275         mux.AttachSocketListener( &externalSocket, &externalSocketListener );
276
277         // timer is used for purging inactive users
278         mux.AttachPeriodicTimerListener( (timeoutSeconds * 1000) / 10, &externalSocketListener );
279    
280         Log() << "oscgroupserver listening on port " << port << "...\n"
281                 << "timeoutSeconds=" << timeoutSeconds << ", "
282                 << "maxUsers=" << maxUsers << ", "
283                 << "maxGroups=" << maxGroups << ", "
284                 << "logFile=" << ((logFile) ? logFile : "stdout") << "\n"
285                 << "press ctrl-c to end" << std::endl;
286
287         mux.RunUntilSigInt();
288        
289         Log() << "finishing." << std::endl;
290
291     }catch( std::exception& e ){
292         Log() << e.what() << std::endl;
293         Log() << "finishing." << std::endl;
294     }
295
296     std::cout.rdbuf( oldCoutRdbuf );
297     delete logStream;
298 }
299
300
301 static void usage()
302 {
303     std::cerr << "usage: oscgroupserver [-p port] [-t timeoutSeconds] [-u maxUsers] [-g maxGroups] [-l logfile]\n";
304 }
305
306
307 int oscgroupserver_main(int argc, char* argv[])
308 {
309     if( argc % 2 != 1 ){
310         usage();
311         return 0;
312     }
313
314     int port = 22242;
315     int timeoutSeconds = 60;
316     int maxUsers = 100;
317     int maxGroups = 50;
318     const char *logFile = 0;
319
320     int argIndex = 1;
321     while( argIndex < argc ){
322         const char *selector = argv[argIndex];
323         const char *value = argv[argIndex + 1];
324         if( selector[0] == '-' && selector[1] != '\0' && selector[2] == '\0' ){
325             switch( selector[1] ){
326                 case 'p':
327                     port = atoi( value );
328                     break;
329                 case 't':
330                     timeoutSeconds = atoi( value );
331                     break;
332                 case 'u':
333                     maxUsers = atoi( value );
334                     break;
335                 case 'g':
336                     maxGroups = atoi( value );
337                     break;
338                 case 'l':
339                     logFile = value;
340                     break;
341                 default:
342                     usage();
343                     return 0;
344             }
345         }else{
346             usage();
347             return 0;
348         }
349         argIndex += 2;
350     }
351        
352     RunOscGroupServerUntilSigInt(
353             port, timeoutSeconds, maxUsers, maxGroups, logFile );
354
355     return 0;
356 }
357
358
359 #ifndef NO_MAIN
360
361 int main(int argc, char* argv[])
362 {
363     return oscgroupserver_main( argc, argv );
364 }
365
366 #endif /* NO_MAIN */
367
Note: See TracBrowser for help on using the browser.