root/oscgroups/trunk/OscGroupClient.cpp

Revision 31, 29.9 kB (checked in by ross, 3 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
21 #include "OscGroupClient.h"
22
23 #include <string.h>
24 #include <iostream>
25 #include <vector>
26 #include <time.h>
27 #include <string>
28 #include <assert.h>
29
30
31 #include "osc/OscReceivedElements.h"
32 #include "osc/OscOutboundPacketStream.h"
33 #include "osc/OscPacketListener.h"
34
35 #include "ip/UdpSocket.h"
36 #include "ip/IpEndpointName.h"
37 #include "ip/PacketListener.h"
38 #include "ip/TimerListener.h"
39
40 #include "md5.h"
41
42
43 /*
44         There are three sockets:
45                 externalSocket is used to send and receive data from the network
46         including the server and other peers
47         
48                 localRxSocket is used to forward data to the local (client)
49         application from other peers
50
51                 localTxSocket is used to recieve data from the local (client)
52         application to be forwarded to other peers
53
54
55     Some behavioral rules:
56
57         the choice of which peer endpoint to forward traffic to is made based
58         on which endpoints we have received pings from. if pings have been
59         received from multiple endpoints preference is given to the private
60         endpoint. as soon as a ping has been received we start forwarding data
61         
62         during the establishment phase we ping all endpoints repeatedly
63         even if we have already received pings from an endpoint.
64
65         during the establishment phase we initially send pings faster, and then
66         gradually return to a slower rate.
67
68         the establishment phase ends when:
69             - we receive a ping from the private endpoint
70
71             - we receive a ping from any endpoint, and
72                 ESTABLISHMENT_PING_PERIOD_COUNT ping periods have elapsed
73
74         if the server indicates that any of the port or address information of
75         the peer has changed, we restart the establishment process.
76
77         if a ping is received from an endpoint which we haven't received from
78         before we restart the establishment phase.
79
80         pings are sent less frequently if the channel is being kept open
81         by forwarded traffic. We ensure that some traffic is sent accross
82         the link every IDLE_PING_PERIOD_SECONDS and that a ping is sent
83         accross the link at least every ACTIVE_PING_PERIOD_SECONDS
84
85
86         if the last time at which the server has heard from a peer
87         AND the last time from which we received a ping from the peer
88         exceeds PURGE_PEER_TIMEOUT_SECONDS then the peer is removed from
89         the peers list.
90
91         
92         if we havn't received a ping from a peer in
93         PEER_ESTABLISHMENT_RETRY_TIME_SECONDS then we attempt to re-establish
94         the connection.
95
96
97
98     TODO:
99
100         o- report ping times (requires higher resolution timer i think)
101
102
103     -----------
104
105         
106     
107 */
108
109
110
111
112 #define PURGE_PEER_TIMEOUT_SECONDS          180              // time before removing peer from active list
113
114 #define PEER_ESTABLISHMENT_RETRY_TIME_SECONDS    60
115
116 #define IDLE_PING_PERIOD_SECONDS            6              // seconds between pings when the link is idle
117
118 #define ACTIVE_PING_PERIOD_SECONDS          30              // seconds between pings when the link is active
119
120 #define ESTABLISHMENT_PING_PERIOD_COUNT     5               // 5 pings are always sent to all peer endpoints
121
122
123
124 struct PeerEndpoint{
125     PeerEndpoint()
126         : pingReceived( false )
127         , sentPingsCount( 0 )
128         , forwardedPacketsCount( 0 ) {}
129
130     IpEndpointName endpointName;
131     bool pingReceived;
132     int lastPingReceiveTime;
133
134     int sentPingsCount;
135     int lastPingSendTime;
136
137     int forwardedPacketsCount;
138     int lastPacketForwardTime;
139 };
140
141
142 struct Peer{
143     Peer( const char *userName )
144         : name( userName )
145         , pingPeriodCount( 0 ) {}
146        
147     std::string name;
148
149     int lastUserInfoReceiveTime;
150     int secondsSinceLastAliveReceivedByServer;
151    
152     /*
153         we maintain three addresses for each peer. the private address is the
154         address the peer thinks it is, the public is the address that the
155         peer appears as to the server, and the ping address is the address
156         that the client appears as to us when we receive a ping from it. this
157         last address is necessary for half cone (symmetric) NATs which assign
158         the peer a different port to talk to us than the one they assigned to
159         talk to the server.
160     */
161
162         PeerEndpoint privateEndpoint;
163     PeerEndpoint publicEndpoint;
164     PeerEndpoint pingEndpoint;
165
166     int pingPeriodCount;
167     int lastPingPeriodTime;
168
169     int MostRecentActivityTime() const
170     {
171         // the most recent activity time is the most recent time either the
172         // server heard from the peer, or we received a ping from the peer
173     
174         int lastUserInfoReceivedByTheServerTime =
175                 lastUserInfoReceiveTime - secondsSinceLastAliveReceivedByServer;
176
177         int result = lastUserInfoReceivedByTheServerTime;
178         if( privateEndpoint.pingReceived )
179             result = std::max( result, privateEndpoint.lastPingReceiveTime );
180         if( publicEndpoint.pingReceived )
181             result = std::max( result, publicEndpoint.lastPingReceiveTime );
182         if( pingEndpoint.pingReceived )
183             result = std::max( result, pingEndpoint.lastPingReceiveTime );
184
185         return result;
186     }
187
188 };
189
190
191 static std::vector<Peer> peers_;
192
193
194 class ExternalCommunicationsSender : public TimerListener {
195     #define IP_MTU_SIZE 1536
196     char aliveBuffer_[IP_MTU_SIZE];
197     int aliveSize_;
198     int lastAliveSentTime_;
199    
200     char pingBuffer_[IP_MTU_SIZE];
201     int pingSize_;
202
203     UdpSocket& externalSocket_;
204     IpEndpointName remoteServerEndpoint_;
205     IpEndpointName localToServerEndpoint_;
206
207     std::string userName_;
208     std::string userPassword_;
209     std::string groupName_;
210     std::string groupPassword_;
211
212     void PrepareAliveBuffer()
213     {
214         osc::OutboundPacketStream p( aliveBuffer_, IP_MTU_SIZE );
215
216         p << osc::BeginBundle();
217
218         p << osc::BeginMessage( "/groupserver/user_alive" )
219                     << userName_.c_str()
220                     << userPassword_.c_str()
221                     << ~((long)localToServerEndpoint_.address)
222                     << localToServerEndpoint_.port
223                     << groupName_.c_str()
224                     << groupPassword_.c_str()
225                     << osc::EndMessage;
226
227         p << osc::BeginMessage( "/groupserver/get_group_users_info" )
228                     << groupName_.c_str()
229                     << groupPassword_.c_str()
230                     << osc::EndMessage;
231
232         p << osc::EndBundle;
233
234         aliveSize_ = p.Size();
235     }
236
237     void SendAlive( int currentTime )
238     {
239         int timeSinceLastAliveSent = currentTime - lastAliveSentTime_;
240         if( timeSinceLastAliveSent >= IDLE_PING_PERIOD_SECONDS ){
241            
242             externalSocket_.SendTo( remoteServerEndpoint_, aliveBuffer_, aliveSize_ );
243
244             lastAliveSentTime_ = currentTime;
245         }   
246     }
247
248     void PreparePingBuffer()
249     {
250         osc::OutboundPacketStream p( pingBuffer_, IP_MTU_SIZE );
251
252         p << osc::BeginBundle();
253
254         p << osc::BeginMessage( "/groupclient/ping" )
255                     << userName_.c_str()
256                     << osc::EndMessage;
257
258         p << osc::EndBundle;
259
260         pingSize_ = p.Size();
261     }
262
263     void SendPing( PeerEndpoint& to, int currentTime )
264         {
265                 char addressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ];
266                 to.endpointName.AddressAndPortAsString( addressString );
267          
268         std::cout << "sending ping to " << addressString << "\n";
269
270         externalSocket_.SendTo( to.endpointName, pingBuffer_, pingSize_ );
271         ++to.sentPingsCount;
272         to.lastPingSendTime = currentTime;
273         }
274
275    
276     PeerEndpoint* SelectEndpoint( Peer& peer )
277     {
278         if( peer.privateEndpoint.pingReceived ){
279
280             return &peer.privateEndpoint;
281
282         }else if( peer.publicEndpoint.pingReceived ){
283
284             return &peer.publicEndpoint;
285
286         }else if( peer.pingEndpoint.pingReceived ){
287
288             return &peer.pingEndpoint;
289         }
290
291         return 0;
292     }
293
294
295     void PollPeerPingTimer( Peer& peer, int currentTime, bool executeNowIgnoringTimeouts=false )
296     {
297         bool noPingsReceivedYet =
298                 !peer.privateEndpoint.pingReceived
299                 && !peer.publicEndpoint.pingReceived
300                 && !peer.pingEndpoint.pingReceived;
301
302         if( !noPingsReceivedYet ){
303             // check whether we should attempt to re-establish the link
304             // due to no traffic arriving for PEER_ESTABLISHMENT_RETRY_TIME_SECONDS
305
306             int mostRecentPingTime = 0;
307             if( peer.privateEndpoint.pingReceived )
308                 mostRecentPingTime = std::max( mostRecentPingTime, peer.privateEndpoint.lastPingReceiveTime );
309             if( peer.publicEndpoint.pingReceived )
310                 mostRecentPingTime = std::max( mostRecentPingTime, peer.publicEndpoint.lastPingReceiveTime );
311             if( peer.pingEndpoint.pingReceived )
312                 mostRecentPingTime = std::max( mostRecentPingTime, peer.pingEndpoint.lastPingReceiveTime );
313            
314             if( currentTime - mostRecentPingTime > PEER_ESTABLISHMENT_RETRY_TIME_SECONDS ){
315
316                 peer.pingPeriodCount = 0;
317                 executeNowIgnoringTimeouts = true;
318             }
319         }
320
321
322         bool inEstablishmentPhase =
323                 ( ( peer.pingPeriodCount < ESTABLISHMENT_PING_PERIOD_COUNT )
324                 &&  ( !peer.privateEndpoint.pingReceived ) )
325                 || noPingsReceivedYet;
326
327         if( inEstablishmentPhase ){
328             int pingPeriod;
329             if( peer.pingPeriodCount < ESTABLISHMENT_PING_PERIOD_COUNT ){
330            
331                 pingPeriod = (int) (IDLE_PING_PERIOD_SECONDS *
332                         ((double)(peer.pingPeriodCount + 1) / (double) ESTABLISHMENT_PING_PERIOD_COUNT));
333
334             }else{
335                 pingPeriod = IDLE_PING_PERIOD_SECONDS;
336             }
337
338             if( currentTime >= peer.lastPingPeriodTime + pingPeriod
339                     || executeNowIgnoringTimeouts ){
340                 SendPing( peer.privateEndpoint, currentTime );
341                 SendPing( peer.publicEndpoint, currentTime );
342                 if( peer.pingEndpoint.pingReceived )
343                     SendPing( peer.pingEndpoint, currentTime );
344
345                 peer.lastPingPeriodTime = currentTime;
346                 ++peer.pingPeriodCount;
347             }
348        
349         }else{
350
351             PeerEndpoint *peerEndpointToUse = SelectEndpoint( peer );
352             assert( peerEndpointToUse != 0 );
353
354             bool sendPing = false;
355
356             if( executeNowIgnoringTimeouts ){
357
358                 sendPing = true;
359
360             }else{
361            
362                 if( peerEndpointToUse->sentPingsCount == 0 ){
363
364                     sendPing = true;
365
366                 }else{
367
368                     int timeSinceLastPing = currentTime - peerEndpointToUse->lastPingSendTime;
369
370                     if( peerEndpointToUse->forwardedPacketsCount == 0 ){
371
372                         if( timeSinceLastPing >= IDLE_PING_PERIOD_SECONDS ){
373                        
374                             sendPing = true;
375                         }
376
377                     }else{
378
379                         int timeSinceLastForwardedTraffic =
380                                 currentTime - peerEndpointToUse->lastPacketForwardTime;
381
382                         if( timeSinceLastForwardedTraffic >= IDLE_PING_PERIOD_SECONDS ){
383
384                             if( timeSinceLastPing >= IDLE_PING_PERIOD_SECONDS ){
385                                 sendPing = true;
386                             }
387
388                         }else if( timeSinceLastPing >= ACTIVE_PING_PERIOD_SECONDS ){
389                             sendPing = true;
390                         }
391                     }
392                 }
393             }
394
395             if( sendPing ){
396                 SendPing( *peerEndpointToUse, currentTime );
397                 peer.lastPingPeriodTime = currentTime;
398                 ++peer.pingPeriodCount;
399             }
400         }
401     }
402    
403 public:
404     ExternalCommunicationsSender( UdpSocket& externalSocket,
405                         IpEndpointName remoteServerEndpoint,
406                         int localToRemotePort,
407             const char *userName, const char *userPassword,
408             const char *groupName, const char *groupPassword )
409         : lastAliveSentTime_( 0 )
410         , externalSocket_( externalSocket )
411                 , remoteServerEndpoint_( remoteServerEndpoint )
412                 , localToServerEndpoint_(
413                                 externalSocket.LocalEndpointFor( remoteServerEndpoint ).address,
414                                 localToRemotePort )
415         , userName_( userName )
416         , userPassword_( userPassword )
417         , groupName_( groupName )
418         , groupPassword_( groupPassword )
419     {
420         PrepareAliveBuffer();
421         PreparePingBuffer();
422     }
423
424
425     void RestartPeerCommunicationEstablishment( Peer& peer, int currentTime )
426     {
427         peer.pingPeriodCount = 0;
428         PollPeerPingTimer( peer, currentTime, true );
429     }
430
431
432     void ForwardPacketToAllPeers( const char *data, int size )
433     {
434         int currentTime = time(0);
435        
436         for( std::vector<Peer>::iterator i = peers_.begin(); i != peers_.end(); ++i ){
437
438             PeerEndpoint *peerEndpointToUse = SelectEndpoint( *i );
439             if( peerEndpointToUse ){
440                 externalSocket_.SendTo( peerEndpointToUse->endpointName, data, size );
441                 ++peerEndpointToUse->forwardedPacketsCount;
442                 peerEndpointToUse->lastPacketForwardTime = currentTime;
443             }
444         }       
445     }
446
447    
448     virtual void TimerExpired()
449     {       
450         int currentTime = time(0);
451
452         SendAlive( currentTime );
453
454         // check for peers to purge,
455         std::vector<Peer>::iterator i = peers_.begin();
456         while( i != peers_.end() ){
457
458             if( currentTime - i->MostRecentActivityTime() > PURGE_PEER_TIMEOUT_SECONDS ){
459
460                 i = peers_.erase( i );
461
462             }else{
463                 PollPeerPingTimer( *i, currentTime );
464                 ++i;
465             }       
466         }
467     }
468 };
469
470
471 class ExternalSocketListener : public osc::OscPacketListener {
472
473     void user_alive_status( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint )
474     {
475         // only accept user_alive_status from the server
476         if( remoteEndpoint != remoteServerEndpoint_ )
477             return;
478
479         // /groupclient/user_alive_status userName userPassword status
480
481         osc::ReceivedMessageArgumentStream args = m.ArgumentStream();
482
483         const char *userName, *userPassword, *status;
484
485         args >> userName >> userPassword >> status;
486
487         if( strcmp( userName, userName_ ) == 0
488                 &&  strcmp( userPassword, userPassword_ ) == 0 ){
489             // message really is for us
490
491             if( strcmp( status, "ok" ) == 0 ){
492
493                 std::cout << "ok: user '" << userName << "' is registered with server\n";
494
495             }else{
496                 std::cout << "user registration error: server returned status of '" << status
497                         << "' for user '" << userName << "'\n";
498             }
499         }
500     }
501
502     void user_group_status( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint )
503     {
504         // only accept user_alive_status from the server
505         if( remoteEndpoint != remoteServerEndpoint_ )
506             return;
507
508         // /groupclient/user_group_status userName userPassword groupName groupPassword status
509
510         osc::ReceivedMessageArgumentStream args = m.ArgumentStream();
511
512         const char *userName, *userPassword, *groupName, *groupPassword, *status;
513
514         args >> userName >> userPassword >> groupName >> groupPassword >> status;
515
516         if( strcmp( userName, userName_ ) == 0
517                 && strcmp( userPassword, userPassword_ ) == 0
518                 && strcmp( groupName, groupName_ ) == 0
519                 && strcmp( groupPassword, groupPassword_ ) == 0 ){
520             // message really is for us
521
522             if( strcmp( status, "ok" ) == 0 ){
523
524                 std::cout << "ok: user '" << userName << "' is a member of group '" << groupName << "'\n";
525
526             }else{
527                 std::cout << "group membership error: server returned status of '" << status
528                     << "' for user '" << userName
529                     << "' membership of group '" << groupName << "'\n";
530             }
531         }
532     }
533
534     void user_info( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint )
535     {
536         // only accept user_info from the server
537         if( remoteEndpoint != remoteServerEndpoint_ )
538             return;
539
540         // /groupclient/user_info userName privateIpAddress privatePort
541         //      publicIpAddress publicPort secondsSinceLastAlive group0 group1 ...
542
543         osc::ReceivedMessageArgumentStream args = m.ArgumentStream();
544
545         const char *userName;
546         long privateAddress;
547         long privatePort;
548         long publicAddress;
549         long publicPort;
550         long secondsSinceLastAlive;
551
552         args >> userName >> privateAddress >> privatePort >>
553                 publicAddress >> publicPort >> secondsSinceLastAlive;
554
555         // addresses are transmitted as ones complement (bit inverse)
556         // to avoid problems with buggy NATs trying to re-write addresses
557         privateAddress = ~privateAddress;
558         publicAddress = ~publicAddress;
559
560                 IpEndpointName privateEndpoint( privateAddress, privatePort );
561                 IpEndpointName publicEndpoint( publicAddress, publicPort );
562
563                 char privateAddressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ];
564                 privateEndpoint.AddressAndPortAsString( privateAddressString );
565         char publicAddressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ];
566                 publicEndpoint.AddressAndPortAsString( publicAddressString );
567        
568         std::cout << "user info received for '" << userName << "', "
569             << "private: " << privateAddressString
570             << " public: " << publicAddressString
571             << "\n";
572                
573         if( strcmp( userName, userName_ ) == 0 )
574             return; // discard info referring to ourselves
575
576
577         bool userIsInGroup = false;
578         while( !args.Eos() ){
579             const char *groupName;
580             args >> groupName;
581             if( strcmp( groupName, groupName_ ) == 0 ){
582                 userIsInGroup = true;
583                 break;
584             }
585         }
586        
587
588         if( userIsInGroup ){
589             bool restartPeerCommunicationEstablishment = false;
590            
591             bool found = false;
592             std::vector<Peer>::iterator peer;
593             for( std::vector<Peer>::iterator i = peers_.begin(); i != peers_.end(); ++i ){
594
595                 if( i->name.compare( userName ) == 0 ){
596                     peer = i;
597                     found = true;
598                     break;
599                 }
600             }
601
602             if( !found ){
603                 peers_.push_back( Peer( userName ) );
604                 peer = peers_.end() - 1;
605                 restartPeerCommunicationEstablishment = true;
606             }
607
608             if( peer->privateEndpoint.endpointName != privateEndpoint ){
609                 peer->privateEndpoint.endpointName = privateEndpoint;
610                 peer->privateEndpoint.pingReceived = false;
611                 peer->privateEndpoint.forwardedPacketsCount = 0;
612                 peer->pingEndpoint.pingReceived = false;
613                 peer->pingEndpoint.forwardedPacketsCount = 0;
614                 restartPeerCommunicationEstablishment = true;
615             }
616
617             if( peer->publicEndpoint.endpointName != publicEndpoint ){
618                 peer->publicEndpoint.endpointName = publicEndpoint;
619                 peer->publicEndpoint.pingReceived = false;
620                 peer->publicEndpoint.forwardedPacketsCount = 0;
621                 peer->pingEndpoint.pingReceived = false;
622                 peer->pingEndpoint.forwardedPacketsCount = 0;
623                 restartPeerCommunicationEstablishment = true;
624             }
625
626
627             peer->secondsSinceLastAliveReceivedByServer = secondsSinceLastAlive;
628
629             int currentTime = time(0);
630             peer->lastUserInfoReceiveTime = currentTime;
631
632             if( restartPeerCommunicationEstablishment )
633                 externalCommunicationsSender_.RestartPeerCommunicationEstablishment( *peer, currentTime );
634            
635         }else{
636             // fixme should remove user from peer list if it is present
637         }
638     }
639
640     void ping( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint )
641     {
642         osc::ReceivedMessageArgumentStream args = m.ArgumentStream();
643
644         const char *userName;
645         // osc::TimeTag timeSent;
646
647         // TODO:
648         // support 3 variants of the ping message:
649         // /ping userName (basic version, only one needed for compatibility)
650         // /ping userName timeSent
651         //        response -> /ping userName timeSent inResponseToUserName inResponseToTimeSent
652
653         args >> userName >> osc::EndMessage;
654
655                 char sourceAddressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ];
656                 remoteEndpoint.AddressAndPortAsString( sourceAddressString );
657        
658         std::cout << "ping recieved from '" << userName << "' at "
659                 << sourceAddressString  << "\n";
660
661         for( std::vector<Peer>::iterator i = peers_.begin(); i != peers_.end(); ++i ){
662
663             if( i->name.compare( userName ) == 0 ){
664                 bool restartPeerCommunicationEstablishment = false;
665
666                 int currentTime = time(0);
667
668                                 if( remoteEndpoint == i->privateEndpoint.endpointName ){
669
670                     restartPeerCommunicationEstablishment = !i->privateEndpoint.pingReceived;
671                     i->privateEndpoint.pingReceived = true;
672                     i->privateEndpoint.lastPingReceiveTime = currentTime;
673
674                 }else if( remoteEndpoint == i->publicEndpoint.endpointName ){
675
676                     restartPeerCommunicationEstablishment = !i->publicEndpoint.pingReceived;
677                     i->publicEndpoint.pingReceived = true;
678                     i->publicEndpoint.lastPingReceiveTime = currentTime;
679                    
680                 }else{
681                                         // otherwise assume the messages is coming from the ping endpoint
682
683                     restartPeerCommunicationEstablishment = ( !i->pingEndpoint.pingReceived
684                             || i->pingEndpoint.endpointName != remoteEndpoint );
685                            
686                     i->pingEndpoint.endpointName = remoteEndpoint;
687                     i->pingEndpoint.pingReceived = true;
688                     i->pingEndpoint.lastPingReceiveTime = currentTime;
689                 }
690
691                 if( restartPeerCommunicationEstablishment )
692                     externalCommunicationsSender_.RestartPeerCommunicationEstablishment( *i, currentTime );
693
694                 break;
695             }
696         }
697     }
698    
699 protected:
700
701     virtual void ProcessMessage( const osc::ReceivedMessage& m,
702                         const IpEndpointName& remoteEndpoint )
703     {
704         try{
705    
706             if( strcmp( m.AddressPattern(), "/groupclient/user_info" ) == 0 ){
707                 user_info( m, remoteEndpoint );
708             }else if( strcmp( m.AddressPattern(), "/groupclient/ping" ) == 0 ){
709                 ping( m, remoteEndpoint );
710             }else if( strcmp( m.AddressPattern(), "/groupclient/user_alive_status" ) == 0 ){
711                 user_alive_status( m, remoteEndpoint );
712             }else if( strcmp( m.AddressPattern(), "/groupclient/user_group_status" ) == 0 ){
713                 user_group_status( m, remoteEndpoint );
714             }
715
716         }catch( osc::Exception& e ){
717             std::cout << "error while parsing message: " << e.what() << "\n";
718         }
719     }
720
721     IpEndpointName remoteServerEndpoint_;
722
723         const char *userName_;
724     const char *userPassword_;
725     const char *groupName_;
726     const char *groupPassword_;
727
728     UdpTransmitSocket localRxSocket_;
729
730     ExternalCommunicationsSender& externalCommunicationsSender_;
731
732 public:
733     ExternalSocketListener( const IpEndpointName& remoteServerEndpoint,
734                         int localRxPort, const char *userName, const char *userPassword,
735             const char *groupName, const char *groupPassword,
736             ExternalCommunicationsSender& externalCommunicationsSender )
737         : remoteServerEndpoint_( remoteServerEndpoint )
738         , userName_( userName )
739         , userPassword_( userPassword )
740         , groupName_( groupName )
741         , groupPassword_( groupPassword )
742         , localRxSocket_( IpEndpointName( "localhost", localRxPort ) )
743         , externalCommunicationsSender_( externalCommunicationsSender )
744     {
745     }
746
747     virtual void ProcessPacket( const char *data, int size,
748                         const IpEndpointName& remoteEndpoint )
749     {
750         // for now we parse _all_ packets, and pass all those on to clients
751         // except those which come from the server. ideally we should avoid
752         // parsing most packets except the ones containing pings, or perhaps
753         // only process non-bundled pings.
754
755         // in the future it could be useful to register which peer a packet
756         // is coming from so that we can keep track of channel activity
757         // not just by receiving pings but also by recieving other traffic
758         // this would also allow us to reject packets from unknown sources
759
760        
761         osc::OscPacketListener::ProcessPacket( data, size, remoteEndpoint );
762                    
763         if( remoteEndpoint != remoteServerEndpoint_ ){
764          
765             // forward packet to local receive socket
766
767             localRxSocket_.Send( data, size );
768         }
769     }
770 };
771
772
773 class LocalTxSocketListener : public PacketListener {
774
775     ExternalCommunicationsSender& externalCommunicationsSender_;
776    
777 public:
778     LocalTxSocketListener( ExternalCommunicationsSender& externalCommunicationsSender )
779         : externalCommunicationsSender_( externalCommunicationsSender )
780     {
781     }
782
783         virtual void ProcessPacket( const char *data, int size,
784                         const IpEndpointName& remoteEndpoint )
785     {
786         externalCommunicationsSender_.ForwardPacketToAllPeers( data, size );
787     }
788 };
789
790
791 char IntToHexDigit( int n )
792 {
793     if( n < 10 )
794         return (char)('0' + n);
795     else
796         return (char)('a' + (n-10));
797 }
798
799 void MakeHashString( char *dest, const char *src )
800 {
801     MD5_CTX md5Context;
802     MD5Init( &md5Context );
803     MD5Update( &md5Context, (unsigned char*)src, strlen(src) );
804     unsigned char numericHash[16];
805     MD5Final( numericHash, &md5Context );
806     for( int i=0; i < 16; ++i ){
807
808         *dest++ = IntToHexDigit(((unsigned char)numericHash[i] >> 4) & 0x0F);
809         *dest++ = IntToHexDigit((unsigned char)numericHash[i] & 0x0F);
810     }
811     *dest = '\0';
812 }
813
814
815 void RunOscGroupClientUntilSigInt(
816                 const IpEndpointName& serverRemoteEndpoint,
817                 int localToRemotePort, int localTxPort, int localRxPort,
818                 const char *userName, const char *userPassword,
819                 const char *groupName, const char *groupPassword )
820 {
821     // used hashed passwords instead of the user supplied ones
822     
823     char userPasswordHash[33];
824     MakeHashString( userPasswordHash, userPassword );
825
826     char groupPasswordHash[33];
827     MakeHashString( groupPasswordHash, groupPassword );
828
829         UdpReceiveSocket externalSocket(
830                         IpEndpointName( IpEndpointName::ANY_ADDRESS, localToRemotePort ) );
831
832         UdpReceiveSocket localTxSocket( localTxPort );
833
834     ExternalCommunicationsSender externalCommunicationsSender( externalSocket,
835                         serverRemoteEndpoint, localToRemotePort,
836                         userName, userPasswordHash, groupName, groupPasswordHash );
837
838     ExternalSocketListener externalSocketListener(
839                         serverRemoteEndpoint, localRxPort,
840             userName, userPasswordHash, groupName, groupPasswordHash,
841             externalCommunicationsSender );
842
843     LocalTxSocketListener localTxSocketListener( externalCommunicationsSender );
844
845         SocketReceiveMultiplexer mux;
846     mux.AttachPeriodicTimerListener( 0, (IDLE_PING_PERIOD_SECONDS * 1000) / 10, &externalCommunicationsSender );
847         mux.AttachSocketListener( &externalSocket, &externalSocketListener );
848         mux.AttachSocketListener( &localTxSocket, &localTxSocketListener );   
849
850         std::cout << "running...\n";
851         std::cout << "press ctrl-c to end\n";
852
853         mux.RunUntilSigInt();
854
855         std::cout << "finishing.\n";   
856 }
857
858
859 int oscgroupclient_main(int argc, char* argv[])
860 {
861     try{
862         if( argc != 10 ){
863             std::cout << "usage: oscgroupclient serveraddress serverport localtoremoteport localtxport localrxport username password groupname grouppassword\n";
864             std::cout << "users should send data to localhost:localtxport and listen on localhost:localrxport\n";
865             return 0;
866         }
867
868                 IpEndpointName serverRemoteEndpoint( argv[1], atoi( argv[2] ) );
869         int localToRemotePort = atoi( argv[3] );
870         int localTxPort = atoi( argv[4] );
871         int localRxPort = atoi( argv[5] );
872         const char *userName = argv[6];
873         const char *userPassword = argv[7];
874         const char *groupName = argv[8];
875         const char *groupPassword = argv[9];
876
877                 char serverAddressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ];
878                 serverRemoteEndpoint.AddressAndPortAsString( serverAddressString );
879
880         std::cout << "oscgroupclient\n";
881         std::cout << "connecting to group '" << groupName << "' as user '" << userName << "'.\n";
882         std::cout << "using server at " << serverAddressString
883                         << " with external traffic on local port " << localToRemotePort << "\n";
884         std::cout << "--> send outbound traffic to localhost port " << localTxPort << "\n";
885         std::cout << "<-- listen for inbound traffic on localhost port " << localRxPort << "\n";
886
887         RunOscGroupClientUntilSigInt( serverRemoteEndpoint, localToRemotePort,
888                 localTxPort, localRxPort, userName, userPassword, groupName, groupPassword );
889
890     }catch( std::exception& e ){
891         std::cout << e.what() << std::endl;
892     }
893    
894     return 0;
895 }
896
897
898 #ifndef NO_MAIN
899
900