comparison oscgroups/GroupServer.cpp @ 76:0ae87af84e2f

added oscgroups
author Rob Canning <rob@foo.net>
date Sun, 13 Jul 2014 10:07:41 +0100
parents
children
comparison
equal deleted inserted replaced
75:3a2845e3156e 76:0ae87af84e2f
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 "GroupServer.h"
21
22 #include <string>
23 #include <set>
24 #include <iostream>
25 #include <ctime>
26 #include <algorithm>
27 #include <iterator>
28 #include <cassert>
29 #include <cstring>
30
31 #include "ip/NetworkingUtils.h"
32
33 #if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug
34 namespace std {
35 using ::__strcpy__; // avoid error: E2316 '__strcpy__' is not a member of 'std'.
36 }
37 #endif
38
39 /*
40 TODO:
41 switch to using char * instead of string:: to avoid allocating new strings
42 every time we execute a find()
43 */
44
45
46 static std::ostream& Log()
47 {
48 std::time_t t;
49 std::time( &t );
50
51 // ctime() returns a constant width 26 char string including trailing \0
52 // the fields are all constant width.
53 char s[26];
54 std::strcpy( s, std::ctime( &t ) );
55 s[24] = '\0'; // remove trailing null
56
57 std::cout << s << ": ";
58 return std::cout;
59 }
60
61
62 GroupServer::GroupServer( int timeoutSeconds, int maxUsers, int maxGroups )
63 : timeoutSeconds_( timeoutSeconds )
64 , maxUsers_( maxUsers )
65 , maxGroups_( maxGroups )
66 , userCount_( 0 )
67 , groupCount_( 0 )
68 {
69
70 }
71
72
73 GroupServer::~GroupServer()
74 {
75 for( const_user_iterator i = users_begin(); i != users_end(); ++i )
76 delete i->second;
77 for( const_group_iterator i = groups_begin(); i != groups_end(); ++i )
78 delete i->second;
79 }
80
81
82 User *GroupServer::CreateUser( const char *userName, const char *userPassword )
83 {
84 Log() << "creating user '" << userName << "'." << std::endl;
85
86 User *user = new User( userName, userPassword );
87 std::pair<user_iterator,bool> r =
88 users_.insert( std::make_pair( user->name, user ) );
89 // we assume the caller didn't try to create a user with an existing name
90 assert( r.second == true );
91 ++userCount_;
92
93 return user;
94 }
95
96
97 User *GroupServer::FindUser( const char *userName )
98 {
99 user_iterator user = users_.find( userName );
100
101 return ( user != users_.end() ) ? user->second : (User*) 0;
102 }
103
104
105 void GroupServer::PurgeStaleUsers()
106 {
107 std::time_t currentTime = std::time(0);
108
109 for( std::map< std::string, User* >::iterator i = users_.begin();
110 i != users_.end(); /* nothing */ ){
111
112 unsigned long inactiveSeconds =
113 i->second->SecondsSinceLastAliveReceived( currentTime );
114 if( inactiveSeconds >= (unsigned int)timeoutSeconds_ ){
115 Log() << "purging user '" << i->second->name << "' after "
116 << inactiveSeconds << " seconds of inactivity." << std::endl;
117
118 SeparateUserFromAllGroups( i->second );
119 delete i->second;
120 --userCount_;
121
122 // i think this is safe, we increment i before erasing the
123 // element referenced by its old value...
124 std::map< std::string, User* >::iterator j = i;
125 ++i;
126 users_.erase( j );
127 }else{
128 ++i;
129 }
130 }
131 }
132
133
134 Group *GroupServer::CreateGroup(
135 const char *groupName, const char *groupPassword )
136 {
137 Log() << "creating group '" << groupName << "'." << std::endl;
138
139 Group *group = new Group( groupName, groupPassword );
140 std::pair<group_iterator, bool> r =
141 groups_.insert( std::make_pair( group->name, group ) );
142 // we assume the caller didn't try to create an existing group
143 assert( r.second == true );
144 ++groupCount_;
145
146 return group;
147 }
148
149
150 Group *GroupServer::FindGroup( const char *groupName )
151 {
152 group_iterator group = groups_.find( groupName );
153
154 return ( group != groups_.end() ) ? group->second : (Group*) 0;
155 }
156
157
158 // The User <-> Group relation is managed as a bidirectional link (each User
159 // contains a set of groups which it is associated with, and each Group
160 // maintains a set of Users which it is associated with). The Associate*
161 // and Separate* methods below create and destroy the bidirectional link
162 // RemoveUserReferenceFromGroup is a helper function which only destroys
163 // one side of the link.
164
165 void GroupServer::AssociateUserWithGroup( User *user, Group* group )
166 {
167 Log() << "adding user '" << user->name
168 << "' to group '" << group->name << "'." << std::endl;
169
170 user->groups_.insert( group );
171 group->users_.insert( user );
172 }
173
174
175 void GroupServer::RemoveUserReferenceFromGroup( User *user, Group* group )
176 {
177 Log() << "removing user '" << user->name
178 << "' from group '" << group->name << "'." << std::endl;
179
180 std::set< User* >::iterator i = group->users_.find( user );
181 assert( i != group->users_.end() );
182
183 group->users_.erase( i );
184
185 if( group->users_.empty() ){
186 Log() << "purging empty group '" << group->name << "'." << std::endl;
187
188 groups_.erase( group->name );
189 delete group;
190 --groupCount_;
191 }
192 }
193
194
195 void GroupServer::SeparateUserFromGroup( User *user, Group* group )
196 {
197 user->groups_.erase( group );
198 RemoveUserReferenceFromGroup( user, group );
199 }
200
201
202 void GroupServer::SeparateUserFromAllGroups( User *user )
203 {
204 for( std::set<Group*>::iterator i = user->groups_.begin();
205 i != user->groups_.end(); ++i ){
206
207 RemoveUserReferenceFromGroup( user, *i);
208 }
209
210 user->groups_.clear();
211 }
212
213
214 static void UpdateEndpoint( IpEndpointName& dest, const IpEndpointName& src,
215 const char *userName, const char *whichEndpoint )
216 {
217 if( dest != src ){
218
219 char endpointString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ];
220 src.AddressAndPortAsString( endpointString );
221
222 Log() << "updating " << whichEndpoint << " endpoint for user '"
223 << userName << "' to " << endpointString << "." << std::endl;
224
225 dest = src;
226 }
227 }
228
229
230 GroupServer::UserStatus GroupServer::UserAlive(
231 const char *userName, const char *userPassword,
232 const IpEndpointName& privateEndpoint,
233 const IpEndpointName& publicEndpoint,
234 const char **groupNamesAndPasswords,
235 UserStatus *userGroupsStatus, int groupCount )
236 {
237 // find or create the user, aborting if the password for an existing
238 // user is incorrect, or if the maximum number of users has been reached
239
240 User *user = FindUser( userName );
241 if( user ){
242 if( user->password.compare( userPassword ) != 0 ){
243 Log() << "attempt to update user '"
244 << userName << "' with incorrect password." << std::endl;
245 return USER_STATUS_WRONG_PASSWORD;
246 }
247 }else{
248 if( userCount_ == maxUsers_ ){
249 Log() << "user limit reached, user '"
250 << userName << "' not admitted." << std::endl;
251 return USER_STATUS_SERVER_LIMIT_REACHED;
252 }else{
253 user = CreateUser( userName, userPassword );
254 }
255 }
256
257 UpdateEndpoint(
258 user->privateEndpoint, privateEndpoint, userName, "private" );
259 UpdateEndpoint( user->publicEndpoint, publicEndpoint, userName, "public" );
260
261 user->lastAliveMessageArrivalTime = time(0);
262
263
264 // previousButNotCurrentGroups begins containing all the groups the user
265 // was in before the current message was received. We remove those which
266 // the user is still a member of, leaving the set that the user is no
267 // longer a member of. We then use the set to remove associations with
268 // non-current groups.
269
270 std::set<Group*> previousButNotCurrentGroups( user->groups_ );
271
272 for( int i=0; i < groupCount; ++i ){
273 const char *groupName = groupNamesAndPasswords[i*2];
274 const char *groupPassword = groupNamesAndPasswords[i*2 + 1];
275
276 Group *group = FindGroup( groupName );
277 if( group ){
278 if( user->IsMemberOf( group ) ){
279 // check that previousButNotCurrentGroups contains group before
280 // removing it. it won't if we were passed the same group
281 // multiple times
282 std::set<Group*>::iterator j =
283 previousButNotCurrentGroups.find( group );
284 if( j != previousButNotCurrentGroups.end() )
285 previousButNotCurrentGroups.erase( j );
286
287 userGroupsStatus[i] = USER_STATUS_OK;
288 }else{
289 // user isn't in group so join it
290 if( group->password.compare( groupPassword ) == 0 ){
291 AssociateUserWithGroup( user, group );
292 userGroupsStatus[i] = USER_STATUS_OK;
293 }else{
294 userGroupsStatus[i] = USER_STATUS_WRONG_PASSWORD;
295 }
296 }
297 }else{ // group doesn't exist
298 if( groupCount_ == maxGroups_ ){
299 Log() << "group limit reached, group '"
300 << groupName << "' not created." << std::endl;
301 userGroupsStatus[i] = USER_STATUS_SERVER_LIMIT_REACHED;
302 }else{
303 group = CreateGroup( groupName, groupPassword );
304 AssociateUserWithGroup( user, group );
305 userGroupsStatus[i] = USER_STATUS_OK;
306 }
307 }
308 }
309
310 for( std::set<Group*>::iterator j = previousButNotCurrentGroups.begin();
311 j != previousButNotCurrentGroups.end(); ++j ){
312
313 SeparateUserFromGroup( user, *j );
314 }
315
316 return USER_STATUS_OK;
317 }
318