/**************************************************************************** * * 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 #include #include #include #include "app.h" #include "rtspprot.h" #include "dbg.h" /************************************** * * RTSP "pseudo-socket" class * **************************************/ CRtspInterleavedSocket::CRtspInterleavedSocket( void ) : CSocket(), m_pProt(NULL), m_chan(0), m_pbuf(NULL) { // Empty } CRtspInterleavedSocket::CRtspInterleavedSocket( CStreamResponse* pResponse ) : CSocket(pResponse), m_pProt(NULL), m_chan(0), m_pbuf(NULL) { // Empty } CRtspInterleavedSocket::~CRtspInterleavedSocket( void ) { Close(); delete m_pbuf; } bool CRtspInterleavedSocket::IsOpen( void ) { return ( m_pProt != NULL ); } void CRtspInterleavedSocket::Close( void ) { if( m_pProt ) { m_pProt->m_ppSockets[m_chan] = NULL; m_pProt = NULL; if( m_pResponse ) m_pResponse->OnClosed(); } } size_t CRtspInterleavedSocket::Read( PVOID pbuf, size_t nLen ) { assert_or_retv( 0, (m_pbuf != NULL) ); // We will treat the read as a datagram -- copy as much as will fit and // discard any remainder size_t copylen = min( nLen, m_pbuf->GetSize() ); memcpy( pbuf, m_pbuf->GetBuffer(), copylen ); delete m_pbuf; m_pbuf = NULL; return copylen; } size_t CRtspInterleavedSocket::Write( CPVOID pbuf, size_t nLen ) { assert_or_retv( 0, (pbuf != NULL && nLen < 0xFFFF) ); assert_or_retv( 0, (m_chan < 256 && m_pProt != NULL) ); assert_or_retv( 0, (m_pProt->m_psock != NULL) ); CSocket* psock = m_pProt->m_psock; BYTE hdr[4]; hdr[0] = 0x24; hdr[1] = m_chan; hdr[2] = nLen >> 8; hdr[3] = nLen & 0xFF; //TODO: implement vector read/write size_t n = 0; if( psock->Write( hdr, 4 ) == 4 ) { n = psock->Write( pbuf, nLen ); } return n; } bool CRtspInterleavedSocket::Connect( UINT chan, CRtspProtocol* pProt ) { assert( chan < 256 && pProt != NULL ); assert_or_retv( false, (m_pProt == NULL) ); if( pProt->m_ppSockets[chan] ) return false; pProt->m_ppSockets[chan] = this; m_chan = chan; m_pProt = pProt; return true; } /************************************** * * RTSP protocol class * **************************************/ const char* s_szHeaders[] = { // From s6.2 "Accept", "Accept-Encoding", "Accept-Language", "Authorization", "From", "If-Modified-Since", "Range", "Referer", "User-Agent", NULL }; // Initial and max line buffer lengths #define MIN_READ_BUF 32 #define MAX_READ_BUF 1024 #define MAX_BODY_LEN 16384 CRtspProtocol::CRtspProtocol( CRtspProtocolResponse* pResponse ) : m_pResponse(pResponse), m_state(stCmd), m_psock(NULL), m_nBufLen(0), m_pReadBuf(NULL), m_pTail(NULL), m_cseqSend(0), m_cseqRecv(0), m_pmsg(NULL), m_nBodyLen(0) { memset( m_ppSockets, 0, 256*sizeof(CRtspInterleavedSocket*) ); } CRtspProtocol::~CRtspProtocol( void ) { delete m_psock; delete[] m_pReadBuf; delete m_pmsg; } void CRtspProtocol::Init( CSocket* pSocket ) { m_psock = pSocket; m_psock->SetResponse( this ); m_psock->Select( SF_READ ); } #define FUDGE 16 void CRtspProtocol::SendRequest( CRtspRequestMsg* pmsg ) { assert_or_ret( pmsg ); UINT32 cseq = 0; CString strCSeq = pmsg->GetHdr( "CSeq" ); if( strCSeq.IsEmpty() ) { char buf[16]; cseq = GetNextCseq(); sprintf( buf, "%u", cseq ); pmsg->SetHdr( "CSeq", buf ); } else { cseq = atoi( strCSeq ); } CPCHAR pVerb = pmsg->GetVerbStr(); CPCHAR pUrl = pmsg->GetUrl(); size_t nHdrLen = pmsg->GetHdrLen(); size_t nBufLen = pmsg->GetBufLen(); // SP SP "RTSP/1.0" CRLF // CRLF size_t len = FUDGE + strlen(pVerb)+1+strlen(pUrl)+1+8 + 2 + nHdrLen + 2 + nBufLen; char* pbuf = new char[ FUDGE + strlen(pVerb)+1+strlen(pUrl)+1+8 + 2 + nHdrLen + 2 + nBufLen ]; char* p = pbuf; if( !pbuf ) { printf( "Out of memory\n" ); exit( -1 ); } p += sprintf( pbuf, "%s %s RTSP/1.0\r\n", pVerb, pUrl ); for( UINT n = 0; n < pmsg->GetHdrCount(); n++ ) { CRtspHdr* pHdr = pmsg->GetHdr( n ); p += sprintf( p, "%s: %s\r\n", (CPCHAR)pHdr->GetKey(), (CPCHAR)pHdr->GetVal() ); } p += sprintf( p, "\r\n" ); if( nBufLen ) { memcpy( p, pmsg->GetBuf(), nBufLen ); p += nBufLen; } assert( p-pbuf <= len ); m_psock->Write( pbuf, p-pbuf ); delete[] pbuf; m_listRequestQueue.InsertTail( CRtspRequestTag( cseq, pmsg->GetVerb() ) ); } void CRtspProtocol::SendResponse( CRtspResponseMsg* pmsg ) { UINT nCode = pmsg->GetStatusCode(); CPCHAR pReason = pmsg->GetStatusMsg(); size_t nHdrLen = pmsg->GetHdrLen(); size_t nBufLen = pmsg->GetBufLen(); // "RTSP/1.0" SP SP CRLF // CRLF // (or terminating NULL from sprintf() if no buffer) size_t nResponseLen = 8+1+3+1+strlen(pReason)+2+nHdrLen+2+nBufLen; if( 0 == nBufLen ) nResponseLen++; char* pbuf = new char[ nResponseLen ]; char* p = pbuf; if( !pbuf ) { printf( "Out of memory\n" ); exit( -1 ); } p += sprintf( pbuf, "RTSP/1.0 %u %s\r\n", nCode, pReason ); for( UINT n = 0; n < pmsg->GetHdrCount(); n++ ) { CRtspHdr* pHdr = pmsg->GetHdr( n ); p += sprintf( p, "%s: %s\r\n", (CPCHAR)pHdr->GetKey(), (CPCHAR)pHdr->GetVal() ); } p += sprintf( p, "\r\n" ); if( nBufLen ) { memcpy( p, pmsg->GetBuf(), nBufLen ); p += nBufLen; } m_psock->Write( pbuf, p-pbuf ); delete[] pbuf; } // Parse newly read block and find start of next line // If no EOL found: returns NULL // If EOL found: Terminates line, returns ptr to next line char* CRtspProtocol::parseLine( void ) { char* p = m_pReadBuf; char* pNextLine = NULL; while( p < m_pTail ) { if( *p == '\n' ) { pNextLine = p+1; // We found EOL but it may not be the end of the logical header. // First check for CRLF and adjust p, then if either the line is // empty, or the next line does not start with LWS, it's the end. if( p > m_pReadBuf && *(p-1) == '\r' ) p--; if( p == m_pReadBuf || (pNextLine < m_pTail && *pNextLine != ' ' && *pNextLine != '\t') ) { *p = '\0'; // Terminate header break; } // It's not the end of the logical header so keep going p = pNextLine-1; pNextLine = NULL; } p++; } if( !pNextLine && m_pTail == m_pReadBuf+m_nBufLen ) { size_t nNewBufLen = m_nBufLen * 2; if( nNewBufLen > MAX_READ_BUF ) { dbgout( "CRtspProtocol::parseLine: Line too long" ); m_psock->Close(); m_state = stFail; return NULL; } char* pNewBuf = new char[ nNewBufLen ]; if( !pNewBuf ) { dbgout( "CRtspProtocol::parseLine: Out of memory" ); m_psock->Close(); m_state = stFail; return NULL; } memcpy( pNewBuf, m_pReadBuf, m_nBufLen ); m_pTail = pNewBuf + (m_pTail-m_pReadBuf); m_nBufLen = nNewBufLen; delete[] m_pReadBuf; m_pReadBuf = pNewBuf; } return pNextLine; } void CRtspProtocol::handleReadCmd( void ) { if( 0 == strncmp( m_pReadBuf, "RTSP", 4 ) && m_pReadBuf[4] == '/' && isdigit(m_pReadBuf[5]) && m_pReadBuf[6] == '.' && isdigit(m_pReadBuf[7]) && m_pReadBuf[8] == ' ' ) { // Response: RTSP/#.# if( !isdigit(m_pReadBuf[9]) || !isdigit(m_pReadBuf[10]) || !isdigit(m_pReadBuf[11]) || m_pReadBuf[12] != ' ' || m_pReadBuf[13] == ' ' || m_pReadBuf[13] == '\0' ) { dbgout( "RTSP protocol error: bad status line" ); m_state = stCmd; return; } assert( NULL == m_pmsg ); CRtspResponseMsg* pmsg = new CRtspResponseMsg(); pmsg->SetStatus( atoi( m_pReadBuf+9 ) ); //pmsg->SetReason( m_pReadBuf+13 ); m_pmsg = pmsg; m_state = stHdr; } else { // Request: RTSP/#.# char* p = m_pReadBuf; if( *p == ' ' ) { dbgout( "RTSP protocol error: bad request line" ); m_state = stCmd; return; } char* pVerb = p; while( *p && *p != ' ' ) p++; if( *p != ' ' || *(p+1) == ' ' ) { dbgout( "RTSP protocol error: bad request line" ); m_state = stCmd; return; } *p++ = '\0'; char* pUrl = p; while( *p && *p != ' ' ) p++; if( *p != ' ' || *(p+1) == ' ' ) { dbgout( "RTSP protocol error: bad request line" ); m_state = stCmd; return; } *p++ = '\0'; if( 0 != strncmp( p, "RTSP", 4 ) || p[4] != '/' || !isdigit(p[5]) || p[6] != '.' || !isdigit(p[7]) || p[8] != '\0' ) { dbgout( "RTSP protocol error: bad request line" ); m_state = stCmd; return; } CRtspRequestMsg* pmsg = new CRtspRequestMsg(); pmsg->SetVerb( pVerb ); pmsg->SetUrl( pUrl ); m_pmsg = pmsg; m_state = stHdr; } } void CRtspProtocol::handleReadHdr( void ) { //XXX: headers need a better parser if( '\0' == *m_pReadBuf ) { // Empty line indicates end of headers // Note: body may not be present, that's cool CString strBodyLen = m_pmsg->GetHdr( "Content-Length" ); m_nBodyLen = atoi( strBodyLen ); m_state = stBody; } else { char* p = m_pReadBuf; if( *p == ':' ) { dbgout( "RTSP protocol error: received header line '%s'", m_pReadBuf ); return; } while( *p && *p != ':' ) p++; if( *p != ':' ) { dbgout( "RTSP protocol error: received header line '%s'", m_pReadBuf ); return; } // Break into key and val, skip LWS, and save it *p++ = '\0'; while( *p == ' ' ) p++; if( *p ) { m_pmsg->SetHdr( m_pReadBuf, p ); } } } void CRtspProtocol::handleReadBody( void ) { if( m_pTail >= m_pReadBuf+m_nBodyLen ) { if( m_nBodyLen ) m_pmsg->SetBuf( (CPBYTE)m_pReadBuf, m_nBodyLen ); m_state = stDispatch; } else { if( m_nBufLen < m_nBodyLen ) { char* pNewBuf = new char[ m_nBodyLen ]; if( !pNewBuf ) { dbgout( "CRtspProtocol::handleReadBody: Out of memory" ); m_psock->Close(); m_state = stFail; return; } memcpy( pNewBuf, m_pReadBuf, m_nBufLen ); m_pTail = pNewBuf + (m_pTail-m_pReadBuf); m_nBufLen = m_nBodyLen; delete[] m_pReadBuf; m_pReadBuf = pNewBuf; } } } void CRtspProtocol::handleReadPkt( void ) { assert( NULL != m_pResponse ); if( m_pTail < m_pReadBuf+4 ) return; if( !m_nBodyLen ) { UINT16 usTmp; memcpy( &usTmp, m_pReadBuf+2, 2 ); m_nBodyLen = 4 + ntohs( usTmp ); if( m_nBufLen < m_nBodyLen ) { char* pNewBuf = new char[ m_nBodyLen ]; if( !pNewBuf ) { dbgout( "CRtspProtocol::handleReadPkt: Out of memory" ); m_psock->Close(); m_state = stFail; return; } memcpy( pNewBuf, m_pReadBuf, m_nBufLen ); m_pTail = pNewBuf + (m_pTail-m_pReadBuf); m_nBufLen = m_nBodyLen; delete[] m_pReadBuf; m_pReadBuf = pNewBuf; } } if( m_pTail >= m_pReadBuf+m_nBodyLen ) { BYTE chan = *( (BYTE*)m_pReadBuf+1 ); CRtspInterleavedSocket* psock = m_ppSockets[chan]; if( psock ) { assert( !psock->m_pbuf ); // the last packet should be gone delete psock->m_pbuf; psock->m_pbuf = new CBuffer( (BYTE*)m_pReadBuf+4, m_nBodyLen-4 ); psock->m_pResponse->OnReadReady(); } else { m_pResponse->OnError( RTSPE_NOTRAN ); } m_state = stReady; } } void CRtspProtocol::dispatchMessage( void ) { assert( NULL != m_pResponse ); assert( NULL != m_pmsg ); switch( m_pmsg->GetType() ) { case RTSP_TYPE_REQUEST: { CRtspRequestMsg* pmsg = (CRtspRequestMsg*)m_pmsg; switch( pmsg->GetVerb() ) { case VERB_DESCRIBE: m_pResponse->OnDescribeRequest( pmsg ); break; case VERB_ANNOUNCE: m_pResponse->OnAnnounceRequest( pmsg ); break; case VERB_GETPARAM: m_pResponse->OnGetParamRequest( pmsg ); break; case VERB_SETPARAM: m_pResponse->OnSetParamRequest( pmsg ); break; case VERB_OPTIONS: m_pResponse->OnOptionsRequest( pmsg ); break; case VERB_PAUSE: m_pResponse->OnPauseRequest( pmsg ); break; case VERB_PLAY: m_pResponse->OnPlayRequest( pmsg ); break; case VERB_RECORD: m_pResponse->OnRecordRequest( pmsg ); break; case VERB_REDIRECT: m_pResponse->OnRedirectRequest( pmsg ); break; case VERB_SETUP: m_pResponse->OnSetupRequest( pmsg ); break; case VERB_TEARDOWN: m_pResponse->OnTeardownRequest( pmsg ); break; default: assert(false); } } break; case RTSP_TYPE_RESPONSE: { UINT cseq = atoi( m_pmsg->GetHdr( "CSeq" ) ); CRtspRequestTagList::Iterator itr( m_listRequestQueue.Begin() ); while( itr ) { if( cseq == (*itr).m_cseq ) { CRtspResponseMsg* pmsg = (CRtspResponseMsg*)m_pmsg; switch( (*itr).m_verb ) { case VERB_DESCRIBE: m_pResponse->OnDescribeResponse( pmsg ); break; case VERB_ANNOUNCE: m_pResponse->OnAnnounceResponse( pmsg ); break; case VERB_GETPARAM: m_pResponse->OnGetParamResponse( pmsg ); break; case VERB_SETPARAM: m_pResponse->OnSetParamResponse( pmsg ); break; case VERB_OPTIONS: m_pResponse->OnOptionsResponse( pmsg ); break; case VERB_PAUSE: m_pResponse->OnPauseResponse( pmsg ); break; case VERB_PLAY: m_pResponse->OnPlayResponse( pmsg ); break; case VERB_RECORD: m_pResponse->OnRecordResponse( pmsg ); break; case VERB_REDIRECT: m_pResponse->OnRedirectResponse( pmsg ); break; case VERB_SETUP: m_pResponse->OnSetupResponse( pmsg ); break; case VERB_TEARDOWN: m_pResponse->OnTeardownResponse( pmsg ); break; default: assert(false); } m_listRequestQueue.Remove( itr ); break; } itr++; } } break; default: assert(false); } delete m_pmsg; m_pmsg = NULL; m_nBodyLen = 0; m_state = stReady; } void CRtspProtocol::OnConnectDone( int err ) { // Socket must be connected before Init() assert( false ); } void CRtspProtocol::OnReadReady( void ) { if( !m_pReadBuf ) { assert( m_state < stBody ); m_pReadBuf = new char[ MIN_READ_BUF ]; m_nBufLen = MIN_READ_BUF; m_pTail = m_pReadBuf; } size_t len = m_nBufLen - (m_pTail-m_pReadBuf); size_t n = m_psock->Read( m_pTail, len ); if( n == SOCKERR_EOF ) { dbgout( "CRtspProtocol::OnReadReady: Socket read failed" ); m_psock->Close(); return; } m_pTail += n; bool bParsing = true; while( bParsing ) { char* pNextLine = NULL; size_t nParsed = 0; bParsing = false; switch( m_state ) { case stReady: if( m_pTail > m_pReadBuf ) { m_state = ('$' == *m_pReadBuf) ? stPkt : stCmd; nParsed = 0; bParsing = true; } break; case stPkt: handleReadPkt(); if( m_state == stReady ) { nParsed = m_nBodyLen; m_nBodyLen = 0; bParsing = true; } break; case stCmd: pNextLine = parseLine(); if( pNextLine ) { handleReadCmd(); nParsed = (pNextLine - m_pReadBuf); bParsing = true; } break; case stHdr: pNextLine = parseLine(); if( pNextLine ) { handleReadHdr(); nParsed = (pNextLine - m_pReadBuf); bParsing = true; } break; case stBody: handleReadBody(); if( m_state == stDispatch ) { nParsed = m_nBodyLen; bParsing = true; } break; case stDispatch: dispatchMessage(); nParsed = 0; bParsing = true; break; default: assert(false); } if( nParsed ) { char* pFrom = m_pReadBuf + nParsed; size_t len = (m_pTail - m_pReadBuf) - nParsed; memmove( m_pReadBuf, pFrom, len ); m_pTail -= nParsed; } } } void CRtspProtocol::OnWriteReady( void ) { assert(false); } void CRtspProtocol::OnExceptReady( void ) { assert(false); } void CRtspProtocol::OnClosed( void ) { assert( m_pResponse ); dbgout( "CRtspProtocol::OnClosed: Socket closed" ); m_pResponse->OnError( RTSPE_CLOSED ); }