/* OSCgroups -- open sound control groupcasting infrastructure Copyright (C) 2005 Ross Bencina This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "OscGroupClient.h" #include #include #include #include #include #include #include #include "osc/OscReceivedElements.h" #include "osc/OscOutboundPacketStream.h" #include "osc/OscPacketListener.h" #include "ip/UdpSocket.h" #include "ip/IpEndpointName.h" #include "ip/PacketListener.h" #include "ip/TimerListener.h" #include "md5.h" #if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug namespace std { using ::__strcmp__; // avoid error: E2316 '__strcmp__' is not a member of 'std'. } #endif /* There are three sockets: externalSocket is used to send and receive data from the network including the server and other peers localRxSocket is used to forward data to the local (client) application from other peers localTxSocket is used to recieve data from the local (client) application to be forwarded to other peers Some behavioral rules: the choice of which peer endpoint to forward traffic to is made based on which endpoints we have received pings from. if pings have been received from multiple endpoints preference is given to the private endpoint. as soon as a ping has been received we start forwarding data during the establishment phase we ping all endpoints repeatedly even if we have already received pings from an endpoint. during the establishment phase we initially send pings faster, and then gradually return to a slower rate. the establishment phase ends when: - we receive a ping from the private endpoint - we receive a ping from any endpoint, and ESTABLISHMENT_PING_PERIOD_COUNT ping periods have elapsed if the server indicates that any of the port or address information of the peer has changed, we restart the establishment process. if a ping is received from an endpoint which we haven't received from before we restart the establishment phase. pings are sent less frequently if the channel is being kept open by forwarded traffic. We ensure that some traffic is sent accross the link every IDLE_PING_PERIOD_SECONDS and that a ping is sent accross the link at least every ACTIVE_PING_PERIOD_SECONDS if the last time at which the server has heard from a peer AND the last time from which we received a ping from the peer exceeds PURGE_PEER_TIMEOUT_SECONDS then the peer is removed from the peers list. if we havn't received a ping from a peer in PEER_ESTABLISHMENT_RETRY_TIME_SECONDS then we attempt to re-establish the connection. TODO: o- report ping times (requires higher resolution timer i think) ----------- */ #define PURGE_PEER_TIMEOUT_SECONDS 180 // time before removing peer from active list #define PEER_ESTABLISHMENT_RETRY_TIME_SECONDS 60 #define IDLE_PING_PERIOD_SECONDS 6 // seconds between pings when the link is idle #define ACTIVE_PING_PERIOD_SECONDS 30 // seconds between pings when the link is active #define ESTABLISHMENT_PING_PERIOD_COUNT 5 // 5 pings are always sent to all peer endpoints struct PeerEndpoint{ PeerEndpoint() : pingReceived( false ) , sentPingsCount( 0 ) , forwardedPacketsCount( 0 ) {} IpEndpointName endpointName; bool pingReceived; std::time_t lastPingReceiveTime; int sentPingsCount; std::time_t lastPingSendTime; int forwardedPacketsCount; std::time_t lastPacketForwardTime; }; struct Peer{ Peer( const char *userName ) : name( userName ) , pingPeriodCount( 0 ) {} std::string name; std::time_t lastUserInfoReceiveTime; int secondsSinceLastAliveReceivedByServer; /* we maintain three addresses for each peer. the private address is the address the peer thinks it is, the public is the address that the peer appears as to the server, and the ping address is the address that the client appears as to us when we receive a ping from it. this last address is necessary for half cone (symmetric) NATs which assign the peer a different port to talk to us than the one they assigned to talk to the server. */ PeerEndpoint privateEndpoint; PeerEndpoint publicEndpoint; PeerEndpoint pingEndpoint; int pingPeriodCount; std::time_t lastPingPeriodTime; std::time_t MostRecentActivityTime() const { // the most recent activity time is the most recent time either the // server heard from the peer, or we received a ping from the peer std::time_t lastUserInfoReceivedByTheServerTime = lastUserInfoReceiveTime - secondsSinceLastAliveReceivedByServer; // FIXME: assumes time_t is in seconds std::time_t result = lastUserInfoReceivedByTheServerTime; if( privateEndpoint.pingReceived ) result = std::max( result, privateEndpoint.lastPingReceiveTime ); if( publicEndpoint.pingReceived ) result = std::max( result, publicEndpoint.lastPingReceiveTime ); if( pingEndpoint.pingReceived ) result = std::max( result, pingEndpoint.lastPingReceiveTime ); return result; } }; static std::vector peers_; class ExternalCommunicationsSender : public TimerListener { #define IP_MTU_SIZE 1536 char aliveBuffer_[IP_MTU_SIZE]; std::size_t aliveSize_; std::time_t lastAliveSentTime_; char pingBuffer_[IP_MTU_SIZE]; std::size_t pingSize_; UdpSocket& externalSocket_; IpEndpointName remoteServerEndpoint_; IpEndpointName localToServerEndpoint_; std::string userName_; std::string userPassword_; std::string groupName_; std::string groupPassword_; void PrepareAliveBuffer() { osc::OutboundPacketStream p( aliveBuffer_, IP_MTU_SIZE ); p << osc::BeginBundle(); p << osc::BeginMessage( "/groupserver/user_alive" ) << userName_.c_str() << userPassword_.c_str() << ~((osc::int32)localToServerEndpoint_.address) << (osc::int32)localToServerEndpoint_.port << groupName_.c_str() << groupPassword_.c_str() << osc::EndMessage; p << osc::BeginMessage( "/groupserver/get_group_users_info" ) << groupName_.c_str() << groupPassword_.c_str() << osc::EndMessage; p << osc::EndBundle; aliveSize_ = p.Size(); } void SendAlive( std::time_t currentTime ) { int secondsSinceLastAliveSent = (int)std::difftime(currentTime, lastAliveSentTime_); if( secondsSinceLastAliveSent >= IDLE_PING_PERIOD_SECONDS ){ externalSocket_.SendTo( remoteServerEndpoint_, aliveBuffer_, aliveSize_ ); lastAliveSentTime_ = currentTime; } } void PreparePingBuffer() { osc::OutboundPacketStream p( pingBuffer_, IP_MTU_SIZE ); p << osc::BeginBundle(); p << osc::BeginMessage( "/groupclient/ping" ) << userName_.c_str() << osc::EndMessage; p << osc::EndBundle; pingSize_ = p.Size(); } void SendPing( PeerEndpoint& to, std::time_t currentTime ) { char addressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ]; to.endpointName.AddressAndPortAsString( addressString ); std::cout << "sending ping to " << addressString << "\n"; externalSocket_.SendTo( to.endpointName, pingBuffer_, pingSize_ ); ++to.sentPingsCount; to.lastPingSendTime = currentTime; } PeerEndpoint* SelectEndpoint( Peer& peer ) { if( peer.privateEndpoint.pingReceived ){ return &peer.privateEndpoint; }else if( peer.publicEndpoint.pingReceived ){ return &peer.publicEndpoint; }else if( peer.pingEndpoint.pingReceived ){ return &peer.pingEndpoint; } return 0; } void PollPeerPingTimer( Peer& peer, std::time_t currentTime, bool executeNowIgnoringTimeouts=false ) { bool noPingsReceivedYet = !peer.privateEndpoint.pingReceived && !peer.publicEndpoint.pingReceived && !peer.pingEndpoint.pingReceived; if( !noPingsReceivedYet ){ // check whether we should attempt to re-establish the link // due to no traffic arriving for PEER_ESTABLISHMENT_RETRY_TIME_SECONDS std::time_t mostRecentPingTime = 0; if( peer.privateEndpoint.pingReceived ) mostRecentPingTime = std::max( mostRecentPingTime, peer.privateEndpoint.lastPingReceiveTime ); if( peer.publicEndpoint.pingReceived ) mostRecentPingTime = std::max( mostRecentPingTime, peer.publicEndpoint.lastPingReceiveTime ); if( peer.pingEndpoint.pingReceived ) mostRecentPingTime = std::max( mostRecentPingTime, peer.pingEndpoint.lastPingReceiveTime ); if( (int)std::difftime(currentTime, mostRecentPingTime) > PEER_ESTABLISHMENT_RETRY_TIME_SECONDS ){ peer.pingPeriodCount = 0; executeNowIgnoringTimeouts = true; } } bool inEstablishmentPhase = ( ( peer.pingPeriodCount < ESTABLISHMENT_PING_PERIOD_COUNT ) && ( !peer.privateEndpoint.pingReceived ) ) || noPingsReceivedYet; if( inEstablishmentPhase ){ int pingPeriod; if( peer.pingPeriodCount < ESTABLISHMENT_PING_PERIOD_COUNT ){ pingPeriod = (int) (IDLE_PING_PERIOD_SECONDS * ((double)(peer.pingPeriodCount + 1) / (double) ESTABLISHMENT_PING_PERIOD_COUNT)); }else{ pingPeriod = IDLE_PING_PERIOD_SECONDS; } if( currentTime >= (peer.lastPingPeriodTime + pingPeriod) || executeNowIgnoringTimeouts ){ SendPing( peer.privateEndpoint, currentTime ); SendPing( peer.publicEndpoint, currentTime ); if( peer.pingEndpoint.pingReceived ) SendPing( peer.pingEndpoint, currentTime ); peer.lastPingPeriodTime = currentTime; ++peer.pingPeriodCount; } }else{ PeerEndpoint *peerEndpointToUse = SelectEndpoint( peer ); assert( peerEndpointToUse != 0 ); bool sendPing = false; if( executeNowIgnoringTimeouts ){ sendPing = true; }else{ if( peerEndpointToUse->sentPingsCount == 0 ){ sendPing = true; }else{ int secondsSinceLastPing = (int)std::difftime(currentTime, peerEndpointToUse->lastPingSendTime); if( peerEndpointToUse->forwardedPacketsCount == 0 ){ if( secondsSinceLastPing >= IDLE_PING_PERIOD_SECONDS ){ sendPing = true; } }else{ int secondsSinceLastForwardedTraffic = (int)std::difftime(currentTime, peerEndpointToUse->lastPacketForwardTime); if( secondsSinceLastForwardedTraffic >= IDLE_PING_PERIOD_SECONDS ){ if( secondsSinceLastPing >= IDLE_PING_PERIOD_SECONDS ){ sendPing = true; } }else if( secondsSinceLastPing >= ACTIVE_PING_PERIOD_SECONDS ){ sendPing = true; } } } } if( sendPing ){ SendPing( *peerEndpointToUse, currentTime ); peer.lastPingPeriodTime = currentTime; ++peer.pingPeriodCount; } } } ExternalCommunicationsSender(); // no default ctor ExternalCommunicationsSender( const ExternalCommunicationsSender& ); // no copy ctor ExternalCommunicationsSender& operator=( const ExternalCommunicationsSender& ); // no assignment operator public: ExternalCommunicationsSender( UdpSocket& externalSocket, IpEndpointName remoteServerEndpoint, int localToRemotePort, const char *userName, const char *userPassword, const char *groupName, const char *groupPassword ) : lastAliveSentTime_( 0 ) , externalSocket_( externalSocket ) , remoteServerEndpoint_( remoteServerEndpoint ) , localToServerEndpoint_( externalSocket.LocalEndpointFor( remoteServerEndpoint ).address, localToRemotePort ) , userName_( userName ) , userPassword_( userPassword ) , groupName_( groupName ) , groupPassword_( groupPassword ) { PrepareAliveBuffer(); PreparePingBuffer(); } void RestartPeerCommunicationEstablishment( Peer& peer, std::time_t currentTime ) { peer.pingPeriodCount = 0; PollPeerPingTimer( peer, currentTime, true ); } void ForwardPacketToAllPeers( const char *data, int size ) { std::time_t currentTime = std::time(0); for( std::vector::iterator i = peers_.begin(); i != peers_.end(); ++i ){ PeerEndpoint *peerEndpointToUse = SelectEndpoint( *i ); if( peerEndpointToUse ){ externalSocket_.SendTo( peerEndpointToUse->endpointName, data, size ); ++peerEndpointToUse->forwardedPacketsCount; peerEndpointToUse->lastPacketForwardTime = currentTime; } } } virtual void TimerExpired() { std::time_t currentTime = std::time(0); SendAlive( currentTime ); // check for peers to purge, std::vector::iterator i = peers_.begin(); while( i != peers_.end() ){ if( std::difftime(currentTime,i->MostRecentActivityTime()) > PURGE_PEER_TIMEOUT_SECONDS ){ i = peers_.erase( i ); }else{ PollPeerPingTimer( *i, currentTime ); ++i; } } } }; class ExternalSocketListener : public osc::OscPacketListener { void user_alive_status( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint ) { // only accept user_alive_status from the server if( remoteEndpoint != remoteServerEndpoint_ ) return; // /groupclient/user_alive_status userName userPassword status osc::ReceivedMessageArgumentStream args = m.ArgumentStream(); const char *userName, *userPassword, *status; args >> userName >> userPassword >> status; if( std::strcmp( userName, userName_ ) == 0 && std::strcmp( userPassword, userPassword_ ) == 0 ){ // message really is for us if( std::strcmp( status, "ok" ) == 0 ){ std::cout << "ok: user '" << userName << "' is registered with server\n"; }else{ std::cout << "user registration error: server returned status of '" << status << "' for user '" << userName << "'\n"; } } } void user_group_status( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint ) { // only accept user_alive_status from the server if( remoteEndpoint != remoteServerEndpoint_ ) return; // /groupclient/user_group_status userName userPassword groupName groupPassword status osc::ReceivedMessageArgumentStream args = m.ArgumentStream(); const char *userName, *userPassword, *groupName, *groupPassword, *status; args >> userName >> userPassword >> groupName >> groupPassword >> status; if( std::strcmp( userName, userName_ ) == 0 && std::strcmp( userPassword, userPassword_ ) == 0 && std::strcmp( groupName, groupName_ ) == 0 && std::strcmp( groupPassword, groupPassword_ ) == 0 ){ // message really is for us if( std::strcmp( status, "ok" ) == 0 ){ std::cout << "ok: user '" << userName << "' is a member of group '" << groupName << "'\n"; }else{ std::cout << "group membership error: server returned status of '" << status << "' for user '" << userName << "' membership of group '" << groupName << "'\n"; } } } void user_info( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint ) { // only accept user_info from the server if( remoteEndpoint != remoteServerEndpoint_ ) return; // /groupclient/user_info userName privateIpAddress privatePort // publicIpAddress publicPort secondsSinceLastAlive group0 group1 ... osc::ReceivedMessageArgumentStream args = m.ArgumentStream(); const char *userName; osc::int32 privateAddress; osc::int32 privatePort; osc::int32 publicAddress; osc::int32 publicPort; osc::int32 secondsSinceLastAlive; args >> userName >> privateAddress >> privatePort >> publicAddress >> publicPort >> secondsSinceLastAlive; // addresses are transmitted as ones complement (bit inverse) // to avoid problems with buggy NATs trying to re-write addresses privateAddress = ~privateAddress; publicAddress = ~publicAddress; IpEndpointName privateEndpoint( privateAddress, privatePort ); IpEndpointName publicEndpoint( publicAddress, publicPort ); char privateAddressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ]; privateEndpoint.AddressAndPortAsString( privateAddressString ); char publicAddressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ]; publicEndpoint.AddressAndPortAsString( publicAddressString ); std::cout << "user info received for '" << userName << "', " << "private: " << privateAddressString << " public: " << publicAddressString << "\n"; if( std::strcmp( userName, userName_ ) == 0 ) return; // discard info referring to ourselves bool userIsInGroup = false; while( !args.Eos() ){ const char *groupName; args >> groupName; if( std::strcmp( groupName, groupName_ ) == 0 ){ userIsInGroup = true; break; } } if( userIsInGroup ){ bool restartPeerCommunicationEstablishment = false; bool found = false; std::vector::iterator peer; for( std::vector::iterator i = peers_.begin(); i != peers_.end(); ++i ){ if( i->name.compare( userName ) == 0 ){ peer = i; found = true; break; } } if( !found ){ peers_.push_back( Peer( userName ) ); peer = peers_.end() - 1; restartPeerCommunicationEstablishment = true; } if( peer->privateEndpoint.endpointName != privateEndpoint ){ peer->privateEndpoint.endpointName = privateEndpoint; peer->privateEndpoint.pingReceived = false; peer->privateEndpoint.forwardedPacketsCount = 0; peer->pingEndpoint.pingReceived = false; peer->pingEndpoint.forwardedPacketsCount = 0; restartPeerCommunicationEstablishment = true; } if( peer->publicEndpoint.endpointName != publicEndpoint ){ peer->publicEndpoint.endpointName = publicEndpoint; peer->publicEndpoint.pingReceived = false; peer->publicEndpoint.forwardedPacketsCount = 0; peer->pingEndpoint.pingReceived = false; peer->pingEndpoint.forwardedPacketsCount = 0; restartPeerCommunicationEstablishment = true; } peer->secondsSinceLastAliveReceivedByServer = secondsSinceLastAlive; std::time_t currentTime = std::time(0); peer->lastUserInfoReceiveTime = currentTime; if( restartPeerCommunicationEstablishment ) externalCommunicationsSender_.RestartPeerCommunicationEstablishment( *peer, currentTime ); }else{ // fixme should remove user from peer list if it is present } } void ping( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint ) { osc::ReceivedMessageArgumentStream args = m.ArgumentStream(); const char *userName; // osc::TimeTag timeSent; // TODO: // support 3 variants of the ping message: // /ping userName (basic version, only one needed for compatibility) // /ping userName timeSent // response -> /ping userName timeSent inResponseToUserName inResponseToTimeSent args >> userName >> osc::EndMessage; char sourceAddressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ]; remoteEndpoint.AddressAndPortAsString( sourceAddressString ); std::cout << "ping recieved from '" << userName << "' at " << sourceAddressString << "\n"; for( std::vector::iterator i = peers_.begin(); i != peers_.end(); ++i ){ if( i->name.compare( userName ) == 0 ){ bool restartPeerCommunicationEstablishment = false; std::time_t currentTime = std::time(0); if( remoteEndpoint == i->privateEndpoint.endpointName ){ restartPeerCommunicationEstablishment = !i->privateEndpoint.pingReceived; i->privateEndpoint.pingReceived = true; i->privateEndpoint.lastPingReceiveTime = currentTime; }else if( remoteEndpoint == i->publicEndpoint.endpointName ){ restartPeerCommunicationEstablishment = !i->publicEndpoint.pingReceived; i->publicEndpoint.pingReceived = true; i->publicEndpoint.lastPingReceiveTime = currentTime; }else{ // otherwise assume the messages is coming from the ping endpoint restartPeerCommunicationEstablishment = ( !i->pingEndpoint.pingReceived || i->pingEndpoint.endpointName != remoteEndpoint ); i->pingEndpoint.endpointName = remoteEndpoint; i->pingEndpoint.pingReceived = true; i->pingEndpoint.lastPingReceiveTime = currentTime; } if( restartPeerCommunicationEstablishment ) externalCommunicationsSender_.RestartPeerCommunicationEstablishment( *i, currentTime ); break; } } } protected: virtual void ProcessMessage( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint ) { try{ if( std::strcmp( m.AddressPattern(), "/groupclient/user_info" ) == 0 ){ user_info( m, remoteEndpoint ); }else if( std::strcmp( m.AddressPattern(), "/groupclient/ping" ) == 0 ){ ping( m, remoteEndpoint ); }else if( std::strcmp( m.AddressPattern(), "/groupclient/user_alive_status" ) == 0 ){ user_alive_status( m, remoteEndpoint ); }else if( std::strcmp( m.AddressPattern(), "/groupclient/user_group_status" ) == 0 ){ user_group_status( m, remoteEndpoint ); } }catch( osc::Exception& e ){ std::cout << "error while parsing message: " << e.what() << "\n"; } } IpEndpointName remoteServerEndpoint_; const char *userName_; const char *userPassword_; const char *groupName_; const char *groupPassword_; UdpTransmitSocket localRxSocket_; ExternalCommunicationsSender& externalCommunicationsSender_; ExternalSocketListener(); // no default ctor ExternalSocketListener( const ExternalSocketListener& ); // no copy ctor ExternalSocketListener& operator=( const ExternalSocketListener& ); // no assignment operator public: ExternalSocketListener( const IpEndpointName& remoteServerEndpoint, int localRxPort, const char *userName, const char *userPassword, const char *groupName, const char *groupPassword, ExternalCommunicationsSender& externalCommunicationsSender ) : remoteServerEndpoint_( remoteServerEndpoint ) , userName_( userName ) , userPassword_( userPassword ) , groupName_( groupName ) , groupPassword_( groupPassword ) , localRxSocket_( IpEndpointName( "localhost", localRxPort ) ) , externalCommunicationsSender_( externalCommunicationsSender ) { } virtual void ProcessPacket( const char *data, int size, const IpEndpointName& remoteEndpoint ) { // for now we parse _all_ packets, and pass all those on to clients // except those which come from the server. ideally we should avoid // parsing most packets except the ones containing pings, or perhaps // only process non-bundled pings. // in the future it could be useful to register which peer a packet // is coming from so that we can keep track of channel activity // not just by receiving pings but also by recieving other traffic // this would also allow us to reject packets from unknown sources osc::OscPacketListener::ProcessPacket( data, size, remoteEndpoint ); if( remoteEndpoint != remoteServerEndpoint_ ){ // forward packet to local receive socket localRxSocket_.Send( data, size ); } } }; class LocalTxSocketListener : public PacketListener { ExternalCommunicationsSender& externalCommunicationsSender_; LocalTxSocketListener(); // no default ctor LocalTxSocketListener( const LocalTxSocketListener& ); // no copy ctor LocalTxSocketListener& operator=( const LocalTxSocketListener& ); // no assignment operator public: LocalTxSocketListener( ExternalCommunicationsSender& externalCommunicationsSender ) : externalCommunicationsSender_( externalCommunicationsSender ) { } virtual void ProcessPacket( const char *data, int size, const IpEndpointName& remoteEndpoint ) { (void) remoteEndpoint; // suppress unused parameter warning externalCommunicationsSender_.ForwardPacketToAllPeers( data, size ); } }; char IntToHexDigit( int n ) { if( n < 10 ) return (char)('0' + n); else return (char)('a' + (n-10)); } void MakeHashString( char *dest, const char *src ) { MD5_CTX md5Context; MD5Init( &md5Context ); MD5Update( &md5Context, (unsigned char*)src, (unsigned int)std::strlen(src) ); unsigned char numericHash[16]; MD5Final( numericHash, &md5Context ); char *p = dest; for( int i=0; i < 16; ++i ){ *p++ = IntToHexDigit(((unsigned char)numericHash[i] >> 4) & 0x0F); *p++ = IntToHexDigit((unsigned char)numericHash[i] & 0x0F); } *p = '\0'; //printf( "src: %s dest: %s\n", src, dest ); } void SanityCheckMd5() { // if anything in this function fails there's a problem with your build configuration // check that the size of types declared in md5.h are correct assert( sizeof(UINT2) == 2 ); assert( sizeof(UINT4) == 4 ); // sanity check that the hash is working by comparing to a known good hash: char testHash[33]; MakeHashString( testHash, "0123456789" ); assert( std::strcmp( testHash, "781e5e245d69b566979b86e28d23f2c7" ) == 0 ); } void RunOscGroupClientUntilSigInt( const IpEndpointName& serverRemoteEndpoint, int localToRemotePort, int localTxPort, int localRxPort, const char *userName, const char *userPassword, const char *groupName, const char *groupPassword ) { // used hashed passwords instead of the user supplied ones char userPasswordHash[33]; MakeHashString( userPasswordHash, userPassword ); char groupPasswordHash[33]; MakeHashString( groupPasswordHash, groupPassword ); UdpReceiveSocket externalSocket( IpEndpointName( IpEndpointName::ANY_ADDRESS, localToRemotePort ) ); UdpReceiveSocket localTxSocket( localTxPort ); ExternalCommunicationsSender externalCommunicationsSender( externalSocket, serverRemoteEndpoint, localToRemotePort, userName, userPasswordHash, groupName, groupPasswordHash ); ExternalSocketListener externalSocketListener( serverRemoteEndpoint, localRxPort, userName, userPasswordHash, groupName, groupPasswordHash, externalCommunicationsSender ); LocalTxSocketListener localTxSocketListener( externalCommunicationsSender ); SocketReceiveMultiplexer mux; mux.AttachPeriodicTimerListener( 0, (IDLE_PING_PERIOD_SECONDS * 1000) / 10, &externalCommunicationsSender ); mux.AttachSocketListener( &externalSocket, &externalSocketListener ); mux.AttachSocketListener( &localTxSocket, &localTxSocketListener ); std::cout << "running...\n"; std::cout << "press ctrl-c to end\n"; mux.RunUntilSigInt(); std::cout << "finishing.\n"; } int oscgroupclient_main(int argc, char* argv[]) { SanityCheckMd5(); try{ if( argc != 10 ){ std::cout << "usage: oscgroupclient serveraddress serverport localtoremoteport localtxport localrxport username password groupname grouppassword\n"; std::cout << "users should send data to localhost:localtxport and listen on localhost:localrxport\n"; return 0; } IpEndpointName serverRemoteEndpoint( argv[1], atoi( argv[2] ) ); int localToRemotePort = std::atoi( argv[3] ); int localTxPort = std::atoi( argv[4] ); int localRxPort = std::atoi( argv[5] ); const char *userName = argv[6]; const char *userPassword = argv[7]; const char *groupName = argv[8]; const char *groupPassword = argv[9]; char serverAddressString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ]; serverRemoteEndpoint.AddressAndPortAsString( serverAddressString ); std::cout << "oscgroupclient\n"; std::cout << "connecting to group '" << groupName << "' as user '" << userName << "'.\n"; std::cout << "using server at " << serverAddressString << " with external traffic on local port " << localToRemotePort << "\n"; std::cout << "--> send outbound traffic to localhost port " << localTxPort << "\n"; std::cout << "<-- listen for inbound traffic on localhost port " << localRxPort << "\n"; RunOscGroupClientUntilSigInt( serverRemoteEndpoint, localToRemotePort, localTxPort, localRxPort, userName, userPassword, groupName, groupPassword ); }catch( std::exception& e ){ std::cout << e.what() << std::endl; } return 0; } #ifndef NO_MAIN int main(int argc, char* argv[]) { return oscgroupclient_main( argc, argv ); } #endif /* NO_MAIN */