/****************************************************************************
 *
 *  Copyright (C) 2000-2001 RealNetworks, Inc. All rights reserved.
 *
 *  This program is free software.  It may be distributed under the terms
 *  in the file LICENSE, found in the top level of the source distribution.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdarg.h>

#include "app.h"
#include "rtspproxy.h"
#include "tranhdr.h"

#include "dbg.h"

bool g_DebugFlagTurnedOn = false;
void OutputDebugInfo( const char* fmt, ... )
{
    if( !g_DebugFlagTurnedOn )
        return;
    char str[4096];
    va_list v;
    va_start( v, fmt );
    vsprintf( str, fmt, v );
    strcat( str, "\n" );
    printf( str );
}

/**************************************
 *
 * CClientCnx class
 *
 **************************************/

CClientCnx::CClientCnx( CRtspProxyCnx* pOwner, CTcpSocket* psock ) :
    m_state( stClosed ),
    m_pOwner( pOwner ),
    m_pprot( NULL ),
    m_pSock( psock )
{
    m_pprot = new CRtspProtocol( this );
    m_pprot->Init( psock );
    m_addrClient = psock->GetPeerAddr();
    if( m_addrClient.IsValid() )
    {
        m_pSock = psock;
        m_state = stConnected;
    }
    m_addrSelf = psock->GetLocalAddr();
}

CClientCnx::~CClientCnx( void )
{

}

void CClientCnx::Close( void )
{
    m_state = stClosed;
    if( m_pprot )
    {
        delete m_pprot;
        m_pprot = NULL;
    }
}

void CClientCnx::sendRequest( CRtspRequestMsg* pmsg )
{
    if( m_state == stClosed )
        return;
    m_pprot->SendRequest( pmsg );
}

void CClientCnx::sendResponse( CRtspResponseMsg* pmsg )
{
    if( m_state == stClosed )
        return;
    m_pprot->SendResponse( pmsg );
}

void CClientCnx::sendSetupResponse( CRtspResponseMsg* pmsg )
{
    if( m_state == stClosed )
        return;

    m_pprot->SendResponse( pmsg );
}

void CClientCnx::sendResponse( UINT code, UINT cseq )
{
    if( m_state == stClosed )
        return;

    CRtspResponseMsg msg;
    msg.SetStatus( code );
    if( cseq )
    {
        char buf[20];
        sprintf( buf, "%d", cseq );
        msg.SetHdr( "CSeq", buf );
    }
    sendResponse( &msg );
}

CRtspProtocol* CClientCnx::GetRtspProtocol( void ) const
{
    return m_pprot;
}

const CSockAddr& CClientCnx::GetClientAddr( void ) const
{
    return m_addrClient;
}

const CSockAddr& CClientCnx::GetSelfAddr( void ) const
{
    return m_addrSelf;
}

void CClientCnx::OnError( RtspErr err )
{
    if( m_state == stConnected )
    {
        m_state = stClosed;
        dbgout( "CClientCnx::OnError: err=%i", err );
        if( err == RTSPE_CLOSED )
        {
            m_pOwner->OnClientCnxClosed();
        }
    }
}

/*** Requests ***/

void CClientCnx::OnDescribeRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnAnnounceRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnGetParamRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnSetParamRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnOptionsRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnPauseRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnPlayRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnRecordRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnRedirectRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnSetupRequest( CRtspRequestMsg* pmsg )
{
    OutputDebugInfo( "Setup request for url: %s", pmsg->GetUrl() );
    m_pOwner->PassSetupRequestMsgToServer( pmsg );
}

void CClientCnx::OnTeardownRequest( CRtspRequestMsg* pmsg )
{
    OutputDebugInfo( "Teardown request" );
    m_pOwner->PassToServer( pmsg );
    
    CSessionHdr sessionHdr( pmsg->GetHdr( "Session" ) );
    m_pOwner->DeleteSessionByClientSessionID( sessionHdr.GetSessionID() );
}

void CClientCnx::OnExtensionRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

/*** Responses ***/

void CClientCnx::OnDescribeResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnAnnounceResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnGetParamResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnSetParamResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnOptionsResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnPauseResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnPlayResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnRecordResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnRedirectResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnSetupResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnTeardownResponse( CRtspResponseMsg* pmsg )
{
    OutputDebugInfo( "Teardown response" );
    m_pOwner->PassToServer( pmsg );
}

void CClientCnx::OnExtensionResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToServer( pmsg );
}

/**************************************
 *
 * CServerCnx class
 *
 **************************************/

CServerCnx::CServerCnx( CRtspProxyCnx* pOwner, CString strHost, UINT16 uPort ) :
    m_state( stClosed ),
    m_pOwner( pOwner ),
    m_pprot( NULL ),
    m_pSock( NULL ),
    m_strHost( strHost ),
    m_uPort( uPort )
{
        m_pResolver = CResolver::GetResolver(); 
}

CServerCnx::~CServerCnx( void )
{

}

void CServerCnx::Close( void )
{
    m_state = stClosed;

    if( m_pResolver )
    {
        //delete m_pResolver;
        m_pResolver = NULL;
    }

    if( m_pprot )
    {
        delete m_pprot; 
        m_pprot = NULL;
    }

    // we havn't passed it to m_pprot
    if( m_pSock )
    {
        delete m_pSock;
        m_pSock = NULL;
    }

    while( !m_RequestMsgQueue.IsEmpty() )
    {
        CRtspRequestMsg* pmsg = m_RequestMsgQueue.RemoveHead();
        delete pmsg;
    }
}

void CServerCnx::sendRequest( CRtspRequestMsg* pmsg )
{
    if( m_state == stClosed )
    {
        return;
    }
    m_pprot->SendRequest( pmsg );
}

void CServerCnx::sendResponse( CRtspResponseMsg* pmsg )
{
    if( m_state == stClosed )
        return;
    m_pprot->SendResponse( pmsg );
}

void CServerCnx::sendResponse( UINT code, UINT cseq )
{
    if( m_state == stClosed )
        return;

    CRtspResponseMsg msg;
    msg.SetStatus( code );
    if( cseq )
    {
        msg.SetHdr( "CSeq", cseq );
    }
    sendResponse( &msg );
}

CRtspProtocol* CServerCnx::GetRtspProtocol( void ) const
{
    return m_pprot;
}

const CSockAddr& CServerCnx::GetServerAddr( void ) const
{
    return m_addrServer;
}

const CString& CServerCnx::GetHostName( void ) const
{
    return m_strHost;
}

UINT16 CServerCnx::GetPort( void ) const
{
    return m_uPort;
}

void CServerCnx::AddRtspMsgToQueue( CRtspRequestMsg* pmsg )
{
    m_RequestMsgQueue.InsertTail(new CRtspRequestMsg(*pmsg));
}

void CServerCnx::ConnectToServer( CPCHAR szHost, UINT16 port )
{
    m_addr.SetPort( port );
    m_pResolver->GetHost( this, szHost );
}

void CServerCnx::OnError( RtspErr err )
{
    if( m_state == stConnected )
    {
        m_state = stClosed;
        dbgout( "CServerCnx::OnError: err=%i", err );
        if( err == RTSPE_CLOSED )
        {
            Close();
            m_pOwner->OnServerCnxClosed( this );
        }
    }
}

/*** Requests ***/

void CServerCnx::OnDescribeRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnAnnounceRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnGetParamRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnSetParamRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnOptionsRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnPauseRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnPlayRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnRecordRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnRedirectRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnSetupRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnTeardownRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->DeleteSessionByServerSessionID(pmsg->GetHdr("Session"), GetHostName() );
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnExtensionRequest( CRtspRequestMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

/*** Responses ***/

void CServerCnx::OnDescribeResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnAnnounceResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnGetParamResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnSetParamResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnOptionsResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnPauseResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnPlayResponse( CRtspResponseMsg* pmsg )
{
    OutputDebugInfo( "We are playing!!!" );
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnRecordResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnRedirectResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnSetupResponse( CRtspResponseMsg* pmsg )
{
    CString strSess =  pmsg->GetHdr( "Session" );
    CString strTrans = pmsg->GetHdr( "Transport" );

    OutputDebugInfo( "Setup response: server session = '%s', transport = '%s'", (CPCHAR)strSess, (CPCHAR)strTrans );
    m_pOwner->PassSetupResponseToClient(pmsg, this);
}

void CServerCnx::OnTeardownResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::OnExtensionResponse( CRtspResponseMsg* pmsg )
{
    m_pOwner->PassToClient( pmsg, this );
}

void CServerCnx::GetHostDone( int err, const CString& strQuery, in_addr addrResult )
{
    if( err )
    {
        SendClientConnectionError();
        return;
    }

    m_addr.SetHost( addrResult );
    m_pSock = new CTcpSocket( this );
    m_pSock->Connect( m_addr );
}

void CServerCnx::GetHostDone( int err, in_addr addrQuery, const CString& strResult )
{
    assert(false);
}

void CServerCnx::OnConnectDone( int err )
{
    if( err )
    {
        delete m_pSock;
        m_pSock = NULL;
        SendClientConnectionError();
        return;
    }

    m_pprot = new CRtspProtocol( this );
    m_pprot->Init( m_pSock );
    m_addrServer = m_pSock->GetPeerAddr();
    if( m_addrServer.IsValid() )
    {
        m_state = stConnected;
    }

    while( !m_RequestMsgQueue.IsEmpty() )
    {
        CRtspRequestMsg* pmsg = m_RequestMsgQueue.RemoveHead();
        sendRequest( pmsg);
        delete pmsg;
    }
    m_pSock = NULL;  //we don't own it any more
}

void CServerCnx::OnReadReady( void )
{
    assert(false);
}

void CServerCnx::OnWriteReady( void )
{
    assert(false);
}

void CServerCnx::OnExceptReady( void )
{
    assert(false);
}

void CServerCnx::OnClosed( void )
{
}

void CServerCnx::SendClientConnectionError( void )
{
    int cseq = 0;
    if( !m_RequestMsgQueue.IsEmpty() )
    {
        CRtspRequestMsg* pmsg = m_RequestMsgQueue.RemoveHead();
        cseq = atoi( pmsg->GetHdr( "CSeq" ) );
        delete pmsg;
    }
    m_pOwner->OnServerConnectionError( 502, cseq );

    Close();
}

/**************************************
 *
 * CSeqPair class
 *
 **************************************/
CCSeqPair::CCSeqPair( const CString & cseqToClient, const CString & cseqToServer, 
          const CString & strHostName, UINT16 uPort ) :
        m_cseqToClient( cseqToClient ),
        m_cseqToServer( cseqToServer ),
        m_strHost( strHostName ),
        m_uPort( uPort )
{
    // Empty
}

CCSeqPair::~CCSeqPair( void )
{
    // Empty
}


/**************************************
 *
 * CRtspProxyCnx class
 *
 **************************************/

CRtspProxyCnx::CRtspProxyCnx( CRtspProxyApp* pOwner, CTcpSocket* psock, CPCHAR viaHdrValue ) :
    m_pClientCnx( NULL ),
    m_clientChannel( 0 ),
    m_pOwner( pOwner ),
    m_viaHdrValue( viaHdrValue ),
    m_cseqToClient( 1 ),
    m_sessionIndex( 0 )
{
    m_pClientCnx = new CClientCnx( this, psock );
}

CRtspProxyCnx::~CRtspProxyCnx( void )
{
    // Empty
}

void CRtspProxyCnx::PassToClient( CRtspRequestMsg* pmsg, CServerCnx* pServerCnx )
{
    assert( pmsg );
    assert( m_pClientCnx );
    
    CSessionHdr sessionHdr( pmsg->GetHdr( "Session" ) );
    CString strServerSessionID = sessionHdr.GetSessionID();
    if( !strServerSessionID.IsEmpty() )
    {
        CString strClientSessionID = FindClientSessionID( strServerSessionID, pServerCnx->GetHostName() );
        sessionHdr.SetSessionID( strClientSessionID );
        pmsg->SetHdr( "Session", sessionHdr.GetSessionHdrString() );
    }

    if( !SetViaHdr( pmsg ) ) 
    {
        // if we got a loop
        m_pClientCnx->sendResponse( 502, atoi( pmsg->GetHdr( "CSeq" ) ) );
        return;
    }

    CString strCSeq = pmsg->GetHdr( "CSeq" );
    char buf[20];
    sprintf( buf, "%u", m_cseqToClient++ );

    CCSeqPair* pPair = new CCSeqPair( buf, strCSeq, pServerCnx->GetHostName(), pServerCnx->GetPort() );
    m_listCCSeqPairList.InsertTail( pPair );

    pmsg->SetHdr( "CSeq", buf );
    m_pClientCnx->sendRequest( pmsg );
 
}

void CRtspProxyCnx::PassToClient( CRtspResponseMsg* pmsg, CServerCnx* pServerCnx )
{
    assert( pmsg );
    assert( m_pClientCnx ); 
    
    CSessionHdr sessionHdr( pmsg->GetHdr( "Session" ) );
    CString strServerSessionID = sessionHdr.GetSessionID();
    if( !strServerSessionID.IsEmpty() )
    {
        CString strClientSessionID = FindClientSessionID( strServerSessionID, pServerCnx->GetHostName() );
        sessionHdr.SetSessionID( strClientSessionID );
        pmsg->SetHdr( "Session", sessionHdr.GetSessionHdrString() );
    }
    
    if( !SetViaHdr( pmsg ) ) 
    {
        // if we got a loop
        m_pClientCnx->sendResponse( 502, atoi( pmsg->GetHdr( "CSeq") ) ) ;
        return;
    }

    m_pClientCnx->sendResponse( pmsg );
}

void CRtspProxyCnx::PassToServer( CRtspRequestMsg* pmsg )
{
    assert( pmsg );
    CServerCnx* pServerCnx;
    CUrl url( pmsg->GetUrl() );
    CString strHost = url.GetHost();
    UINT16 uPort = url.GetPort();
    CSessionHdr sessionHdr( pmsg->GetHdr( "Session" ) );
    CString strClientSessionID = sessionHdr.GetSessionID();
    CString strProxyRequire = pmsg->GetHdr( "Proxy-Require" );
    
    if( !strClientSessionID.IsEmpty() )
    {
        CString strServerSessionID = FindServerSessionID( strClientSessionID );
        sessionHdr.SetSessionID( strServerSessionID );
        pmsg->SetHdr( "Session", sessionHdr.GetSessionHdrString() );
    }

    if( !url.IsValid() )
    {
        m_pClientCnx->sendResponse( 451, atoi( pmsg->GetHdr( "CSeq" ) ) );
        return;
    }

    if( !SetViaHdr( pmsg ) ) 
    {
        // if we got a loop
        m_pClientCnx->sendResponse( 502, atoi( pmsg->GetHdr( "CSeq") ) ) ;
        return;
    }

    if( !strProxyRequire.IsEmpty() )
    {
        // currently we don't support any Proxy-Require features
        CRtspResponseMsg msg;

        msg.SetStatus( 551 );
        msg.SetHdr( "CSeq", pmsg->GetHdr( "CSeq" ) );
        msg.SetHdr( "Unsupported", strProxyRequire );
        m_pClientCnx->sendResponse( &msg ) ;
        return;
    }

    pServerCnx = FindServerCnx( strHost, uPort );
    if( pServerCnx )
    {
        pServerCnx->sendRequest( pmsg) ;
    }
    else
    {
        pServerCnx = new CServerCnx( this, strHost, uPort );
        pServerCnx->AddRtspMsgToQueue( pmsg );
        pServerCnx->ConnectToServer( strHost, uPort );
 
        m_listServerCnx.InsertTail( pServerCnx );
    }
}

void CRtspProxyCnx::PassToServer( CRtspResponseMsg* pmsg )
{
    assert( pmsg );

    CSessionHdr sessionHdr( pmsg->GetHdr( "Session" ) );
    CString strClientSessionID = sessionHdr.GetSessionID();
    
    if( !strClientSessionID.IsEmpty() )
    {
        CString strServerSessionID = FindServerSessionID( strClientSessionID );
        sessionHdr.SetSessionID( strServerSessionID );
        pmsg->SetHdr( "Session", sessionHdr.GetSessionHdrString() );
    }
    
    if( !SetViaHdr( pmsg ) ) 
    {
        // if we got a loop
        m_pClientCnx->sendResponse( 502, atoi( pmsg->GetHdr( "CSeq") ) ) ;
        return;
    }
    
    CString strCSeq = pmsg->GetHdr( "CSeq" );
    CCseqPairList::Iterator itr( m_listCCSeqPairList.Begin() );
    while(itr)
    {
        CCSeqPair* pPair = *itr;
        if( pPair->m_cseqToClient == strCSeq )
        {
            CServerCnx* pServerCnx = FindServerCnx( pPair->m_strHost, pPair->m_uPort );
            if( pServerCnx )
            {
                pmsg->SetHdr( "CSeq", pPair->m_cseqToServer );
                pServerCnx->sendResponse( pmsg );
            }

            m_listCCSeqPairList.Remove( itr );
            delete pPair;
            return;
        }
        itr++;
    }
}

void CRtspProxyCnx::PassSetupRequestMsgToServer( CRtspRequestMsg* pmsg )
{
    assert( pmsg );
    UINT16  clientPort, proxyToServerPort;
    int     nPorts;
    bool    bOldSession = false;
    bool    bReuseTunnel = false;
    bool    bSessionID = false;
    CString strCSeq = pmsg->GetHdr( "CSeq" );
    CString strTransport = pmsg->GetHdr( "Transport" );
    CSessionHdr sessionHdr( pmsg->GetHdr( "Session" ) );
    CString strSessionID = sessionHdr.GetSessionID();
    CString strBlocksize = pmsg->GetHdr( "Blocksize" );
    CRequestTransportHdr rqtHdr( strTransport );

    CRtspProxySession* pSession = NULL;
    CProxyDataTunnel* pTunnel = NULL;

    rqtHdr.GetBasePort( &clientPort, &nPorts );
    bSessionID = !strSessionID.IsEmpty();

    // if it is tcp interleaved, we will delay the build up of the tunnel until 
    // we get setup response
    if(!rqtHdr.IsInterleaved() && nPorts != 0)
    {
        CRtspProxySessionList::Iterator itr( m_listRtspProxySession.Begin() );
        while( itr )
        {
            pSession = *itr;
            if( bSessionID && !strcmp( pSession->GetClientSessionID(), strSessionID ) )
            { 
                bOldSession = true;
                break;
            }
            itr++;
        }

        itr = m_listRtspProxySession.Begin();
        while( itr )
        {
           pTunnel = ( *itr )->FindTunnelByClientPort( clientPort );
            if( pTunnel )
            {
                bReuseTunnel = true;
                break;
            }
            itr++;
        }
        
        if( !bReuseTunnel )
        {
            pTunnel = new CProxyDataTunnel;
            if(!pTunnel->Init( nPorts ) )
            {
                // we are running out of file descriptor
                m_pClientCnx->sendResponse( 503, atoi( pmsg->GetHdr( "CSeq" ) ) );
                dbgout( " Tunnel initialization failed" );
                delete pTunnel;
                return;
            }
            pTunnel->SetClientPort( clientPort );
        }

        if( !bOldSession )
        {
            pSession = new CRtspProxySession;
            pSession->SetSetupCSeq( strCSeq );
            m_listRtspProxySession.InsertTail( pSession );
        }
 
        // in case of oldSession and resueTunnel, we do nothing, 
        // otherwise we link the session and the tunnel
        if( !( bOldSession && bReuseTunnel ) )
        {
            pTunnel->AddRef();
            pSession->AddTunnel( pTunnel );
        }

        proxyToServerPort = pTunnel->GetProxyToServerPort();
        rqtHdr.SetPort( proxyToServerPort );
        pmsg->SetHdr( "Transport", rqtHdr.GetHdrString() );

        //we want to set the udp packet size to MAX_UDP_LEN
        int nBlocksize = MAX_UDP_LEN - 0x80;//exclude ip, udp and rtp headers.
        char buf[20];
        if( !strBlocksize.IsEmpty() )
        {
            int nClientBlocksize = atoi( strBlocksize );
            if( nBlocksize > nClientBlocksize )
            {
                nBlocksize = nClientBlocksize;
            }
        }
        sprintf( buf, "%d", nBlocksize );
        pmsg->SetHdr( "Blocksize", buf );
    }
    PassToServer( pmsg );
}

void CRtspProxyCnx::PassSetupResponseToClient( CRtspResponseMsg* pmsg, CServerCnx* pServerCnx )
{
    UINT16 serverPort, proxyToServerPort;
    int    nPorts;
    bool   bOldSession = false;
    CString strTran = pmsg->GetHdr( "Transport" );
    CString strCSeq = pmsg->GetHdr( "CSeq" );
    CSessionHdr sessionHdr( pmsg->GetHdr( "Session" ) );
    CString strSessionID = sessionHdr.GetSessionID();
    CSingleTransportHdr rtHdr( strTran );
    bool bInterleaved = rtHdr.IsInterleaved();

    CRtspProxySession* pSession = NULL;
    CProxyDataTunnel* pTunnel = NULL;

    rtHdr.GetServerBasePort( &serverPort, &nPorts );
    rtHdr.GetClientBasePort( &proxyToServerPort, &nPorts );

    if(nPorts != 0)
    {
        CRtspProxySessionList::Iterator itrs( m_listRtspProxySession.Begin() );
        while( itrs )
        {
            pSession = *itrs;
            if( pSession->GetServerSessionID() == strSessionID &&
                pSession->GetHost() == pServerCnx->GetHostName() )
            {
                bOldSession = true;
                pTunnel = pSession->FindTunnelByProxyPort( proxyToServerPort );
                break;
            }

            if(!strcmp( pSession->GetSetupCSeq(), strCSeq ) )
            {
                pSession->SetSessionID( strSessionID, pServerCnx->GetHostName(), m_sessionIndex++ );
                pTunnel = pSession->FindTunnelByProxyPort( proxyToServerPort );
                break;
            }

            itrs++;
        }

        if( bInterleaved && !bOldSession )
        {
            pSession = new CRtspProxySession;
            pSession->SetSetupCSeq( strCSeq );
            pSession->SetSessionID( strSessionID, pServerCnx->GetHostName(), m_sessionIndex++ );
            m_listRtspProxySession.InsertTail( pSession );
        }

        // we create the interleaved tunnel in setup response
        if(bInterleaved)
        {
            pTunnel = new CProxyDataTunnel;
            pTunnel->Init( m_pClientCnx->GetRtspProtocol(), pServerCnx->GetRtspProtocol(),
                          nPorts, serverPort, m_clientChannel );
            m_clientChannel += nPorts;
            pTunnel->AddRef();
            pSession->AddTunnel( pTunnel );
        }
        
        //for udp, the tunnel should be created in setup request msg
        if( !pTunnel )
        {
            // something is wrong
            m_pClientCnx->sendResponse( 500, atoi( pmsg->GetHdr( "CSeq" ) ) );
            return;
        }

         // we create the interleaved session in setup response
        if( !pTunnel->IsSetup() )
        {
            pTunnel->SetServerPort( serverPort );
            pTunnel->SetClientAddr( m_pClientCnx->GetClientAddr() );
            pTunnel->SetServerAddr( pServerCnx->GetServerAddr() );

            //we got all the info, connect them
            pTunnel->SetupTunnel();  
        }

        rtHdr.SetServerBasePort( pTunnel->GetProxyToClientPort() );
        rtHdr.SetClientBasePort( pTunnel->GetClientPort() );
        rtHdr.SetSourceAddr( m_pClientCnx->GetSelfAddr() );
    }

    pmsg->SetHdr( "Transport", rtHdr.GetHdrString() );
    PassToClient( pmsg, pServerCnx );
}

void CRtspProxyCnx::DeleteSessionByClientSessionID( const CString & strClientSessionID )
{
    CRtspProxySession* pSession = NULL;
    CRtspProxySessionList::Iterator itrs( m_listRtspProxySession.Begin() );

    while( itrs )
    {
        pSession = *itrs;
        if( pSession->GetClientSessionID() == strClientSessionID )
        {
            pSession->ReleaseAllTunnels();
            m_listRtspProxySession.Remove( itrs );
            delete pSession;
            break; 
        }

        itrs++;
    } 
}

void CRtspProxyCnx::DeleteSessionByServerSessionID( const CString & strServerSessionID, const CString & strHost)
{
    CRtspProxySession* pSession = NULL;
    CRtspProxySessionList::Iterator itrs( m_listRtspProxySession.Begin() );

    while( itrs )
    {
        pSession = *itrs;
        if( pSession->GetServerSessionID() == strServerSessionID &&
            pSession->GetHost() == strHost )
        {
            pSession->ReleaseAllTunnels();
            m_listRtspProxySession.Remove( itrs );
            delete pSession;
            break; 
        }

        itrs++;
    } 
}

CString CRtspProxyCnx::FindClientSessionID( const CString & strServerSessionID, const CString & strHost )
{
    CRtspProxySession* pSession = NULL;
    CRtspProxySessionList::Iterator itrs( m_listRtspProxySession.Begin() );

    while( itrs )
    {
        pSession = *itrs;
        if( pSession->GetServerSessionID() == strServerSessionID &&
            pSession->GetHost() == strHost )
        {
            return pSession->GetClientSessionID();
        }

        itrs++;
    } 
    return "";
}
CString CRtspProxyCnx::FindServerSessionID( const CString & strClientSessionID )
{
    CRtspProxySession* pSession = NULL;
    CRtspProxySessionList::Iterator itrs( m_listRtspProxySession.Begin() );

    while(itrs)
    {
        pSession = *itrs;
        if( pSession->GetClientSessionID() == strClientSessionID )
        {
            return pSession->GetServerSessionID();
        }

        itrs++;
    } 
    return "";
}

CServerCnx* CRtspProxyCnx::FindServerCnx( const CString & strHost, UINT16 uHost )
{
    CServerCnxList::Iterator itr( m_listServerCnx.Begin() );
    while( itr )
    {
        CServerCnx* pServerCnx = *itr;
        if( pServerCnx->GetHostName() == strHost && pServerCnx->GetPort() == uHost )
        {
            return pServerCnx;
        }
        itr++;
    }
    return NULL;
}

void CRtspProxyCnx::OnServerConnectionError( UINT code, UINT cseq )
{
    m_pClientCnx->sendResponse( code, cseq );
}

void CRtspProxyCnx::OnClientCnxClosed( void )
{
    while( !m_listServerCnx.IsEmpty() )
    {
        CServerCnx* pServerCnx = m_listServerCnx.RemoveHead();
        pServerCnx->Close();
        delete pServerCnx;
    }

    while( !m_listRtspProxySession.IsEmpty() )
    {
        CRtspProxySession* pSession = m_listRtspProxySession.RemoveHead();
        pSession->ReleaseAllTunnels();
        delete pSession;
    }

    while( !m_listCCSeqPairList.IsEmpty() )
    {
        CCSeqPair* pPair = m_listCCSeqPairList.RemoveHead();
        delete pPair;
    }

    m_pClientCnx->Close();
    delete m_pClientCnx;
    m_pOwner->DeleteProxyCnx( this );
}

void CRtspProxyCnx::OnServerCnxClosed( CServerCnx* pServerCnx )
{
    CServerCnxList::Iterator itr( m_listServerCnx.Begin() );
    while(itr)
    {
        CServerCnx* pSrvCnx = *itr;
        if( pServerCnx == pSrvCnx )
        {
            // remove all server request msg cseq pair related to this server
            CCseqPairList::Iterator itrp( m_listCCSeqPairList.Begin() );
            while( itrp )
            {
                CCSeqPair* pPair = *itrp;
                if( pPair->m_strHost == pServerCnx->GetHostName() &&
                    pPair->m_uPort == pServerCnx->GetPort() )
                {
                    m_listCCSeqPairList.Remove( itrp );
                    delete pPair;
                    break;
                }
                itrp++;
            }

            m_listServerCnx.Remove( itr );
            delete pSrvCnx;
            break;
        }
        itr++;
    }
}

bool CRtspProxyCnx::SetViaHdr( CRtspMsg* pMsg )
{
    CString strValue = pMsg->GetHdr( "Via" );
    
    if( strValue.IsEmpty() )
    {
        strValue = m_viaHdrValue;
    }
    else
    {
        if(strstr(strValue, m_viaHdrValue))
        {
            // a loop here, we need to inform the client
            return false;
        }
        strValue.Append( ", " );
        strValue.Append( m_viaHdrValue );
    }
    pMsg->SetHdr( "Via", strValue );
    return true;
}

/**************************************
 *
 * CRtspProxyApp class
 *
 **************************************/

CRtspProxyApp::CRtspProxyApp( int argc, char** argv ) :
    CApp(argc,argv),
    m_sock(this),
    m_port(554)
{
    //Empty
}

CRtspProxyApp::~CRtspProxyApp( void )
{
    // Empty
}

bool CRtspProxyApp::Init( void )
{
    if( ! CApp::Init() ) return false;

    CSockAddr addr( CInetAddr::Any(), m_port );
    if( ! m_sock.Listen( addr ) )
    {
        printf( "Port %d not available. Exit!\n", m_port );
        return false;
    }
    printf( "Listening on port %hu\n", m_port );

//    Daemonize();

    addr = m_sock.GetLocalAddr();
    sprintf(m_viaHdrValue, "RTSP/1.0 %lx", addr.GetHost().s_addr^time(NULL)^rand());

    return true;
}

int CRtspProxyApp::Exit( void )
{
    return 0;
}

void CRtspProxyApp::OnConnection( CTcpSocket* psock )
{
    OutputDebugInfo( "CRtspProxyApp::OnConnection: new client" );
    CRtspProxyCnx* pCnx = new CRtspProxyCnx( this, psock, m_viaHdrValue );
    m_listProxyCnx.InsertTail(pCnx);
}

void CRtspProxyApp::OnClosed( void )
{
   while(!m_listProxyCnx.IsEmpty())
   {
       CRtspProxyCnx* pCnx = m_listProxyCnx.RemoveHead();
       //pCnx->OnClosed();
       delete pCnx;
   }
}

void CRtspProxyApp::SetPort( UINT16 port )
{
    m_port = port;
}

void CRtspProxyApp::DeleteProxyCnx( CRtspProxyCnx* proxyCnx )
{
    CRtspProxyCnxList::Iterator itr( m_listProxyCnx.Begin() );
    while(itr)
    {
        CRtspProxyCnx* pCnx = *itr;
        if(pCnx == proxyCnx)
        {
            m_listProxyCnx.Remove(itr);
            delete proxyCnx;
            return;
        }
        itr++;
    }
}

#ifdef _UNIX
static void ctrlCHandler( int n )
{
    delete CResolver::GetResolver();
   exit( 0 );
}
#endif

#ifdef _WIN32
static BOOL ctrlCHandler( DWORD fdwCtrlType ) 
{
    if( fdwCtrlType == CTRL_C_EVENT)
    {
        delete CResolver::GetResolver();
        exit( 0 ); 
        return TRUE; 
    }
    return FALSE;
} 
#endif

static void Usage( CPCHAR progname )
{
    printf( "usage: %s [-p port]  [-v]\n", progname);
    printf( "    -p port         Run as a server bound to the specified port (default: %d).\n", 554);
    printf( "    -v              Print version information.\n");
    printf( "    -h              Display this help message.\n");
    printf( "    -d              Enable useful debug messages.\n");
}

int main( int argc, char** argv )
{
#ifdef _UNIX
    signal( SIGINT, ctrlCHandler );
    signal( SIGPIPE, SIG_IGN );
#endif

#ifdef _WIN32
    SetConsoleCtrlHandler( (PHANDLER_ROUTINE)ctrlCHandler,  TRUE);                           
#endif

    CRtspProxyApp app( argc, argv );

    for( int i = 1; i < argc; i++ )
    {
        if(strcasecmp( argv[i], "-v" ) == 0 )
        {
            printf( "RTSP Proxy Reference Implementation Version 2.0 \n(c) 2001 RealNetworks, Inc. All Rights Reserved" );
            exit( 0 );
        }
        else if( strcasecmp( argv[i], "-h" ) == 0 )
        {
            Usage( argv[0] );
            exit( 1 );
        }
        else if(strcasecmp( argv[i], "-p" ) == 0 )
        {
            if( i + 1 >= argc )
            {
                Usage( argv[0] );
                exit( 1 );
            }

            INT16 port = atoi( argv[i + 1] );
            if( port > 0 )
            {
                app.SetPort( port );
                i++;
            }
            else
            {
                Usage( argv[0] );
                exit( 1 );
            }
        }
        else if( strcasecmp( argv[i], "-d" ) == 0 )
        {
            g_DebugFlagTurnedOn = true;
        }
    }

    app.Run();
}


syntax highlighted by Code2HTML, v. 0.9.1