nodescore/oscgroups/OscGroupClient.cpp

943 lines
33 KiB
C++
Executable File

/*
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 <cstring>
#include <iostream>
#include <vector>
#include <ctime>
#include <string>
#include <cassert>
#include <cstdlib>
#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<Peer> 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<Peer>::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<Peer>::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<Peer>::iterator peer;
for( std::vector<Peer>::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<Peer>::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 */