diff --git a/Jamulus.pro b/Jamulus.pro index 28f3bacd74..fd1239707b 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -399,6 +399,8 @@ HEADERS += src/plugins/audioreverb.h \ src/serverlogging.h \ src/settings.h \ src/socket.h \ + src/tcpserver.h \ + src/tcpconnection.h \ src/util.h \ src/recorder/jamrecorder.h \ src/recorder/creaperproject.h \ @@ -507,6 +509,8 @@ SOURCES += src/plugins/audioreverb.cpp \ src/settings.cpp \ src/signalhandler.cpp \ src/socket.cpp \ + src/tcpserver.cpp \ + src/tcpconnection.cpp \ src/util.cpp \ src/recorder/jamrecorder.cpp \ src/recorder/creaperproject.cpp \ diff --git a/src/channel.cpp b/src/channel.cpp index 9bd409889a..c8246ae5ad 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -26,6 +26,7 @@ // CChannel implementation ***************************************************** CChannel::CChannel ( const bool bNIsServer ) : + pTcpConnection ( nullptr ), vecfGains ( MAX_NUM_CHANNELS, 1.0f ), vecfPannings ( MAX_NUM_CHANNELS, 0.5f ), iCurSockBufNumFrames ( INVALID_INDEX ), @@ -81,7 +82,7 @@ CChannel::CChannel ( const bool bNIsServer ) : QObject::connect ( &Protocol, &CProtocol::ChangeChanPan, this, &CChannel::OnChangeChanPan ); - QObject::connect ( &Protocol, &CProtocol::ClientIDReceived, this, &CChannel::ClientIDReceived ); + QObject::connect ( &Protocol, &CProtocol::ClientIDReceived, this, &CChannel::OnClientIDReceived ); QObject::connect ( &Protocol, &CProtocol::MuteStateHasChangedReceived, this, &CChannel::MuteStateHasChangedReceived ); @@ -712,3 +713,25 @@ void CChannel::UpdateSocketBufferSize() SetSockBufNumFrames ( SockBuf.GetAutoSetting(), true ); } } + +void CChannel::OnClientIDReceived ( int iChanID ) +{ + qDebug() << Q_FUNC_INFO << "iChanID =" << iChanID; + emit ClientIDReceived ( iChanID ); +} + +void CChannel::CreateConClientListMes ( const CVector& vecChanInfo, CProtocol& ConnLessProtocol ) +{ + if ( pTcpConnection ) + { + qDebug() << "- sending client list via TCP"; + + ConnLessProtocol.CreateCLConnClientsListMes ( InetAddr, vecChanInfo, pTcpConnection ); + } + else + { + qDebug() << "- sending client list via UDP"; + + Protocol.CreateConClientListMes ( vecChanInfo ); + } +} diff --git a/src/channel.h b/src/channel.h index 1567268307..6879c16c86 100644 --- a/src/channel.h +++ b/src/channel.h @@ -86,6 +86,9 @@ class CChannel : public QObject void SetAddress ( const CHostAddress& NAddr ) { InetAddr = NAddr; } const CHostAddress& GetAddress() const { return InetAddr; } + void SetTcpConnection ( CTcpConnection* pConnection ) { pTcpConnection = pConnection; } + CTcpConnection* GetTcpConnection() { return pTcpConnection; } + void ResetInfo() { bIsIdentified = false; @@ -161,7 +164,7 @@ class CChannel : public QObject void CreateReqChannelLevelListMes() { Protocol.CreateReqChannelLevelListMes(); } //### TODO: END ###// - void CreateConClientListMes ( const CVector& vecChanInfo ) { Protocol.CreateConClientListMes ( vecChanInfo ); } + void CreateConClientListMes ( const CVector& vecChanInfo, CProtocol& ConnLessProtocol ); void CreateRecorderStateMes ( const ERecorderState eRecorderState ) { Protocol.CreateRecorderStateMes ( eRecorderState ); } @@ -186,7 +189,8 @@ class CChannel : public QObject } // connection parameters - CHostAddress InetAddr; + CHostAddress InetAddr; + CTcpConnection* pTcpConnection; // channel info CChannelCoreInfo ChannelInfo; @@ -255,11 +259,12 @@ public slots: PutProtocolData ( iRecCounter, iRecID, vecbyMesBodyData, RecHostAddr ); } - void OnProtocolCLMessageReceived ( int iRecID, CVector vecbyMesBodyData, CHostAddress RecHostAddr ) + void OnProtocolCLMessageReceived ( int iRecID, CVector vecbyMesBodyData, CHostAddress RecHostAddr, CTcpConnection* pTcpConnection ) { - emit DetectedCLMessage ( vecbyMesBodyData, iRecID, RecHostAddr ); + emit DetectedCLMessage ( vecbyMesBodyData, iRecID, RecHostAddr, pTcpConnection ); } + void OnClientIDReceived ( int iChanID ); void OnNewConnection() { emit NewConnection(); } signals: @@ -282,7 +287,7 @@ public slots: void RecorderStateReceived ( ERecorderState eRecorderState ); void Disconnected(); - void DetectedCLMessage ( CVector vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr ); + void DetectedCLMessage ( CVector vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr, CTcpConnection* pTcpConnection ); void ParseMessageBody ( CVector vecbyMesBodyData, int iRecCounter, int iRecID ); }; diff --git a/src/client.cpp b/src/client.cpp index 417f08a404..233b706103 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -145,7 +145,9 @@ CClient::CClient ( const quint16 iPortNumber, QObject::connect ( &ConnLessProtocol, &CProtocol::CLRedServerListReceived, this, &CClient::CLRedServerListReceived ); - QObject::connect ( &ConnLessProtocol, &CProtocol::CLConnClientsListMesReceived, this, &CClient::CLConnClientsListMesReceived ); + QObject::connect ( &ConnLessProtocol, &CProtocol::CLTcpSupported, this, &CClient::OnCLTcpSupported ); + + QObject::connect ( &ConnLessProtocol, &CProtocol::CLConnClientsListMesReceived, this, &CClient::OnCLConnClientsListMesReceived ); QObject::connect ( &ConnLessProtocol, &CProtocol::CLPingReceived, this, &CClient::OnCLPingReceived ); @@ -249,11 +251,80 @@ void CClient::OnSendProtMessage ( CVector vecMessage ) Socket.SendPacket ( vecMessage, Channel.GetAddress() ); } -void CClient::OnSendCLProtMessage ( CHostAddress InetAddr, CVector vecMessage ) +void CClient::OnSendCLProtMessage ( CHostAddress InetAddr, CVector vecMessage, CTcpConnection* pTcpConnection, enum EProtoMode eProtoMode ) { + if ( pTcpConnection ) + { + // already have TCP connection - just send and return + pTcpConnection->write ( (const char*) &( (CVector) vecMessage )[0], vecMessage.Size() ); + return; + } + // the protocol queries me to call the function to send the message // send it through the network - Socket.SendPacket ( vecMessage, InetAddr ); + if ( eProtoMode != PROTO_UDP ) + { + // create a TCP client connection and send message + QTcpSocket* pSocket = new QTcpSocket ( this ); + + // timer for TCP connect timeout shorter than Qt default 30 seconds + QTimer* pTimer = new QTimer ( this ); + pTimer->setSingleShot ( true ); + + connect ( pTimer, &QTimer::timeout, this, [this, pSocket, pTimer]() { + if ( pSocket->state() != QAbstractSocket::ConnectedState ) + { + pSocket->abort(); + pSocket->deleteLater(); + qDebug() << "- TCP connect timeout"; + } + pTimer->deleteLater(); + } ); + +#if QT_VERSION >= QT_VERSION_CHECK( 5, 15, 0 ) +# define ERRORSIGNAL &QTcpSocket::errorOccurred +#else +# define ERRORSIGNAL QOverload::of ( &QAbstractSocket::error ) +#endif + connect ( pSocket, ERRORSIGNAL, this, [this, pSocket, pTimer] ( QAbstractSocket::SocketError err ) { + Q_UNUSED ( err ); + + pTimer->stop(); + pTimer->deleteLater(); + + qWarning() << "- TCP connection error:" << pSocket->errorString(); + // may want to specifically handle ConnectionRefusedError? + pSocket->deleteLater(); + } ); + + connect ( pSocket, &QTcpSocket::connected, this, [this, pSocket, pTimer, InetAddr, vecMessage, eProtoMode]() { + pTimer->stop(); + pTimer->deleteLater(); + + // connection succeeded, give it to a CTcpConnection + CTcpConnection* pTcpConnection = new CTcpConnection ( pSocket, + InetAddr, + this, + &Channel, + eProtoMode == PROTO_TCP_LONG ); // client connection, will self-delete on disconnect + + if ( eProtoMode == PROTO_TCP_LONG ) + { + Channel.SetTcpConnection ( pTcpConnection ); // link session connection with channel + } + + pTcpConnection->write ( (const char*) &( (CVector) vecMessage )[0], vecMessage.Size() ); + + // the CTcpConnection object will pass the reply back up to CClient::Channel + } ); + + pSocket->connectToHost ( InetAddr.InetAddr, InetAddr.iPort ); + pTimer->start ( TCP_CONNECT_TIMEOUT_MS ); + } + else + { + Socket.SendPacket ( vecMessage, InetAddr ); + } } void CClient::OnInvalidPacketReceived ( CHostAddress RecHostAddr ) @@ -268,10 +339,10 @@ void CClient::OnInvalidPacketReceived ( CHostAddress RecHostAddr ) } } -void CClient::OnDetectedCLMessage ( CVector vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr ) +void CClient::OnDetectedCLMessage ( CVector vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr, CTcpConnection* pTcpConnection ) { // connection less messages are always processed - ConnLessProtocol.ParseConnectionLessMessageBody ( vecbyMesBodyData, iRecID, RecHostAddr ); + ConnLessProtocol.ParseConnectionLessMessageBody ( vecbyMesBodyData, iRecID, RecHostAddr, pTcpConnection ); } void CClient::OnJittBufSizeChanged ( int iNewJitBufSize ) @@ -975,6 +1046,16 @@ void CClient::OnClientIDReceived ( int iServerChanID ) ClearClientChannels(); } + // if TCP Supported has already been received, make TCP connection to server + iClientID = iServerChanID; // for sending back to server over TCP + + if ( bTcpSupported ) + { + // *** Make TCP connection + qDebug() << Q_FUNC_INFO << "need to make TCP connection for" << iClientID; + ConnLessProtocol.CreateCLClientIDMes ( Channel.GetAddress(), iClientID, PROTO_TCP_LONG ); // create persistent TCP connection + } + // allocate and map client-side channel 0 int iChanID = FindClientChannel ( iServerChanID, true ); // should always return channel 0 @@ -989,11 +1070,52 @@ void CClient::OnClientIDReceived ( int iServerChanID ) emit ClientIDReceived ( iChanID ); } +void CClient::OnCLTcpSupported ( CHostAddress InetAddr, int iID ) +{ + qDebug() << "- TCP supported at server" << InetAddr.toString() << "for ID =" << iID; + + if ( iID != PROTMESSID_CLM_CLIENT_ID ) + { + emit CLTcpSupported ( InetAddr, iID ); // pass to connect dialog + return; + } + + // if client ID already received, make TCP connection to server + bTcpSupported = true; + + if ( iClientID != INVALID_INDEX ) + { + // *** Make TCP connection + qDebug() << Q_FUNC_INFO << "need to make TCP connection for" << iClientID; + Q_ASSERT ( InetAddr == Channel.GetAddress() ); + ConnLessProtocol.CreateCLClientIDMes ( InetAddr, iClientID, PROTO_TCP_LONG ); // create persistent TCP connection + } +} + +void CClient::OnCLConnClientsListMesReceived ( CHostAddress InetAddr, CVector vecChanInfo, CTcpConnection* pTcpConnection ) +{ + // test if we are receiving for the connect dialog or a connected session + if ( pTcpConnection && pTcpConnection->IsSession() ) + { + qDebug() << "- sending client list to client dialog"; + OnConClientListMesReceived ( vecChanInfo ); // connected session + } + else + { + qDebug() << "- sending client list to connect dialog"; + emit CLConnClientsListMesReceived ( InetAddr, vecChanInfo ); // connect dialog + } +} + void CClient::Start() { // init object Init(); + // clear TCP info + iClientID = INVALID_INDEX; + bTcpSupported = false; + // initialise client channels ClearClientChannels(); @@ -1014,6 +1136,14 @@ void CClient::Stop() // stop audio interface Sound.Stop(); + // close any session TCP connection + CTcpConnection* pTcpConnection = Channel.GetTcpConnection(); + if ( pTcpConnection ) + { + Channel.SetTcpConnection ( nullptr ); + pTcpConnection->disconnectFromHost(); + } + // disable channel Channel.SetEnable ( false ); diff --git a/src/client.h b/src/client.h index 6139ef599e..3b6c317d5e 100644 --- a/src/client.h +++ b/src/client.h @@ -278,9 +278,15 @@ class CClient : public QObject void CreateCLServerListReqVerAndOSMes ( const CHostAddress& InetAddr ) { ConnLessProtocol.CreateCLReqVersionAndOSMes ( InetAddr ); } - void CreateCLServerListReqConnClientsListMes ( const CHostAddress& InetAddr ) { ConnLessProtocol.CreateCLReqConnClientsListMes ( InetAddr ); } + void CreateCLServerListReqConnClientsListMes ( const CHostAddress& InetAddr, enum EProtoMode eProtoMode ) + { + ConnLessProtocol.CreateCLReqConnClientsListMes ( InetAddr, eProtoMode ); + } - void CreateCLReqServerListMes ( const CHostAddress& InetAddr ) { ConnLessProtocol.CreateCLReqServerListMes ( InetAddr ); } + void CreateCLReqServerListMes ( const CHostAddress& InetAddr, enum EProtoMode eProtoMode ) + { + ConnLessProtocol.CreateCLReqServerListMes ( InetAddr, eProtoMode ); + } int EstimatedOverallDelay ( const int iPingTimeMs ); @@ -422,12 +428,16 @@ class CClient : public QObject int maxGainOrPanId; int iCurPingTime; + // for TCP protocol support + bool bTcpSupported; + int iClientID; + protected slots: void OnHandledSignal ( int sigNum ); void OnSendProtMessage ( CVector vecMessage ); void OnInvalidPacketReceived ( CHostAddress RecHostAddr ); - void OnDetectedCLMessage ( CVector vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr ); + void OnDetectedCLMessage ( CVector vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr, CTcpConnection* pTcpConnection ); void OnReqJittBufSize() { CreateServerJitterBufferMessage(); } void OnJittBufSizeChanged ( int iNewJitBufSize ); @@ -441,8 +451,9 @@ protected slots: } } void OnCLPingReceived ( CHostAddress InetAddr, int iMs ); + void OnCLTcpSupported ( CHostAddress InetAddr, int iID ); - void OnSendCLProtMessage ( CHostAddress InetAddr, CVector vecMessage ); + void OnSendCLProtMessage ( CHostAddress InetAddr, CVector vecMessage, CTcpConnection* pTcpConnection, enum EProtoMode eProtoMode ); void OnCLPingWithNumClientsReceived ( CHostAddress InetAddr, int iMs, int iNumClients ); @@ -456,6 +467,13 @@ protected slots: void OnMuteStateHasChangedReceived ( int iServerChanID, bool bIsMuted ); void OnCLChannelLevelListReceived ( CHostAddress InetAddr, CVector vecLevelList ); void OnConClientListMesReceived ( CVector vecChanInfo ); + void OnCLConnClientsListMesReceived ( CHostAddress InetAddr, CVector vecChanInfo, CTcpConnection* pTcpConnection ); + +public slots: + void OnCLSendEmptyMes ( CHostAddress InetAddr, CTcpConnection* pTcpConnection ) + { + ConnLessProtocol.CreateCLEmptyMes ( InetAddr, pTcpConnection ); + } signals: void ConClientListMesReceived ( CVector vecChanInfo ); @@ -471,6 +489,8 @@ protected slots: void CLRedServerListReceived ( CHostAddress InetAddr, CVector vecServerInfo ); + void CLTcpSupported ( CHostAddress InetAddr, int iID ); + void CLConnClientsListMesReceived ( CHostAddress InetAddr, CVector vecChanInfo ); void CLPingTimeWithNumClientsReceived ( CHostAddress InetAddr, int iPingTime, int iNumClients ); diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index 6063547896..9e90ce1bcb 100644 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -509,6 +509,8 @@ CClientDlg::CClientDlg ( CClient* pNCliP, QObject::connect ( pClient, &CClient::CLRedServerListReceived, this, &CClientDlg::OnCLRedServerListReceived ); + QObject::connect ( pClient, &CClient::CLTcpSupported, this, &CClientDlg::OnCLTcpSupported ); + QObject::connect ( pClient, &CClient::CLConnClientsListMesReceived, this, &CClientDlg::OnCLConnClientsListMesReceived ); QObject::connect ( pClient, &CClient::CLPingTimeWithNumClientsReceived, this, &CClientDlg::OnCLPingTimeWithNumClientsReceived ); diff --git a/src/clientdlg.h b/src/clientdlg.h index a5d327676a..d4a2a7d100 100644 --- a/src/clientdlg.h +++ b/src/clientdlg.h @@ -202,13 +202,16 @@ public slots: void OnNewLocalInputText ( QString strChatText ) { pClient->CreateChatTextMes ( strChatText ); } - void OnReqServerListQuery ( CHostAddress InetAddr ) { pClient->CreateCLReqServerListMes ( InetAddr ); } + void OnReqServerListQuery ( CHostAddress InetAddr, enum EProtoMode eProtoMode ) { pClient->CreateCLReqServerListMes ( InetAddr, eProtoMode ); } void OnCreateCLServerListPingMes ( CHostAddress InetAddr ) { pClient->CreateCLServerListPingMes ( InetAddr ); } void OnCreateCLServerListReqVerAndOSMes ( CHostAddress InetAddr ) { pClient->CreateCLServerListReqVerAndOSMes ( InetAddr ); } - void OnCreateCLServerListReqConnClientsListMes ( CHostAddress InetAddr ) { pClient->CreateCLServerListReqConnClientsListMes ( InetAddr ); } + void OnCreateCLServerListReqConnClientsListMes ( CHostAddress InetAddr, enum EProtoMode eProtoMode ) + { + pClient->CreateCLServerListReqConnClientsListMes ( InetAddr, eProtoMode ); + } void OnCLServerListReceived ( CHostAddress InetAddr, CVector vecServerInfo ) { @@ -220,6 +223,8 @@ public slots: ConnectDlg.SetServerList ( InetAddr, vecServerInfo, true ); } + void OnCLTcpSupported ( CHostAddress InetAddr, int iID ) { ConnectDlg.SetTcpSupported ( InetAddr, iID ); } + void OnCLConnClientsListMesReceived ( CHostAddress InetAddr, CVector vecChanInfo ) { ConnectDlg.SetConnClientsList ( InetAddr, vecChanInfo ); diff --git a/src/clientrpc.cpp b/src/clientrpc.cpp index c2efc59b25..09fae6a9a4 100644 --- a/src/clientrpc.cpp +++ b/src/clientrpc.cpp @@ -171,7 +171,7 @@ CClientRpc::CClientRpc ( CClient* pClient, CClientSettings* pSettings, CRpcServe if ( NetworkUtil::ParseNetworkAddress ( jsonDirectoryIp.toString(), haDirectoryAddress, false ) ) { // send the request for the server list - pClient->CreateCLReqServerListMes ( haDirectoryAddress ); + pClient->CreateCLReqServerListMes ( haDirectoryAddress, PROTO_UDP ); response["result"] = "ok"; } else diff --git a/src/connectdlg.cpp b/src/connectdlg.cpp index 5c16bbc3db..9bca8a2d8a 100644 --- a/src/connectdlg.cpp +++ b/src/connectdlg.cpp @@ -336,7 +336,7 @@ void CConnectDlg::RequestServerList() false ) ) { // send the request for the server list - emit ReqServerListQuery ( haDirectoryAddress ); + emit ReqServerListQuery ( haDirectoryAddress, PROTO_UDP ); // start timer, if this message did not get any respond to retransmit // the server list request message @@ -379,7 +379,7 @@ void CConnectDlg::OnTimerReRequestServList() { // note that this is a connection less message which may get lost // and therefore it makes sense to re-transmit it - emit ReqServerListQuery ( haDirectoryAddress ); + emit ReqServerListQuery ( haDirectoryAddress, PROTO_UDP ); } } @@ -531,6 +531,9 @@ void CConnectDlg::SetServerList ( const CHostAddress& InetAddr, const CVectorsetData ( LVC_NAME, Qt::UserRole, CurHostAddress.toString() ); + enum EClientFetchMode eFetchMode = CFM_UDP_REQUEST; // start off in UDP mode + pNewListViewItem->setData ( LVC_CLIENTS, Qt::UserRole, eFetchMode ); // initialise fetch mode + // per default expand the list item (if not "show all servers") if ( bShowAllMusicians ) { @@ -544,6 +547,48 @@ void CConnectDlg::SetServerList ( const CHostAddress& InetAddr, const CVector ( pCurListViewItem->data ( LVC_CLIENTS, Qt::UserRole ).toInt() ); + + if ( eFetchMode == CFM_UDP_REQUEST ) + { + // client list not yet received - switch to TCP mode + eFetchMode = CFM_TCP; + pCurListViewItem->setData ( LVC_CLIENTS, Qt::UserRole, eFetchMode ); // remember for future fetches + + emit CreateCLServerListReqConnClientsListMes ( InetAddr, PROTO_TCP_ONCE ); // close TCP connection after receiving reply + } + } + } + break; + + default: + break; + } +} + void CConnectDlg::SetConnClientsList ( const CHostAddress& InetAddr, const CVector& vecChanInfo ) { // find the server with the correct address @@ -551,6 +596,16 @@ void CConnectDlg::SetConnClientsList ( const CHostAddress& InetAddr, const CVect if ( pCurListViewItem ) { + // find the current fetch mode for the client list for this server + enum EClientFetchMode eFetchMode = static_cast ( pCurListViewItem->data ( LVC_CLIENTS, Qt::UserRole ).toInt() ); + + if ( eFetchMode != CFM_TCP ) + { + // not switched to TCP mode - set to UDP for successful fetch + eFetchMode = CFM_UDP_RESULT; + pCurListViewItem->setData ( LVC_CLIENTS, Qt::UserRole, eFetchMode ); + } + // first remove any existing children DeleteAllListViewItemChilds ( pCurListViewItem ); @@ -981,7 +1036,16 @@ void CConnectDlg::SetPingTimeAndNumClientsResult ( const CHostAddress& InetAddr, // connected clients, if not then request the client names if ( iNumClients != pCurListViewItem->childCount() ) { - emit CreateCLServerListReqConnClientsListMes ( InetAddr ); + // find the current fetch mode for the client list for this server + enum EClientFetchMode eFetchMode = static_cast ( pCurListViewItem->data ( LVC_CLIENTS, Qt::UserRole ).toInt() ); + + if ( eFetchMode != CFM_TCP ) + { + // not switched to TCP mode - reset for next UDP fetch + eFetchMode = CFM_UDP_REQUEST; + pCurListViewItem->setData ( LVC_CLIENTS, Qt::UserRole, eFetchMode ); + } + emit CreateCLServerListReqConnClientsListMes ( InetAddr, eFetchMode == CFM_TCP ? PROTO_TCP_ONCE : PROTO_UDP ); } // this is the first time a ping time was received, set item to visible diff --git a/src/connectdlg.h b/src/connectdlg.h index 435169edf7..7ffcdcd5bd 100644 --- a/src/connectdlg.h +++ b/src/connectdlg.h @@ -69,6 +69,8 @@ class CConnectDlg : public CBaseDlg, private Ui_CConnectDlgBase void SetServerList ( const CHostAddress& InetAddr, const CVector& vecServerInfo, const bool bIsReducedServerList = false ); + void SetTcpSupported ( const CHostAddress& InetAddr, int iID ); + void SetConnClientsList ( const CHostAddress& InetAddr, const CVector& vecChanInfo ); void SetPingTimeAndNumClientsResult ( const CHostAddress& InetAddr, const int iPingTime, const int iNumClients ); @@ -93,6 +95,15 @@ class CConnectDlg : public CBaseDlg, private Ui_CConnectDlgBase }; protected: + // UDP/TCP mode for fetching client list - stored in data field for LVC_CLIENTS column + enum EClientFetchMode + { + CFM_UDP_REQUEST, // set when sending request by UDP + CFM_UDP_RESULT, // set when received a client list by UDP + CFM_TCP, // set when "TCP Supported" message arrives but client list has not arrived - + // re-request using TCP and remain in TCP mode + }; + virtual void showEvent ( QShowEvent* ); virtual void hideEvent ( QHideEvent* ); @@ -135,8 +146,8 @@ public slots: void OnCurrentServerItemChanged ( QTreeWidgetItem* current, QTreeWidgetItem* previous ); signals: - void ReqServerListQuery ( CHostAddress InetAddr ); + void ReqServerListQuery ( CHostAddress InetAddr, enum EProtoMode eProtoMode ); void CreateCLServerListPingMes ( CHostAddress InetAddr ); void CreateCLServerListReqVerAndOSMes ( CHostAddress InetAddr ); - void CreateCLServerListReqConnClientsListMes ( CHostAddress InetAddr ); + void CreateCLServerListReqConnClientsListMes ( CHostAddress InetAddr, enum EProtoMode eProtoMode ); }; diff --git a/src/main.cpp b/src/main.cpp index 98e5ab09ae..d71209b1f2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,6 +96,7 @@ int main ( int argc, char** argv ) bool bUseTranslation = true; bool bCustomPortNumberGiven = false; bool bEnableIPv6 = false; + bool bEnableTcp = false; int iNumServerChannels = DEFAULT_USED_NUM_CHANNELS; quint16 iPortNumber = DEFAULT_PORT_NUMBER; int iJsonRpcPortNumber = INVALID_PORT; @@ -251,6 +252,16 @@ int main ( int argc, char** argv ) // Server only: + // Enable TCP server --------------------------------------------------- + if ( GetFlagArgument ( argv, i, "--enabletcp", "--enabletcp" ) ) + { + bEnableTcp = true; + qInfo() << "- TCP server enabled"; + CommandLineOptions << "--enabletcp"; + ServerOnlyOptions << "--enabletcp"; + continue; + } + // Disconnect all clients on quit -------------------------------------- if ( GetFlagArgument ( argv, i, "-d", "--discononquit" ) ) { @@ -993,6 +1004,7 @@ int main ( int argc, char** argv ) bDisableRecording, bDelayPan, bEnableIPv6, + bEnableTcp, eLicenceType ); #ifndef NO_JSON_RPC @@ -1117,6 +1129,7 @@ QString UsageArguments ( char** argv ) " --norecord set server not to record by default when recording is configured\n" " -s, --server start Server\n" " --serverbindip IP address the Server will bind to (rather than all)\n" + " --enabletcp enable TCP server for Jamulus protocol\n" " -T, --multithreading use multithreading to make better use of\n" " multi-core CPUs and support more Clients\n" " -u, --numchannels maximum number of channels\n" diff --git a/src/protocol.cpp b/src/protocol.cpp index 47be8bc265..fa5b1fbd1e 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -360,6 +360,11 @@ CONNECTION LESS MESSAGES +--------------------+--------------+ +- PROTMESSID_CLM_EMPTY_MESSAGE: Empty message (No-op) + + note: does not have any data -> n = 0 + + - PROTMESSID_CLM_DISCONNECTION: Disconnect message note: does not have any data -> n = 0 @@ -432,6 +437,28 @@ CONNECTION LESS MESSAGES five times for one registration request at 500ms intervals. Beyond this, it should "ping" every 15 minutes (standard re-registration timeout). + + +- PROTMESSID_CLM_TCP_SUPPORTED: TCP supported message + + +-------------------------------------------------------+ + | 2 bytes ID of message to be potentially sent over TCP | + +-------------------------------------------------------+ + + the ID indicates which type of message relates to it: + - PROTMESSID_CLM_SERVER_LIST + - PROTMESSID_CLM_CONN_CLIENTS_LIST + - PROTMESSID_CLM_CLIENT_ID + + +- PROTMESSID_CLM_CLIENT_ID: Sends the client's channel ID back to the server + + +---------------------------------+ + | 1 byte channel ID of the client | + +---------------------------------+ + + the ID informs the server with which channel to associate the TCP connection + */ #include "protocol.h" @@ -605,7 +632,11 @@ void CProtocol::CreateAndImmSendAcknMess ( const int& iID, const int& iCnt ) emit MessReadyForSending ( vecAcknMessage ); } -void CProtocol::CreateAndImmSendConLessMessage ( const int iID, const CVector& vecData, const CHostAddress& InetAddr ) +void CProtocol::CreateAndImmSendConLessMessage ( const int iID, + const CVector& vecData, + const CHostAddress& InetAddr, + CTcpConnection* pTcpConnection, + enum EProtoMode eProtoMode ) { CVector vecNewMessage; @@ -614,7 +645,7 @@ void CProtocol::CreateAndImmSendConLessMessage ( const int iID, const CVector& vecbyMesBodyData, const int iRecCounter, const int iRecID ) @@ -838,7 +869,10 @@ void CProtocol::ParseMessageBody ( const CVector& vecbyMesBodyData, con } } -void CProtocol::ParseConnectionLessMessageBody ( const CVector& vecbyMesBodyData, const int iRecID, const CHostAddress& InetAddr ) +void CProtocol::ParseConnectionLessMessageBody ( const CVector& vecbyMesBodyData, + const int iRecID, + const CHostAddress& InetAddr, + CTcpConnection* pTcpConnection ) { //### TEST: BEGIN ###// // Test channel implementation: randomly delete protocol messages (50 % loss) @@ -870,7 +904,7 @@ void CProtocol::ParseConnectionLessMessageBody ( const CVector& vecbyMe break; case PROTMESSID_CLM_REQ_SERVER_LIST: - EvaluateCLReqServerListMes ( InetAddr ); + EvaluateCLReqServerListMes ( InetAddr, pTcpConnection ); break; case PROTMESSID_CLM_SEND_EMPTY_MESSAGE: @@ -902,11 +936,11 @@ void CProtocol::ParseConnectionLessMessageBody ( const CVector& vecbyMe break; case PROTMESSID_CLM_CONN_CLIENTS_LIST: - EvaluateCLConnClientsListMes ( InetAddr, vecbyMesBodyData ); + EvaluateCLConnClientsListMes ( InetAddr, vecbyMesBodyData, pTcpConnection ); break; case PROTMESSID_CLM_REQ_CONN_CLIENTS_LIST: - EvaluateCLReqConnClientsListMes ( InetAddr ); + EvaluateCLReqConnClientsListMes ( InetAddr, pTcpConnection ); break; case PROTMESSID_CLM_CHANNEL_LEVEL_LIST: @@ -916,6 +950,14 @@ void CProtocol::ParseConnectionLessMessageBody ( const CVector& vecbyMe case PROTMESSID_CLM_REGISTER_SERVER_RESP: EvaluateCLRegisterServerResp ( InetAddr, vecbyMesBodyData ); break; + + case PROTMESSID_CLM_TCP_SUPPORTED: + EvaluateCLTcpSupportedMes ( InetAddr, vecbyMesBodyData ); + break; + + case PROTMESSID_CLM_CLIENT_ID: + EvaluateCLClientIDMes ( InetAddr, vecbyMesBodyData, pTcpConnection ); + break; } } @@ -2005,7 +2047,7 @@ bool CProtocol::EvaluateCLUnregisterServerMes ( const CHostAddress& InetAddr ) return false; // no error } -void CProtocol::CreateCLServerListMes ( const CHostAddress& InetAddr, const CVector vecServerInfo ) +void CProtocol::CreateCLServerListMes ( const CHostAddress& InetAddr, const CVector vecServerInfo, CTcpConnection* pTcpConnection ) { const int iNumServers = vecServerInfo.Size(); @@ -2060,7 +2102,7 @@ void CProtocol::CreateCLServerListMes ( const CHostAddress& InetAddr, const CVec PutStringUTF8OnStream ( vecData, iPos, strUTF8City ); } - CreateAndImmSendConLessMessage ( PROTMESSID_CLM_SERVER_LIST, vecData, InetAddr ); + CreateAndImmSendConLessMessage ( PROTMESSID_CLM_SERVER_LIST, vecData, InetAddr, pTcpConnection ); } bool CProtocol::EvaluateCLServerListMes ( const CHostAddress& InetAddr, const CVector& vecData ) @@ -2220,15 +2262,15 @@ bool CProtocol::EvaluateCLRedServerListMes ( const CHostAddress& InetAddr, const return false; // no error } -void CProtocol::CreateCLReqServerListMes ( const CHostAddress& InetAddr ) +void CProtocol::CreateCLReqServerListMes ( const CHostAddress& InetAddr, enum EProtoMode eProtoMode ) { - CreateAndImmSendConLessMessage ( PROTMESSID_CLM_REQ_SERVER_LIST, CVector ( 0 ), InetAddr ); + CreateAndImmSendConLessMessage ( PROTMESSID_CLM_REQ_SERVER_LIST, CVector ( 0 ), InetAddr, nullptr, eProtoMode ); } -bool CProtocol::EvaluateCLReqServerListMes ( const CHostAddress& InetAddr ) +bool CProtocol::EvaluateCLReqServerListMes ( const CHostAddress& InetAddr, CTcpConnection* pTcpConnection ) { // invoke message action - emit CLReqServerList ( InetAddr ); + emit CLReqServerList ( InetAddr, pTcpConnection ); return false; // no error } @@ -2271,11 +2313,11 @@ bool CProtocol::EvaluateCLSendEmptyMesMes ( const CVector& vecData ) return false; // no error } -void CProtocol::CreateCLEmptyMes ( const CHostAddress& InetAddr ) +void CProtocol::CreateCLEmptyMes ( const CHostAddress& InetAddr, CTcpConnection* pTcpConnection ) { // special message: for this message there exist no Evaluate // function - CreateAndImmSendConLessMessage ( PROTMESSID_CLM_EMPTY_MESSAGE, CVector ( 0 ), InetAddr ); + CreateAndImmSendConLessMessage ( PROTMESSID_CLM_EMPTY_MESSAGE, CVector ( 0 ), InetAddr, pTcpConnection ); } void CProtocol::CreateCLDisconnection ( const CHostAddress& InetAddr ) @@ -2363,7 +2405,7 @@ bool CProtocol::EvaluateCLReqVersionAndOSMes ( const CHostAddress& InetAddr ) return false; // no error } -void CProtocol::CreateCLConnClientsListMes ( const CHostAddress& InetAddr, const CVector& vecChanInfo ) +void CProtocol::CreateCLConnClientsListMes ( const CHostAddress& InetAddr, const CVector& vecChanInfo, CTcpConnection* pTcpConnection ) { const int iNumClients = vecChanInfo.Size(); @@ -2411,10 +2453,10 @@ void CProtocol::CreateCLConnClientsListMes ( const CHostAddress& InetAddr, const PutStringUTF8OnStream ( vecData, iPos, strUTF8City ); } - CreateAndImmSendConLessMessage ( PROTMESSID_CLM_CONN_CLIENTS_LIST, vecData, InetAddr ); + CreateAndImmSendConLessMessage ( PROTMESSID_CLM_CONN_CLIENTS_LIST, vecData, InetAddr, pTcpConnection ); } -bool CProtocol::EvaluateCLConnClientsListMes ( const CHostAddress& InetAddr, const CVector& vecData ) +bool CProtocol::EvaluateCLConnClientsListMes ( const CHostAddress& InetAddr, const CVector& vecData, CTcpConnection* pTcpConnection ) { int iPos = 0; // init position pointer const int iDataLen = vecData.Size(); @@ -2468,20 +2510,20 @@ bool CProtocol::EvaluateCLConnClientsListMes ( const CHostAddress& InetAddr, con } // invoke message action - emit CLConnClientsListMesReceived ( InetAddr, vecChanInfo ); + emit CLConnClientsListMesReceived ( InetAddr, vecChanInfo, pTcpConnection ); return false; // no error } -void CProtocol::CreateCLReqConnClientsListMes ( const CHostAddress& InetAddr ) +void CProtocol::CreateCLReqConnClientsListMes ( const CHostAddress& InetAddr, enum EProtoMode eProtoMode ) { - CreateAndImmSendConLessMessage ( PROTMESSID_CLM_REQ_CONN_CLIENTS_LIST, CVector ( 0 ), InetAddr ); + CreateAndImmSendConLessMessage ( PROTMESSID_CLM_REQ_CONN_CLIENTS_LIST, CVector ( 0 ), InetAddr, nullptr, eProtoMode ); } -bool CProtocol::EvaluateCLReqConnClientsListMes ( const CHostAddress& InetAddr ) +bool CProtocol::EvaluateCLReqConnClientsListMes ( const CHostAddress& InetAddr, CTcpConnection* pTcpConnection ) { // invoke message action - emit CLReqConnClientsList ( InetAddr ); + emit CLReqConnClientsList ( InetAddr, pTcpConnection ); return false; // no error } @@ -2579,9 +2621,80 @@ bool CProtocol::EvaluateCLRegisterServerResp ( const CHostAddress& InetAddr, con return false; // no error } +void CProtocol::CreateCLTcpSupportedMes ( const CHostAddress& InetAddr, const int iID ) +{ + int iPos = 0; // init position pointer + + // build data vector (2 bytes long) + CVector vecData ( 2 ); + + // message ID just sent (2 bytes) + PutValOnStream ( vecData, iPos, static_cast ( iID ), 2 ); + + CreateAndImmSendConLessMessage ( PROTMESSID_CLM_TCP_SUPPORTED, vecData, InetAddr ); +} + +bool CProtocol::EvaluateCLTcpSupportedMes ( const CHostAddress& InetAddr, const CVector& vecData ) +{ + int iPos = 0; // init position pointer + + // check size + if ( vecData.Size() != 2 ) + { + return true; // return error code + } + + // invoke message action + emit CLTcpSupported ( InetAddr, static_cast ( GetValFromStream ( vecData, iPos, 2 ) ) ); + + return false; // no error +} + +void CProtocol::CreateCLClientIDMes ( const CHostAddress& InetAddr, const int iChanID, enum EProtoMode eProtoMode ) +{ + int iPos = 0; // init position pointer + + // build data vector (1 byte long) + CVector vecData ( 1 ); + + // channel ID (1 byte) + PutValOnStream ( vecData, iPos, static_cast ( iChanID ), 1 ); + + CreateAndImmSendConLessMessage ( PROTMESSID_CLM_CLIENT_ID, vecData, InetAddr, nullptr, eProtoMode ); +} + +bool CProtocol::EvaluateCLClientIDMes ( const CHostAddress& InetAddr, const CVector& vecData, CTcpConnection* pTcpConnection ) +{ + int iPos = 0; // init position pointer + + // check size + if ( vecData.Size() != 1 ) + { + return true; // return error code + } + + // channel ID + const int iCurID = static_cast ( GetValFromStream ( vecData, iPos, 1 ) ); + + // invoke message action + emit CLClientIDReceived ( InetAddr, iCurID, pTcpConnection ); + + return false; // no error +} + /******************************************************************************\ * Message generation and parsing * \******************************************************************************/ +int CProtocol::GetBodyLength ( const CVector& vecbyData ) +{ + int iCurPos = 5; // position of length calculation + + // 2 bytes length + const int iLenBy = static_cast ( GetValFromStream ( vecbyData, iCurPos, 2 ) ); + + return iLenBy + 2; // remaining length to read, including CRC +} + bool CProtocol::ParseMessageFrame ( const CVector& vecbyData, const int iNumBytesIn, CVector& vecbyMesBodyData, diff --git a/src/protocol.h b/src/protocol.h index b76e66607b..1ae3c6e32f 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -31,6 +31,7 @@ #include #include "global.h" #include "util.h" +#include "tcpconnection.h" /* Definitions ****************************************************************/ // protocol message IDs @@ -83,6 +84,8 @@ #define PROTMESSID_CLM_REGISTER_SERVER_RESP 1016 // status of server registration request #define PROTMESSID_CLM_REGISTER_SERVER_EX 1017 // register server with extended information #define PROTMESSID_CLM_RED_SERVER_LIST 1018 // reduced server list +#define PROTMESSID_CLM_TCP_SUPPORTED 1019 // TCP is supported +#define PROTMESSID_CLM_CLIENT_ID 1020 // Client ID associated with TCP connection // special IDs #define PROTMESSID_SPECIAL_SPLIT_MESSAGE 2001 // a container for split messages @@ -98,6 +101,14 @@ #define MESS_SPLIT_PART_SIZE_BYTES 550 #define MAX_NUM_MESS_SPLIT_PARTS ( MAX_SIZE_BYTES_NETW_BUF / MESS_SPLIT_PART_SIZE_BYTES ) +/* Enum for protocol mode *****************************************************/ +enum EProtoMode +{ + PROTO_UDP, + PROTO_TCP_ONCE, + PROTO_TCP_LONG, +}; + /* Classes ********************************************************************/ class CProtocol : public QObject { @@ -141,18 +152,22 @@ class CProtocol : public QObject void CreateCLRegisterServerMes ( const CHostAddress& InetAddr, const CHostAddress& LInetAddr, const CServerCoreInfo& ServerInfo ); void CreateCLRegisterServerExMes ( const CHostAddress& InetAddr, const CHostAddress& LInetAddr, const CServerCoreInfo& ServerInfo ); void CreateCLUnregisterServerMes ( const CHostAddress& InetAddr ); - void CreateCLServerListMes ( const CHostAddress& InetAddr, const CVector vecServerInfo ); + void CreateCLServerListMes ( const CHostAddress& InetAddr, const CVector vecServerInfo, CTcpConnection* pTcpConnection ); void CreateCLRedServerListMes ( const CHostAddress& InetAddr, const CVector vecServerInfo ); - void CreateCLReqServerListMes ( const CHostAddress& InetAddr ); + void CreateCLReqServerListMes ( const CHostAddress& InetAddr, enum EProtoMode eProtoMode ); void CreateCLSendEmptyMesMes ( const CHostAddress& InetAddr, const CHostAddress& TargetInetAddr ); - void CreateCLEmptyMes ( const CHostAddress& InetAddr ); + void CreateCLEmptyMes ( const CHostAddress& InetAddr, CTcpConnection* pTcpConnection ); void CreateCLDisconnection ( const CHostAddress& InetAddr ); void CreateCLVersionAndOSMes ( const CHostAddress& InetAddr ); void CreateCLReqVersionAndOSMes ( const CHostAddress& InetAddr ); - void CreateCLConnClientsListMes ( const CHostAddress& InetAddr, const CVector& vecChanInfo ); - void CreateCLReqConnClientsListMes ( const CHostAddress& InetAddr ); + void CreateCLConnClientsListMes ( const CHostAddress& InetAddr, const CVector& vecChanInfo, CTcpConnection* pTcpConnection ); + void CreateCLReqConnClientsListMes ( const CHostAddress& InetAddr, enum EProtoMode eProtoMode ); void CreateCLChannelLevelListMes ( const CHostAddress& InetAddr, const CVector& vecLevelList, const int iNumClients ); void CreateCLRegisterServerResp ( const CHostAddress& InetAddr, const ESvrRegResult eResult ); + void CreateCLTcpSupportedMes ( const CHostAddress& InetAddr, const int iID ); + void CreateCLClientIDMes ( const CHostAddress& InetAddr, const int iChanID, enum EProtoMode eProtoMode ); + + static int GetBodyLength ( const CVector& vecbyData ); static bool ParseMessageFrame ( const CVector& vecbyData, const int iNumBytesIn, @@ -162,7 +177,10 @@ class CProtocol : public QObject void ParseMessageBody ( const CVector& vecbyMesBodyData, const int iRecCounter, const int iRecID ); - void ParseConnectionLessMessageBody ( const CVector& vecbyMesBodyData, const int iRecID, const CHostAddress& InetAddr ); + void ParseConnectionLessMessageBody ( const CVector& vecbyMesBodyData, + const int iRecID, + const CHostAddress& InetAddr, + CTcpConnection* pTcpConnection ); static bool IsConnectionLessMessageID ( const int iID ) { return ( iID >= 1000 ) && ( iID < 2000 ); } @@ -241,7 +259,11 @@ class CProtocol : public QObject void CreateAndSendMessage ( const int iID, const CVector& vecData ); - void CreateAndImmSendConLessMessage ( const int iID, const CVector& vecData, const CHostAddress& InetAddr ); + void CreateAndImmSendConLessMessage ( const int iID, + const CVector& vecData, + const CHostAddress& InetAddr, + CTcpConnection* pTcpConnection = nullptr, + enum EProtoMode eProtoMode = PROTO_UDP ); bool EvaluateJitBufMes ( const CVector& vecData ); bool EvaluateReqJitBufMes(); @@ -270,15 +292,17 @@ class CProtocol : public QObject bool EvaluateCLUnregisterServerMes ( const CHostAddress& InetAddr ); bool EvaluateCLServerListMes ( const CHostAddress& InetAddr, const CVector& vecData ); bool EvaluateCLRedServerListMes ( const CHostAddress& InetAddr, const CVector& vecData ); - bool EvaluateCLReqServerListMes ( const CHostAddress& InetAddr ); + bool EvaluateCLReqServerListMes ( const CHostAddress& InetAddr, CTcpConnection* pTcpConnection ); bool EvaluateCLSendEmptyMesMes ( const CVector& vecData ); bool EvaluateCLDisconnectionMes ( const CHostAddress& InetAddr ); bool EvaluateCLVersionAndOSMes ( const CHostAddress& InetAddr, const CVector& vecData ); bool EvaluateCLReqVersionAndOSMes ( const CHostAddress& InetAddr ); - bool EvaluateCLConnClientsListMes ( const CHostAddress& InetAddr, const CVector& vecData ); - bool EvaluateCLReqConnClientsListMes ( const CHostAddress& InetAddr ); + bool EvaluateCLConnClientsListMes ( const CHostAddress& InetAddr, const CVector& vecData, CTcpConnection* pTcpConnection ); + bool EvaluateCLReqConnClientsListMes ( const CHostAddress& InetAddr, CTcpConnection* pTcpConnection ); bool EvaluateCLChannelLevelListMes ( const CHostAddress& InetAddr, const CVector& vecData ); bool EvaluateCLRegisterServerResp ( const CHostAddress& InetAddr, const CVector& vecData ); + bool EvaluateCLTcpSupportedMes ( const CHostAddress& InetAddr, const CVector& vecData ); + bool EvaluateCLClientIDMes ( const CHostAddress& InetAddr, const CVector& vecData, CTcpConnection* pTcpConnection ); int iOldRecID; int iOldRecCnt; @@ -301,7 +325,7 @@ public slots: signals: // transmitting void MessReadyForSending ( CVector vecMessage ); - void CLMessReadyForSending ( CHostAddress InetAddr, CVector vecMessage ); + void CLMessReadyForSending ( CHostAddress InetAddr, CVector vecMessage, CTcpConnection* pTcpConnection, enum EProtoMode eProtoMode ); // receiving void ChangeJittBufSize ( int iNewJitBufSize ); @@ -336,13 +360,15 @@ public slots: void CLUnregisterServerReceived ( CHostAddress InetAddr ); void CLServerListReceived ( CHostAddress InetAddr, CVector vecServerInfo ); void CLRedServerListReceived ( CHostAddress InetAddr, CVector vecServerInfo ); - void CLReqServerList ( CHostAddress InetAddr ); + void CLReqServerList ( CHostAddress InetAddr, CTcpConnection* pTcpConnection ); void CLSendEmptyMes ( CHostAddress TargetInetAddr ); void CLDisconnection ( CHostAddress InetAddr ); void CLVersionAndOSReceived ( CHostAddress InetAddr, COSUtil::EOpSystemType eOSType, QString strVersion ); void CLReqVersionAndOS ( CHostAddress InetAddr ); - void CLConnClientsListMesReceived ( CHostAddress InetAddr, CVector vecChanInfo ); - void CLReqConnClientsList ( CHostAddress InetAddr ); + void CLConnClientsListMesReceived ( CHostAddress InetAddr, CVector vecChanInfo, CTcpConnection* pTcpConnection ); + void CLReqConnClientsList ( CHostAddress InetAddr, CTcpConnection* pTcpConnection ); void CLChannelLevelListReceived ( CHostAddress InetAddr, CVector vecLevelList ); void CLRegisterServerResp ( CHostAddress InetAddr, ESvrRegResult eStatus ); + void CLTcpSupported ( CHostAddress InetAddr, int iID ); + void CLClientIDReceived ( CHostAddress InetAddr, int iChanID, CTcpConnection* pTcpConnection ); }; diff --git a/src/server.cpp b/src/server.cpp index a477771fde..36c5bf0aca 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -44,12 +44,14 @@ CServer::CServer ( const int iNewMaxNumChan, const bool bDisableRecording, const bool bNDelayPan, const bool bNEnableIPv6, + const bool bNEnableTcp, const ELicenceType eNLicenceType ) : bUseDoubleSystemFrameSize ( bNUseDoubleSystemFrameSize ), bUseMultithreading ( bNUseMultithreading ), iMaxNumChannels ( iNewMaxNumChan ), iCurNumChannels ( 0 ), Socket ( this, iPortNumber, iQosNumber, strServerBindIP, bNEnableIPv6 ), + TcpServer ( this, strServerBindIP, iPortNumber, bNEnableIPv6 ), Logging(), iFrameCount ( 0 ), bWriteStatusHTMLFile ( false ), @@ -63,12 +65,14 @@ CServer::CServer ( const int iNewMaxNumChan, strServerListFilter, iNewMaxNumChan, bNEnableIPv6, + bNEnableTcp, &ConnLessProtocol ), JamController ( this ), bDisableRecording ( bDisableRecording ), bAutoRunMinimized ( false ), bDelayPan ( bNDelayPan ), bEnableIPv6 ( bNEnableIPv6 ), + bEnableTcp ( bNEnableTcp ), eLicenceType ( eNLicenceType ), bDisconnectAllClientsOnQuit ( bNDisconnectAllClientsOnQuit ), pSignalHandler ( CSignalHandler::getSingletonP() ) @@ -278,6 +282,8 @@ CServer::CServer ( const int iNewMaxNumChan, QObject::connect ( &ConnLessProtocol, &CProtocol::CLReqConnClientsList, this, &CServer::OnCLReqConnClientsList ); + QObject::connect ( &ConnLessProtocol, &CProtocol::CLClientIDReceived, this, &CServer::OnCLClientIDReceived ); + QObject::connect ( &ServerListManager, &CServerListManager::SvrRegStatusChanged, this, &CServer::SvrRegStatusChanged ); QObject::connect ( &JamController, &recorder::CJamController::RestartRecorder, this, &CServer::RestartRecorder ); @@ -304,6 +310,10 @@ CServer::CServer ( const int iNewMaxNumChan, // start the socket (it is important to start the socket after all // initializations and connections) Socket.Start(); + if ( bEnableTcp ) + { + TcpServer.Start(); + } } template @@ -379,6 +389,12 @@ void CServer::OnNewConnection ( int iChID, int iTotChans, CHostAddress RecHostAd { QMutexLocker locker ( &Mutex ); + // if TCP is enabled, we need to announce this first, before sending Client ID + if ( bEnableTcp ) + { + ConnLessProtocol.CreateCLTcpSupportedMes ( vecChannels[iChID].GetAddress(), PROTMESSID_CLM_CLIENT_ID ); + } + // inform the client about its own ID at the server (note that this // must be the first message to be sent for a new connection) vecChannels[iChID].CreateClientIDMes ( iChID ); @@ -386,7 +402,7 @@ void CServer::OnNewConnection ( int iChID, int iTotChans, CHostAddress RecHostAd // Send an empty channel list in order to force clients to reset their // audio mixer state. This is required to trigger clients to re-send their // gain levels upon reconnecting after server restarts. - vecChannels[iChID].CreateConClientListMes ( CVector ( 0 ) ); + vecChannels[iChID].CreateConClientListMes ( CVector ( 0 ), ConnLessProtocol ); // query support for split messages in the client vecChannels[iChID].CreateReqSplitMessSupportMes(); @@ -461,11 +477,55 @@ void CServer::OnServerFull ( CHostAddress RecHostAddr ) ConnLessProtocol.CreateCLServerFullMes ( RecHostAddr ); } -void CServer::OnSendCLProtMessage ( CHostAddress InetAddr, CVector vecMessage ) +void CServer::OnSendCLProtMessage ( CHostAddress InetAddr, CVector vecMessage, CTcpConnection* pTcpConnection, enum EProtoMode eProtoMode ) { + if ( eProtoMode != PROTO_UDP ) + { + qWarning() << "Server send cannot use TCP client"; + return; + } + // the protocol queries me to call the function to send the message // send it through the network - Socket.SendPacket ( vecMessage, InetAddr ); + if ( pTcpConnection ) + { + // send to the connected socket directly + pTcpConnection->write ( (const char*) &( (CVector) vecMessage )[0], vecMessage.Size() ); + } + else + { + Socket.SendPacket ( vecMessage, InetAddr ); + } +} + +void CServer::OnCLClientIDReceived ( CHostAddress InetAddr, int iChanID, CTcpConnection* pTcpConnection ) +{ + qDebug() << "- client ID" << iChanID << "received from" << InetAddr.toString() << "with TCP connection" << pTcpConnection; + + if ( iChanID < 0 || iChanID >= iMaxNumChannels || !vecChannels[iChanID].IsConnected() ) + { + // ID out of range or channel not connected - reject connection + pTcpConnection->disconnectFromHost(); + qDebug() << "- rejected invalid client ID"; + return; + } + + CChannel* pChannel = &vecChannels[iChanID]; + + qDebug() << "- request to link TCP connection with UDP client at" << pChannel->GetAddress().toString(); + + // compare IP addresses, but not port numbers + if ( InetAddr.InetAddr != pChannel->GetAddress().InetAddr ) + { + // IP address mismatch - reject connection + pTcpConnection->disconnectFromHost(); + qDebug() << "- rejected mismatched IP address"; + return; + } + + // link TCP connection with UDP channel + pTcpConnection->SetChannel ( pChannel ); + pChannel->SetTcpConnection ( pTcpConnection ); } void CServer::OnCLDisconnection ( CHostAddress InetAddr ) @@ -1211,7 +1271,7 @@ void CServer::CreateAndSendChanListForAllConChannels() if ( vecChannels[i].IsConnected() ) { // send message - vecChannels[i].CreateConClientListMes ( vecChanInfo ); + vecChannels[i].CreateConClientListMes ( vecChanInfo, ConnLessProtocol ); } } @@ -1228,7 +1288,7 @@ void CServer::CreateAndSendChanListForThisChan ( const int iCurChanID ) CVector vecChanInfo ( CreateChannelList() ); // now send connected channels list to the channel with the ID "iCurChanID" - vecChannels[iCurChanID].CreateConClientListMes ( vecChanInfo ); + vecChannels[iCurChanID].CreateConClientListMes ( vecChanInfo, ConnLessProtocol ); } void CServer::CreateAndSendChatTextForAllConChannels ( const int iCurChanID, const QString& strChatText ) @@ -1422,12 +1482,12 @@ void CServer::DumpChannels ( const QString& title ) } } -void CServer::OnProtocolCLMessageReceived ( int iRecID, CVector vecbyMesBodyData, CHostAddress RecHostAddr ) +void CServer::OnProtocolCLMessageReceived ( int iRecID, CVector vecbyMesBodyData, CHostAddress RecHostAddr, CTcpConnection* pTcpConnection ) { QMutexLocker locker ( &Mutex ); // connection less messages are always processed - ConnLessProtocol.ParseConnectionLessMessageBody ( vecbyMesBodyData, iRecID, RecHostAddr ); + ConnLessProtocol.ParseConnectionLessMessageBody ( vecbyMesBodyData, iRecID, RecHostAddr, pTcpConnection ); } void CServer::OnProtocolMessageReceived ( int iRecCounter, int iRecID, CVector vecbyMesBodyData, CHostAddress RecHostAddr ) diff --git a/src/server.h b/src/server.h index cbdbb04373..6cac51a031 100644 --- a/src/server.h +++ b/src/server.h @@ -42,6 +42,8 @@ #include "util.h" #include "serverlogging.h" #include "serverlist.h" +#include "tcpserver.h" +#include "tcpconnection.h" #include "recorder/jamcontroller.h" #include "threadpool.h" @@ -104,6 +106,7 @@ class CServer : public QObject, public CServerSlots const bool bDisableRecording, const bool bNDelayPan, const bool bNEnableIPv6, + const bool bNEnableTcp, const ELicenceType eNLicenceType ); virtual ~CServer(); @@ -270,6 +273,7 @@ class CServer : public QObject, public CServerSlots // actual working objects CHighPrioSocket Socket; + CTcpServer TcpServer; // logging CServerLogging Logging; @@ -299,6 +303,9 @@ class CServer : public QObject, public CServerSlots // enable IPv6 bool bEnableIPv6; + // enable TCP Server + bool bEnableTcp; + // messaging QString strWelcomeMessage; ELicenceType eLicenceType; @@ -334,9 +341,9 @@ public slots: void OnServerFull ( CHostAddress RecHostAddr ); - void OnSendCLProtMessage ( CHostAddress InetAddr, CVector vecMessage ); + void OnSendCLProtMessage ( CHostAddress InetAddr, CVector vecMessage, CTcpConnection* pTcpConnection, enum EProtoMode eProtoMode ); - void OnProtocolCLMessageReceived ( int iRecID, CVector vecbyMesBodyData, CHostAddress RecHostAddr ); + void OnProtocolCLMessageReceived ( int iRecID, CVector vecbyMesBodyData, CHostAddress RecHostAddr, CTcpConnection* pTcpConnection ); void OnProtocolMessageReceived ( int iRecCounter, int iRecID, CVector vecbyMesBodyData, CHostAddress RecHostAddr ); @@ -352,15 +359,24 @@ public slots: // only send empty message if not a directory if ( !ServerListManager.IsDirectory() ) { - ConnLessProtocol.CreateCLEmptyMes ( TargetInetAddr ); + ConnLessProtocol.CreateCLEmptyMes ( TargetInetAddr, nullptr ); } } - void OnCLReqServerList ( CHostAddress InetAddr ) { ServerListManager.RetrieveAll ( InetAddr ); } + void OnCLReqServerList ( CHostAddress InetAddr, CTcpConnection* pTcpConnection ) { ServerListManager.RetrieveAll ( InetAddr, pTcpConnection ); } void OnCLReqVersionAndOS ( CHostAddress InetAddr ) { ConnLessProtocol.CreateCLVersionAndOSMes ( InetAddr ); } - void OnCLReqConnClientsList ( CHostAddress InetAddr ) { ConnLessProtocol.CreateCLConnClientsListMes ( InetAddr, CreateChannelList() ); } + void OnCLReqConnClientsList ( CHostAddress InetAddr, CTcpConnection* pTcpConnection ) + { + ConnLessProtocol.CreateCLConnClientsListMes ( InetAddr, CreateChannelList(), pTcpConnection ); + + // if TCP is enabled but this request is on UDP, say TCP is supported + if ( bEnableTcp && !pTcpConnection ) + { + ConnLessProtocol.CreateCLTcpSupportedMes ( InetAddr, PROTMESSID_CLM_CONN_CLIENTS_LIST ); + } + } void OnCLRegisterServerReceived ( CHostAddress InetAddr, CHostAddress LInetAddr, CServerCoreInfo ServerInfo ) { @@ -382,6 +398,8 @@ public slots: void OnCLDisconnection ( CHostAddress InetAddr ); + void OnCLClientIDReceived ( CHostAddress InetAddr, int iChanID, CTcpConnection* pTcpConnection ); + void OnAboutToQuit(); void OnHandledSignal ( int sigNum ); diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 4dabe4c7ef..334d003210 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -128,9 +128,11 @@ CServerListManager::CServerListManager ( const quint16 iNPortNum, const QString& strServerPublicIP, const int iNumChannels, const bool bNEnableIPv6, + const bool bNEnableTcp, CProtocol* pNConLProt ) : DirectoryType ( AT_NONE ), bEnableIPv6 ( bNEnableIPv6 ), + bEnableTcp ( bNEnableTcp ), ServerListFileName ( strServerListFileName ), strDirectoryAddress ( "" ), bIsDirectory ( false ), @@ -521,7 +523,7 @@ void CServerListManager::OnTimerPingServerInList() for ( int iIdx = 1; iIdx < iCurServerListSize; iIdx++ ) { // send empty message to keep NAT port open at registered server - pConnLessProtocol->CreateCLEmptyMes ( ServerList[iIdx].HostAddr ); + pConnLessProtocol->CreateCLEmptyMes ( ServerList[iIdx].HostAddr, nullptr ); } } @@ -663,7 +665,7 @@ void CServerListManager::Remove ( const CHostAddress& InetAddr ) and allow the client connect dialogue instead to use the IP and Port from which the list was received. */ -void CServerListManager::RetrieveAll ( const CHostAddress& InetAddr ) +void CServerListManager::RetrieveAll ( const CHostAddress& InetAddr, CTcpConnection* pTcpConnection ) { QMutexLocker locker ( &Mutex ); @@ -709,7 +711,9 @@ void CServerListManager::RetrieveAll ( const CHostAddress& InetAddr ) } // do not send a "ping" to a server local to the directory (no need) - if ( !serverIsInternal ) + // also only do so if processing a request over UDP, not TCP, + // as the client will always try UDP before TCP. + if ( !serverIsInternal && !pTcpConnection ) { // create "send empty message" for all other registered servers // this causes the server (vecServerInfo[iIdx].HostAddr) @@ -722,8 +726,18 @@ void CServerListManager::RetrieveAll ( const CHostAddress& InetAddr ) // send the server list to the client, since we do not know that the client // has a UDP fragmentation issue, we send both lists, the reduced and the // normal list after each other - pConnLessProtocol->CreateCLRedServerListMes ( InetAddr, vecServerInfo ); - pConnLessProtocol->CreateCLServerListMes ( InetAddr, vecServerInfo ); + if ( !pTcpConnection ) + { + // no need for reduced list if on TCP + pConnLessProtocol->CreateCLRedServerListMes ( InetAddr, vecServerInfo ); + } + pConnLessProtocol->CreateCLServerListMes ( InetAddr, vecServerInfo, pTcpConnection ); + + // if TCP is enabled but this request is on UDP, say TCP is supported + if ( bEnableTcp && !pTcpConnection ) + { + pConnLessProtocol->CreateCLTcpSupportedMes ( InetAddr, PROTMESSID_CLM_SERVER_LIST ); + } } } @@ -911,7 +925,7 @@ void CServerListManager::OnTimerPingServers() { // send empty message to directory to keep NAT port open -> we do // not require any answer from the directory - pConnLessProtocol->CreateCLEmptyMes ( DirectoryAddress ); + pConnLessProtocol->CreateCLEmptyMes ( DirectoryAddress, nullptr ); } } diff --git a/src/serverlist.h b/src/serverlist.h index cdf1d0f7e6..fe7012a461 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -141,6 +141,7 @@ class CServerListManager : public QObject const QString& strServerPublicIP, const int iNumChannels, const bool bNEnableIPv6, + const bool bNEnableTcp, CProtocol* pNConLProt ); void SetServerName ( const QString& strNewName ); @@ -168,7 +169,7 @@ class CServerListManager : public QObject void Append ( const CHostAddress& InetAddr, const CHostAddress& LInetAddr, const CServerCoreInfo& ServerInfo, const QString strVersion = "" ); void Remove ( const CHostAddress& InetAddr ); - void RetrieveAll ( const CHostAddress& InetAddr ); + void RetrieveAll ( const CHostAddress& InetAddr, CTcpConnection* pTcpConnection ); void StoreRegistrationResult ( ESvrRegResult eStatus ); @@ -192,6 +193,7 @@ class CServerListManager : public QObject EDirectoryType DirectoryType; bool bEnableIPv6; + bool bEnableTcp; CHostAddress ServerPublicIP; CHostAddress ServerPublicIP6; diff --git a/src/socket.h b/src/socket.h index 77fdf24452..2a9c6216a1 100644 --- a/src/socket.h +++ b/src/socket.h @@ -31,6 +31,7 @@ #include "global.h" #include "protocol.h" #include "util.h" +#include "tcpconnection.h" #ifndef _WIN32 # include # include @@ -103,7 +104,7 @@ class CSocket : public QObject void ProtocolMessageReceived ( int iRecCounter, int iRecID, CVector vecbyMesBodyData, CHostAddress HostAdr ); - void ProtocolCLMessageReceived ( int iRecID, CVector vecbyMesBodyData, CHostAddress HostAdr ); + void ProtocolCLMessageReceived ( int iRecID, CVector vecbyMesBodyData, CHostAddress HostAdr, CTcpConnection* pTcpConnection = nullptr ); }; /* Socket which runs in a separate high priority thread --------------------- */ diff --git a/src/tcpconnection.cpp b/src/tcpconnection.cpp new file mode 100644 index 0000000000..2663d96038 --- /dev/null +++ b/src/tcpconnection.cpp @@ -0,0 +1,210 @@ +/******************************************************************************\ + * Copyright (c) 2024-2026 + * + * Author(s): + * Tony Mountifield + * + ****************************************************************************** + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + \******************************************************************************/ + +#include "protocol.h" +#include "server.h" +#ifndef SERVER_ONLY +# include "client.h" +#endif +#include "channel.h" + +#ifndef SERVER_ONLY +// TCP connection used by client +CTcpConnection::CTcpConnection ( QTcpSocket* pTcpSocket, const CHostAddress& tcpAddress, CClient* pClient, CChannel* pChannel, bool bIsSession ) : + pTcpSocket ( pTcpSocket ), + tcpAddress ( tcpAddress ), + pServer ( nullptr ), + pChannel ( pChannel ), + bIsSession ( bIsSession ) +{ + vecbyRecBuf.Init ( MAX_SIZE_BYTES_NETW_BUF ); + iPos = 0; + iPayloadRemain = 0; + + connect ( pTcpSocket, &QTcpSocket::disconnected, this, &CTcpConnection::OnDisconnected ); + connect ( pTcpSocket, &QTcpSocket::readyRead, this, &CTcpConnection::OnReadyRead ); + + connect ( this, &CTcpConnection::ProtocolCLMessageReceived, pChannel, &CChannel::OnProtocolCLMessageReceived ); + + if ( bIsSession ) + { + // set up keepalive CLM_EMPTY_MESSAGE over TCP session connection + connect ( this, &CTcpConnection::CLSendEmptyMes, pClient, &CClient::OnCLSendEmptyMes ); + connect ( &TimerKeepalive, &QTimer::timeout, this, &CTcpConnection::OnTimerKeepalive ); + TimerKeepalive.start ( TCP_KEEPALIVE_INTERVAL_MS ); + } +} +#endif + +// TCP connection used by server +CTcpConnection::CTcpConnection ( QTcpSocket* pTcpSocket, const CHostAddress& tcpAddress, CServer* pServer ) : + pTcpSocket ( pTcpSocket ), + tcpAddress ( tcpAddress ), + pServer ( pServer ), + pChannel ( nullptr ), + bIsSession ( false ) +{ + vecbyRecBuf.Init ( MAX_SIZE_BYTES_NETW_BUF ); + iPos = 0; + iPayloadRemain = 0; + + connect ( pTcpSocket, &QTcpSocket::disconnected, this, &CTcpConnection::OnDisconnected ); + connect ( pTcpSocket, &QTcpSocket::readyRead, this, &CTcpConnection::OnReadyRead ); + + connect ( this, &CTcpConnection::ProtocolCLMessageReceived, pServer, &CServer::OnProtocolCLMessageReceived ); +} + +void CTcpConnection::OnDisconnected() +{ + qDebug() << "- Jamulus-TCP: disconnected from:" << tcpAddress.toString(); + TimerKeepalive.stop(); + pTcpSocket->deleteLater(); + if ( pChannel && pChannel->GetTcpConnection() == this ) + { + pChannel->SetTcpConnection ( nullptr ); // unlink from channel + } + deleteLater(); // delete this object in the next event loop +} + +void CTcpConnection::OnReadyRead() +{ + long iBytesAvail = pTcpSocket->bytesAvailable(); + + qDebug() << "- readyRead(), bytesAvailable() =" << iBytesAvail; + + while ( iBytesAvail > 0 ) + { + if ( iPos < MESS_HEADER_LENGTH_BYTE ) + { + // reading message header + long iNumBytesRead = pTcpSocket->read ( (char*) &vecbyRecBuf[iPos], MESS_HEADER_LENGTH_BYTE - iPos ); + if ( iNumBytesRead == -1 ) + { + return; + } + + qDebug() << "-- (hdr) iNumBytesRead =" << iNumBytesRead; + + iPos += iNumBytesRead; + iBytesAvail -= iNumBytesRead; + + if ( iPos >= MESS_HEADER_LENGTH_BYTE ) + { + // now have a complete header + iPayloadRemain = CProtocol::GetBodyLength ( vecbyRecBuf ); + + Q_ASSERT ( iPayloadRemain <= MAX_SIZE_BYTES_NETW_BUF - MESS_HEADER_LENGTH_BYTE ); + + iPayloadRemain -= iPos - MESS_HEADER_LENGTH_BYTE; + } + } + else + { + // reading message body + long iNumBytesRead = pTcpSocket->read ( (char*) &vecbyRecBuf[iPos], iPayloadRemain ); + if ( iNumBytesRead == -1 ) + { + return; + } + + qDebug() << "-- (body) iNumBytesRead =" << iNumBytesRead; + + iPos += iNumBytesRead; + iPayloadRemain -= iNumBytesRead; + iBytesAvail -= iNumBytesRead; + + Q_ASSERT ( iPayloadRemain >= 0 ); + + if ( iPayloadRemain == 0 ) + { + // have a complete payload + qDebug() << "- Jamulus-TCP: received protocol message of length" << iPos; + + // check if this is a protocol message + int iRecCounter; + int iRecID; + CVector vecbyMesBodyData; + + if ( !CProtocol::ParseMessageFrame ( vecbyRecBuf, iPos, vecbyMesBodyData, iRecCounter, iRecID ) ) + { + qDebug() << "- Jamulus-TCP: message parsed OK, ID =" << iRecID; + + // this is a protocol message, check the type of the message + if ( CProtocol::IsConnectionLessMessageID ( iRecID ) ) + { + //### TODO: BEGIN ###// + // a copy of the vector is used -> avoid malloc in real-time routine + emit ProtocolCLMessageReceived ( iRecID, vecbyMesBodyData, tcpAddress, this ); + //### TODO: END ###// + + // disconnect if it's not a client session connection + if ( !pServer && !bIsSession ) + { + pTcpSocket->disconnectFromHost(); + } + } + else + { + //### TODO: BEGIN ###// + // a copy of the vector is used -> avoid malloc in real-time routine + // emit ProtocolMessageReceived ( iRecCounter, iRecID, vecbyMesBodyData, pTcpConnection->tcpAddress, pTcpConnection ); + //### TODO: END ###// + } + } + else + { + qDebug() << "- Jamulus-TCP: failed to parse frame"; + } + + iPos = 0; // ready for next message, if any + } + } + } + + qDebug() << "- end of readyRead(), bytesAvailable() =" << pTcpSocket->bytesAvailable(); +} + +void CTcpConnection::OnTimerKeepalive() +{ + // qDebug() << "- Keepalive timer" << this << "to TCP" << tcpAddress.toString(); + emit CLSendEmptyMes ( tcpAddress, this ); +} + +qint64 CTcpConnection::write ( const char* data, qint64 maxSize ) +{ + if ( !pTcpSocket ) + { + return -1; + } + + return pTcpSocket->write ( data, maxSize ); +} + +void CTcpConnection::disconnectFromHost() +{ + if ( pTcpSocket ) + { + pTcpSocket->disconnectFromHost(); + } +} diff --git a/src/tcpconnection.h b/src/tcpconnection.h new file mode 100644 index 0000000000..a606e2233e --- /dev/null +++ b/src/tcpconnection.h @@ -0,0 +1,89 @@ +/******************************************************************************\ + * Copyright (c) 2024-2026 + * + * Author(s): + * Tony Mountifield + * + ****************************************************************************** + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * +\******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "global.h" +#include "util.h" + +// The header files channel.h and server.h require to include this header file +// so we get a cyclic dependency. To solve this issue, prototypes of the +// channel class and server class are defined here. +class CServer; // forward declaration of CServer +class CChannel; // forward declaration of CChannel + +#define TCP_CONNECT_TIMEOUT_MS 3000 +#define TCP_KEEPALIVE_INTERVAL_MS 15000 + +/* Classes ********************************************************************/ +class CTcpConnection : public QObject +{ + Q_OBJECT + +public: +#ifndef SERVER_ONLY + CTcpConnection ( QTcpSocket* pTcpSocket, const CHostAddress& tcpAddress, CClient* pClient, CChannel* pChannel, bool bIsSession ); +#endif + CTcpConnection ( QTcpSocket* pTcpSocket, const CHostAddress& tcpAddress, CServer* pServer ); + ~CTcpConnection() {} + + void SetChannel ( CChannel* pChan ) { pChannel = pChan; } + CChannel* GetChannel() { return pChannel; } + + qint64 write ( const char* data, qint64 maxSize ); + void disconnectFromHost(); + + bool IsSession() { return bIsSession; } + +private: + QTcpSocket* pTcpSocket; + CHostAddress tcpAddress; + + CServer* pServer; + CChannel* pChannel; + + const bool bIsSession; + + int iPos; + int iPayloadRemain; + CVector vecbyRecBuf; + + QTimer TimerKeepalive; + +signals: + void ProtocolCLMessageReceived ( int iRecID, CVector vecbyMesBodyData, CHostAddress HostAdr, CTcpConnection* pTcpConnection ); + void CLSendEmptyMes ( CHostAddress InetAddr, CTcpConnection* pTcpConnection ); + +private slots: + void OnDisconnected(); + void OnReadyRead(); + void OnTimerKeepalive(); +}; diff --git a/src/tcpserver.cpp b/src/tcpserver.cpp new file mode 100644 index 0000000000..9669337385 --- /dev/null +++ b/src/tcpserver.cpp @@ -0,0 +1,101 @@ +/******************************************************************************\ + * Copyright (c) 2024-2026 + * + * Author(s): + * Tony Mountifield + * + ****************************************************************************** + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + \******************************************************************************/ + +#include "tcpserver.h" + +#include "server.h" + +CTcpServer::CTcpServer ( CServer* pNServP, const QString& strServerBindIP, int iPort, bool bEnableIPv6 ) : + pServer ( pNServP ), + strServerBindIP ( strServerBindIP ), + iPort ( iPort ), + bEnableIPv6 ( bEnableIPv6 ), + pTcpServer ( new QTcpServer ( this ) ) +{ + connect ( pTcpServer, &QTcpServer::newConnection, this, &CTcpServer::OnNewConnection ); +} + +CTcpServer::~CTcpServer() +{ + if ( pTcpServer->isListening() ) + { + qInfo() << "- stopping Jamulus-TCP server"; + pTcpServer->close(); + } + pTcpServer->deleteLater(); +} + +bool CTcpServer::Start() +{ + if ( iPort < 0 ) + { + return false; + } + + // default to any-address for either both IP protocols or just IPv4 + QHostAddress hostAddress = bEnableIPv6 ? QHostAddress::Any : QHostAddress::AnyIPv4; + + if ( !bEnableIPv6 ) + { + if ( !strServerBindIP.isEmpty() ) + { + hostAddress = QHostAddress ( strServerBindIP ); + } + } + + if ( pTcpServer->listen ( hostAddress, iPort ) ) + { + qInfo() << qUtf8Printable ( + QString ( "- Jamulus-TCP: Server started on %1:%2" ).arg ( pTcpServer->serverAddress().toString() ).arg ( pTcpServer->serverPort() ) ); + return true; + } + qInfo() << "- Jamulus-TCP: Unable to start server:" << pTcpServer->errorString(); + return false; +} + +void CTcpServer::OnNewConnection() +{ + QTcpSocket* pSocket = pTcpServer->nextPendingConnection(); + if ( !pSocket ) + { + return; + } + + // express IPv4 address as IPv4 + CHostAddress peerAddress ( pSocket->peerAddress(), pSocket->peerPort() ); + + if ( peerAddress.InetAddr.protocol() == QAbstractSocket::IPv6Protocol ) + { + bool ok; + quint32 ip4 = peerAddress.InetAddr.toIPv4Address ( &ok ); + if ( ok ) + { + peerAddress.InetAddr.setAddress ( ip4 ); + } + } + + qDebug() << "- Jamulus-TCP: received connection from:" << peerAddress.toString(); + + new CTcpConnection ( pSocket, peerAddress, pServer ); // will auto-delete on disconnect +} diff --git a/src/tcpserver.h b/src/tcpserver.h new file mode 100644 index 0000000000..92329d691d --- /dev/null +++ b/src/tcpserver.h @@ -0,0 +1,64 @@ +/******************************************************************************\ + * Copyright (c) 2024-2026 + * + * Author(s): + * Tony Mountifield + * + ****************************************************************************** + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * +\******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "tcpconnection.h" + +#include "global.h" +#include "util.h" + +// The header file server.h requires to include this header file +// so we get a cyclic dependency. To solve this issue, a prototype of the +// server class is defined here. +class CServer; // forward declaration of CServer + +/* Classes ********************************************************************/ +class CTcpServer : public QObject +{ + Q_OBJECT + +public: + CTcpServer ( CServer* pNServP, const QString& strServerBindIP, int iPort, bool bEnableIPv6 ); + ~CTcpServer(); + + bool Start(); + +private: + CServer* pServer; // for server + const QString strServerBindIP; + const int iPort; + const bool bEnableIPv6; + QTcpServer* pTcpServer; + +private slots: + void OnNewConnection(); +}; diff --git a/src/testbench.h b/src/testbench.h index 3e5568ff9e..03d3dd2fd7 100644 --- a/src/testbench.h +++ b/src/testbench.h @@ -218,11 +218,11 @@ public slots: vecServerInfo[0].strCity = GenRandomString(); vecServerInfo[0].strName = GenRandomString(); - Protocol.CreateCLServerListMes ( CurHostAddress, vecServerInfo ); + Protocol.CreateCLServerListMes ( CurHostAddress, vecServerInfo, nullptr ); break; case 20: // PROTMESSID_CLM_REQ_SERVER_LIST - Protocol.CreateCLReqServerListMes ( CurHostAddress ); + Protocol.CreateCLReqServerListMes ( CurHostAddress, PROTO_UDP ); break; case 21: // PROTMESSID_CLM_SEND_EMPTY_MESSAGE @@ -230,7 +230,7 @@ public slots: break; case 22: // PROTMESSID_CLM_EMPTY_MESSAGE - Protocol.CreateCLEmptyMes ( CurHostAddress ); + Protocol.CreateCLEmptyMes ( CurHostAddress, nullptr ); break; case 23: // PROTMESSID_CLM_DISCONNECTION @@ -261,11 +261,11 @@ public slots: vecChanInfo[0].iChanID = GenRandomIntInRange ( -2, 20 ); vecChanInfo[0].strName = GenRandomString(); - Protocol.CreateCLConnClientsListMes ( CurHostAddress, vecChanInfo ); + Protocol.CreateCLConnClientsListMes ( CurHostAddress, vecChanInfo, nullptr ); break; case 29: // PROTMESSID_CLM_REQ_CONN_CLIENTS_LIST - Protocol.CreateCLReqConnClientsListMes ( CurHostAddress ); + Protocol.CreateCLReqConnClientsListMes ( CurHostAddress, PROTO_UDP ); break; case 30: // PROTMESSID_CLM_CHANNEL_LEVEL_LIST