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