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