Darwin-Streaming-Server/Server.tproj/RTSPSession.cpp

2202 lines
94 KiB
C++
Raw Permalink Normal View History

/*
*
* @APPLE_LICENSE_HEADER_START@
*
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*
*/
/*
File: RTSPSession.cpp
Contains: Implemenation of RTSPSession objects
*/
#define __RTSP_HTTP_DEBUG__ 0
#define __RTSP_HTTP_VERBOSE__ 0
#define __RTSP_AUTH_DEBUG__ 0
#define debug_printf if (__RTSP_AUTH_DEBUG__) qtss_printf
#include "RTSPSession.h"
#include "RTSPRequest.h"
#include "QTSServerInterface.h"
#include "RTSPRequest3GPP.h"
#include "MyAssert.h"
#include "OSMemory.h"
#include "QTSS.h"
#include "QTSSModuleUtils.h"
#include "UserAgentParser.h"
#include "base64.h"
#include "OSArrayObjectDeleter.h"
#include "md5digest.h"
#include "QTSSDataConverter.h"
#if __FreeBSD__ || __hpux__
#include <unistd.h>
#endif
#include <errno.h>
#if __solaris__ || __linux__ || __sgi__ || __hpux__
#include <crypt.h>
#endif
#if __RTSP_HTTP_DEBUG__
#define HTTP_TRACE(s) qtss_printf(s);
#define HTTP_TRACE_SPL(s) PrintfStrPtrLen(s);
#define HTTP_TRACE_ONE(s, one ) qtss_printf(s, one);
#define HTTP_TRACE_TWO(s, one, two ) qtss_printf(s, one, two);
#else
#define HTTP_TRACE(s);
#define HTTP_TRACE_SPL(s);
#define HTTP_TRACE_ONE(s, one );
#define HTTP_TRACE_TWO(s, one, two );
#endif
#if __RTSP_HTTP_VERBOSE__
#define HTTP_VTRACE(s) qtss_printf(s);
#define HTTP_VTRACE_SPL(s) PrintfStrPtrLen(s);
#define HTTP_VTRACE_ONE(s, one ) qtss_printf(s, one);
#define HTTP_VTRACE_TWO(s, one, two ) qtss_printf(s, one, two);
#else
#define HTTP_VTRACE(s);
#define HTTP_VTRACE_SPL(s);
#define HTTP_VTRACE_ONE(s, one );
#define HTTP_VTRACE_TWO(s, one, two );
#endif
#if __RTSP_HTTP_DEBUG__ || __RTSP_HTTP_VERBOSE__
static void PrintfStrPtrLen( StrPtrLen *splRequest )
{
char buff[1024];
memcpy( buff, splRequest->Ptr, splRequest->Len );
buff[ splRequest->Len] = 0;
HTTP_TRACE_ONE( "%s\n", buff )
//qtss_printf( "%s\n", buff );
}
#endif
//hack stuff
static char* sBroadcasterSessionName="QTSSReflectorModuleBroadcasterSession";
static QTSS_AttributeID sClientBroadcastSessionAttr = qtssIllegalAttrID;
static StrPtrLen sVideoStr("video");
static StrPtrLen sAudioStr("audio");
static StrPtrLen sRtpMapStr("rtpmap");
static StrPtrLen sControlStr("control");
static StrPtrLen sBufferDelayStr("x-bufferdelay");
static StrPtrLen sContentType("application/x-random-data");
static StrPtrLen sAuthAlgorithm("md5");
static StrPtrLen sAuthQop("auth");
static StrPtrLen sEmptyStr("");
// static class member initialized in RTSPSession ctor
OSRefTable* RTSPSession::sHTTPProxyTunnelMap = NULL;
char RTSPSession::sHTTPResponseHeaderBuf[kMaxHTTPResponseLen];
StrPtrLen RTSPSession::sHTTPResponseHeaderPtr(sHTTPResponseHeaderBuf, kMaxHTTPResponseLen);
char RTSPSession::sHTTPResponseNoServerHeaderBuf[kMaxHTTPResponseLen];
StrPtrLen RTSPSession::sHTTPResponseNoServerHeaderPtr(sHTTPResponseNoServerHeaderBuf, kMaxHTTPResponseLen);
// stock reponse with place holder for server header and optional "x-server-ip-address" header ( %s%s%s for "x-server-ip-address" + ip address + \r\n )
// the optional version must be generated at runtime to include a valid IP address for the actual interface
char* RTSPSession::sHTTPResponseFormatStr = "HTTP/1.0 200 OK\r\n%s%s%s%s\r\nConnection: close\r\nDate: Thu, 19 Aug 1982 18:30:00 GMT\r\nCache-Control: no-store\r\nPragma: no-cache\r\nContent-Type: application/x-rtsp-tunnelled\r\n\r\n";
char* RTSPSession::sHTTPNoServerResponseFormatStr = "HTTP/1.0 200 OK\r\n%s%s%s%sConnection: close\r\nDate: Thu, 19 Aug 1982 18:30:00 GMT\r\nCache-Control: no-store\r\nPragma: no-cache\r\nContent-Type: application/x-rtsp-tunnelled\r\n\r\n";
void RTSPSession::Initialize()
{
sHTTPProxyTunnelMap = new OSRefTable(OSRefTable::kDefaultTableSize);
// Construct premade HTTP response for HTTP proxy tunnel
qtss_sprintf(sHTTPResponseHeaderBuf, sHTTPResponseFormatStr, "","","", QTSServerInterface::GetServerHeader().Ptr);
sHTTPResponseHeaderPtr.Len = ::strlen(sHTTPResponseHeaderBuf);
Assert(sHTTPResponseHeaderPtr.Len < kMaxHTTPResponseLen);
qtss_sprintf(sHTTPResponseNoServerHeaderBuf, sHTTPNoServerResponseFormatStr, "","","","");
sHTTPResponseNoServerHeaderPtr.Len = ::strlen(sHTTPResponseNoServerHeaderBuf);
Assert(sHTTPResponseNoServerHeaderPtr.Len < kMaxHTTPResponseLen);
}
RTSPSession::RTSPSession( Bool16 doReportHTTPConnectionAddress )
: RTSPSessionInterface(),
fRequest(NULL),
fRTPSession(NULL),
fReadMutex(),
fHTTPMethod( kHTTPMethodInit ),
fWasHTTPRequest( false ),
fFoundValidAccept( false),
fDoReportHTTPConnectionAddress(doReportHTTPConnectionAddress),
fCurrentModule(0),
fState(kReadingFirstRequest)
{
this->SetTaskName("RTSPSession");
// must guarantee this map is present
Assert(sHTTPProxyTunnelMap != NULL);
QTSServerInterface::GetServer()->AlterCurrentRTSPSessionCount(1);
// Setup the QTSS param block, as none of these fields will change through the course of this session.
fRoleParams.rtspRequestParams.inRTSPSession = this;
fRoleParams.rtspRequestParams.inRTSPRequest = NULL;
fRoleParams.rtspRequestParams.inClientSession = NULL;
fModuleState.curModule = NULL;
fModuleState.curTask = this;
fModuleState.curRole = 0;
fModuleState.globalLockRequested = false;
fProxySessionID[0] = 0;
fProxySessionIDPtr.Set( fProxySessionID, 0 );
fLastRTPSessionID[0] = 0;
fLastRTPSessionIDPtr.Set( fLastRTPSessionID, 0 );
Assert(fLastRTPSessionIDPtr.Ptr == &fLastRTPSessionID[0]);
(void)QTSS_IDForAttr(qtssClientSessionObjectType, sBroadcasterSessionName, &sClientBroadcastSessionAttr);
}
RTSPSession::~RTSPSession()
{
// Invoke the session closing modules
QTSS_RoleParams theParams;
theParams.rtspSessionClosingParams.inRTSPSession = this;
// Invoke modules
for (UInt32 x = 0; x < QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPSessionClosingRole); x++)
(void)QTSServerInterface::GetModule(QTSSModule::kRTSPSessionClosingRole, x)->CallDispatch(QTSS_RTSPSessionClosing_Role, &theParams);
fLiveSession = false; //used in Clean up request to remove the RTP session.
this->CleanupRequest();// Make sure that all our objects are deleted
if (fSessionType == qtssRTSPSession)
QTSServerInterface::GetServer()->AlterCurrentRTSPSessionCount(-1);
else
QTSServerInterface::GetServer()->AlterCurrentRTSPHTTPSessionCount(-1);
if ( *fProxySessionID != '\0')
{
#if DEBUG
char * str = "???";
if ( fSessionType == qtssRTSPHTTPInputSession )
str = "input session";
else if ( fSessionType == qtssRTSPHTTPSession )
str = "input session";
HTTP_VTRACE_TWO( "~RTSPSession, was a fProxySessionID (%s), %s\n", fProxySessionID, str )
#endif
sHTTPProxyTunnelMap->UnRegister( &fProxyRef );
}
}
SInt64 RTSPSession::Run()
{
EventFlags events = this->GetEvents();
QTSS_Error err = QTSS_NoErr;
QTSSModule* theModule = NULL;
UInt32 numModules = 0;
Assert(fLastRTPSessionIDPtr.Ptr == &fLastRTPSessionID[0]);
// Some callbacks look for this struct in the thread object
OSThreadDataSetter theSetter(&fModuleState, NULL);
//check for a timeout or a kill. If so, just consider the session dead
if ((events & Task::kTimeoutEvent) || (events & Task::kKillEvent))
fLiveSession = false;
while (this->IsLiveSession())
{
// RTSP Session state machine. There are several well defined points in an RTSP request
// where this session may have to return from its run function and wait for a new event.
// Because of this, we need to track our current state and return to it.
switch (fState)
{
case kReadingFirstRequest:
{
if ((err = fInputStream.ReadRequest()) == QTSS_NoErr)
{
// If the RequestStream returns QTSS_NoErr, it means
// that we've read all outstanding data off the socket,
// and still don't have a full request. Wait for more data.
//+rt use the socket that reads the data, may be different now.
fInputSocketP->RequestEvent(EV_RE);
return 0;
}
if ((err != QTSS_RequestArrived) && (err != E2BIG))
{
// Any other error implies that the client has gone away. At this point,
// we can't have 2 sockets, so we don't need to do the "half closed" check
// we do below
Assert(err > 0);
Assert(!this->IsLiveSession());
break;
}
if (err == QTSS_RequestArrived)
fState = kHTTPFilteringRequest;
// If we get an E2BIG, it means our buffer was overfilled.
// In that case, we can just jump into the following state, and
// the code their does a check for this error and returns an error.
if (err == E2BIG)
fState = kHaveNonTunnelMessage;
}
continue;
case kHTTPFilteringRequest:
{
HTTP_TRACE( "RTSPSession::Run kHTTPFilteringRequest\n" )
fState = kHaveNonTunnelMessage; // assume it's not a tunnel setup message
// prefilter will set correct tunnel state if it is.
QTSS_Error preFilterErr = this->PreFilterForHTTPProxyTunnel();
if ( preFilterErr == QTSS_NoErr )
{
HTTP_TRACE( "RTSPSession::Run kHTTPFilteringRequest\n" )
continue;
}
else
{
// pre filter error indicates a tunnelling message that could
// not join to a session.
HTTP_TRACE( "RTSPSession::Run kHTTPFilteringRequest Tunnel protocol ERROR.\n" )
return -1;
}
}
case kWaitingToBindHTTPTunnel:
//flush the GET response, if it's there
err = fOutputStream.Flush();
if (err == EAGAIN)
{
// If we get this error, we are currently flow-controlled and should
// wait for the socket to become writeable again
fSocket.RequestEvent(EV_WR);
}
return 0;
//continue;
case kSocketHasBeenBoundIntoHTTPTunnel:
// DMS - Can this execute either? I don't think so... this one
// we may not need...
// I've been joined, it's time to kill this session.
Assert(!this->IsLiveSession()); // at least the socket should not report connected any longer
HTTP_TRACE( "RTSPSession has died of snarfage.\n" )
break;
case kReadingRequest:
{
// We should lock down the session while reading in data,
// because we can't snarf up a POST while reading.
OSMutexLocker readMutexLocker(&fReadMutex);
// we should be only reading an RTSP request here, no HTTP tunnel messages
if ((err = fInputStream.ReadRequest()) == QTSS_NoErr)
{
// If the RequestStream returns QTSS_NoErr, it means
// that we've read all outstanding data off the socket,
// and still don't have a full request. Wait for more data.
//+rt use the socket that reads the data, may be different now.
fInputSocketP->RequestEvent(EV_RE);
return 0;
}
if ((err != QTSS_RequestArrived) && (err != E2BIG) && (err != QTSS_BadArgument))
{
//Any other error implies that the input connection has gone away.
// We should only kill the whole session if we aren't doing HTTP.
// (If we are doing HTTP, the POST connection can go away)
Assert(err > 0);
if (fOutputSocketP->IsConnected())
{
// If we've gotten here, this must be an HTTP session with
// a dead input connection. If that's the case, we should
// clean up immediately so as to not have an open socket
// needlessly lingering around, taking up space.
Assert(fOutputSocketP != fInputSocketP);
Assert(!fInputSocketP->IsConnected());
fInputSocketP->Cleanup();
return 0;
}
else
{
Assert(!this->IsLiveSession());
break;
}
}
fState = kHaveNonTunnelMessage;
// fall thru to kHaveNonTunnelMessage
}
case kHaveNonTunnelMessage:
{
// should only get here when fInputStream has a full message built.
Assert( fInputStream.GetRequestBuffer() );
Assert(fRequest == NULL);
fRequest = NEW RTSPRequest(this);
fRoleParams.rtspRequestParams.inRTSPRequest = fRequest;
fRoleParams.rtspRequestParams.inRTSPHeaders = fRequest->GetHeaderDictionary();
// We have an RTSP request and are about to begin processing. We need to
// make sure that anyone sending interleaved data on this session won't
// be allowed to do so until we are done sending our response
// We also make sure that a POST session can't snarf in while we're
// processing the request.
fReadMutex.Lock();
fSessionMutex.Lock();
// The fOutputStream's fBytesWritten counter is used to
// count the # of bytes for this RTSP response. So, at
// this point, reset it to 0 (we can then just let it increment
// until the next request comes in)
fOutputStream.ResetBytesWritten();
// Check for an overfilled buffer, and return an error.
if (err == E2BIG)
{
(void)QTSSModuleUtils::SendErrorResponse(fRequest, qtssClientBadRequest,
qtssMsgRequestTooLong);
fState = kPostProcessingRequest;
break;
}
// Check for a corrupt base64 error, return an error
if (err == QTSS_BadArgument)
{
(void)QTSSModuleUtils::SendErrorResponse(fRequest, qtssClientBadRequest,
qtssMsgBadBase64);
fState = kPostProcessingRequest;
break;
}
Assert(err == QTSS_RequestArrived);
fState = kFilteringRequest;
// Note that there is no break here. We'd like to continue onto the next
// state at this point. This goes for every case in this case statement
}
case kFilteringRequest:
{
// We received something so auto refresh
// The need to auto refresh is because the api doesn't allow a module to refresh at this point
//
fTimeoutTask.RefreshTimeout();
//
// Before we even do this, check to see if this is a *data* packet,
// in which case this isn't an RTSP request, so we don't need to go
// through any of the remaining steps
if (fInputStream.IsDataPacket()) // can this interfere with MP3?
{
this->HandleIncomingDataPacket();
fState = kCleaningUp;
break;
}
//
// In case a module wants to replace the request
char* theReplacedRequest = NULL;
char* oldReplacedRequest = NULL;
// Setup the filter param block
QTSS_RoleParams theFilterParams;
theFilterParams.rtspFilterParams.inRTSPSession = this;
theFilterParams.rtspFilterParams.inRTSPRequest = fRequest;
theFilterParams.rtspFilterParams.outNewRequest = &theReplacedRequest;
// Invoke filter modules
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPFilterRole);
for (; (fCurrentModule < numModules) && ((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested); fCurrentModule++)
{
fModuleState.eventRequested = false;
fModuleState.idleTime = 0;
if (fModuleState.globalLockRequested )
{ fModuleState.globalLockRequested = false;
fModuleState.isGlobalLocked = true;
}
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPFilterRole, fCurrentModule);
(void)theModule->CallDispatch(QTSS_RTSPFilter_Role, &theFilterParams);
fModuleState.isGlobalLocked = false;
// If this module has requested an event, return and wait for the event to transpire
if (fModuleState.globalLockRequested) // call this request back locked
return this->CallLocked();
if (fModuleState.eventRequested)
{
this->ForceSameThread(); // We are holding mutexes, so we need to force
// the same thread to be used for next Run()
return fModuleState.idleTime; // If the module has requested idle time...
}
//
// Check to see if this module has replaced the request. If so, check
// to see if there is an old replacement that we should delete
if (theReplacedRequest != NULL)
{
if (oldReplacedRequest != NULL)
delete [] oldReplacedRequest;
fRequest->SetVal(qtssRTSPReqFullRequest, theReplacedRequest, ::strlen(theReplacedRequest));
oldReplacedRequest = theReplacedRequest;
theReplacedRequest = NULL;
}
}
fCurrentModule = 0;
if (fRequest->HasResponseBeenSent())
{
fState = kPostProcessingRequest;
break;
}
if (fSentOptionsRequest && this->ParseOptionsResponse())
{
fRoundTripTime = (SInt32) (OS::Milliseconds() - fOptionsRequestSendTime);
//qtss_printf("RTSPSession::Run RTT time = %"_S32BITARG_" msec\n", fRoundTripTime);
fState = kSendingResponse;
break;
}
else
// Otherwise, this is a normal request, so parse it and get the RTPSession.
this->SetupRequest();
// This might happen if there is some syntax or other error,
// or if it is an OPTIONS request
if (fRequest->HasResponseBeenSent())
{
fState = kPostProcessingRequest;
break;
}
fState = kRoutingRequest;
}
case kRoutingRequest:
{
// Invoke router modules
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPRouteRole);
{
// Manipulation of the RTPSession from the point of view of
// a module is guaranteed to be atomic by the API.
Assert(fRTPSession != NULL);
OSMutexLocker locker(fRTPSession->GetSessionMutex());
for (; (fCurrentModule < numModules) && ((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested); fCurrentModule++)
{
fModuleState.eventRequested = false;
fModuleState.idleTime = 0;
if (fModuleState.globalLockRequested )
{ fModuleState.globalLockRequested = false;
fModuleState.isGlobalLocked = true;
}
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPRouteRole, fCurrentModule);
(void)theModule->CallDispatch(QTSS_RTSPRoute_Role, &fRoleParams);
fModuleState.isGlobalLocked = false;
if (fModuleState.globalLockRequested) // call this request back locked
return this->CallLocked();
// If this module has requested an event, return and wait for the event to transpire
if (fModuleState.eventRequested)
{
this->ForceSameThread(); // We are holding mutexes, so we need to force
// the same thread to be used for next Run()
return fModuleState.idleTime; // If the module has requested idle time...
}
}
}
fCurrentModule = 0;
// SetupAuthLocalPath must happen after kRoutingRequest and before kAuthenticatingRequest
// placed here so that if the state is shifted to kPostProcessingRequest from a response being sent
// then the AuthLocalPath will still be set.
fRequest->SetupAuthLocalPath();
if (fRequest->HasResponseBeenSent())
{
fState = kPostProcessingRequest;
break;
}
if(fRequest->SkipAuthorization())
{
// Skip the authentication and authorization states
// The foll. normally gets executed at the end of the authorization state
// Prepare for kPreprocessingRequest state.
fState = kPreprocessingRequest;
if (fRequest->GetMethod() == qtssSetupMethod)
// Make sure to erase the session ID stored in the request at this point.
// If we fail to do so, this same session would be used if another
// SETUP was issued on this same TCP connection.
fLastRTPSessionIDPtr.Len = 0;
else if (fLastRTPSessionIDPtr.Len == 0)
fLastRTPSessionIDPtr.Len = ::strlen(fLastRTPSessionIDPtr.Ptr);
break;
}
else
fState = kAuthenticatingRequest;
}
case kAuthenticatingRequest:
{
Bool16 allowedDefault = QTSServerInterface::GetServer()->GetPrefs()->GetAllowGuestDefault();
Bool16 allowed = allowedDefault; //server pref?
Bool16 hasUser = false;
Bool16 handled = false;
Bool16 wasHandled = false;
StrPtrLenDel prefRealm(QTSServerInterface::GetServer()->GetPrefs()->GetAuthorizationRealm());
if (prefRealm.Ptr != NULL)
{
fRequest->SetValue(qtssRTSPReqURLRealm,0, prefRealm.Ptr, prefRealm.Len, kDontObeyReadOnly);
}
QTSS_RTSPMethod method = fRequest->GetMethod();
if (method != qtssIllegalMethod) do
{ //Set the request action before calling the authentication module
if((method == qtssAnnounceMethod) || ((method == qtssSetupMethod) && fRequest->IsPushRequest()))
{ fRequest->SetAction(qtssActionFlagsWrite);
break;
}
void* theSession = NULL;
UInt32 theLen = sizeof(theSession);
if (QTSS_NoErr == fRTPSession->GetValue(sClientBroadcastSessionAttr, 0, &theSession, &theLen) )
{ fRequest->SetAction(qtssActionFlagsWrite); // an incoming broadcast session
break;
}
fRequest->SetAction(qtssActionFlagsRead);
} while (false);
else
{ Assert(0);
}
if(fRequest->GetAuthScheme() == qtssAuthNone)
{
QTSS_AuthScheme scheme = QTSServerInterface::GetServer()->GetPrefs()->GetAuthScheme();
if( scheme == qtssAuthBasic)
fRequest->SetAuthScheme(qtssAuthBasic);
else if( scheme == qtssAuthDigest)
fRequest->SetAuthScheme(qtssAuthDigest);
if( scheme == qtssAuthDigest)
debug_printf("RTSPSession.cpp:kAuthenticatingRequest scheme == qtssAuthDigest\n");
}
// Setup the authentication param block
QTSS_RoleParams theAuthenticationParams;
theAuthenticationParams.rtspAthnParams.inRTSPRequest = fRequest;
fModuleState.eventRequested = false;
fModuleState.idleTime = 0;
fRequest->SetAllowed(allowed);
fRequest->SetHasUser(hasUser);
fRequest->SetAuthHandled(handled);
StrPtrLen* lastUsedDigestChallengePtr = this->GetValue(qtssRTSPSesLastDigestChallenge);
if (lastUsedDigestChallengePtr != NULL)
(void) fRequest->SetValue(qtssRTSPReqDigestChallenge,(UInt32) 0,(void *) lastUsedDigestChallengePtr->Ptr,lastUsedDigestChallengePtr->Len, QTSSDictionary::kDontObeyReadOnly);
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPAthnRole);
for (fCurrentModule = 0; (fCurrentModule < numModules) && ((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested); fCurrentModule++)
{
fRequest->SetAllowed(allowedDefault);
fRequest->SetHasUser(false);
fRequest->SetAuthHandled(false);
debug_printf("RTSPSession.cpp:kAuthenticatingRequest fCurrentModule = %lu numModules=%lu\n", fCurrentModule,numModules);
fModuleState.eventRequested = false;
fModuleState.idleTime = 0;
if (fModuleState.globalLockRequested )
{ fModuleState.globalLockRequested = false;
fModuleState.isGlobalLocked = true;
}
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPAthnRole, fCurrentModule);
if (NULL == theModule)
continue;
if (__RTSP_AUTH_DEBUG__)
{ theModule->GetValue(qtssModName)->PrintStr("QTSSModule::CallDispatch ENTER module=", "\n");
}
(void)theModule->CallDispatch(QTSS_RTSPAuthenticate_Role, &theAuthenticationParams);
fModuleState.isGlobalLocked = false;
if (fModuleState.globalLockRequested) // call this request back locked
return this->CallLocked();
// If this module has requested an event, return and wait for the event to transpire
if (fModuleState.eventRequested)
{
this->ForceSameThread(); // We are holding mutexes, so we need to force
// the same thread to be used for next Run()
return fModuleState.idleTime; // If the module has requested idle time...
}
allowed = fRequest->GetAllowed();
hasUser = fRequest->GetHasUser();
handled = fRequest->GetAuthHandled();
debug_printf("RTSPSession::Run Role(kAuthenticatingRequest) allowedDefault =%d allowed= %d hasUser = %d handled=%d \n",allowedDefault, allowed,hasUser, handled);
if (handled)
wasHandled = handled;
if (hasUser || handled )
{
debug_printf("RTSPSession.cpp::Run(kAuthenticatingRequest) skipping other modules fCurrentModule = %lu numModules=%lu\n", fCurrentModule,numModules);
break;
}
}
if (!wasHandled) //don't check and possibly fail the user if it the user has already been checked.
this->CheckAuthentication();
fCurrentModule = 0;
if (fRequest->HasResponseBeenSent())
{
fState = kPostProcessingRequest;
break;
}
fState = kAuthorizingRequest;
}
case kAuthorizingRequest:
{
// Invoke authorization modules
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPAuthRole);
Bool16 allowedDefault = QTSServerInterface::GetServer()->GetPrefs()->GetAllowGuestDefault();
Bool16 allowed = true;
Bool16 hasUser = false;
Bool16 handled = false;
QTSS_Error theErr = QTSS_NoErr;
// Invoke authorization modules
// Manipulation of the RTPSession from the point of view of
// a module is guaranteed to be atomic by the API.
Assert(fRTPSession != NULL);
OSMutexLocker locker(fRTPSession->GetSessionMutex());
fRequest->SetAllowed(allowed);
fRequest->SetHasUser(hasUser);
fRequest->SetAuthHandled(handled);
for (; (fCurrentModule < numModules) && ((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested); fCurrentModule++)
{
fRequest->SetHasUser(false);
fRequest->SetAuthHandled(false);
debug_printf("RTSPSession.cpp:kAuthorizingRequest BEFORE DISPATCH fCurrentModule = %lu numModules=%lu\n", fCurrentModule,numModules);
fModuleState.eventRequested = false;
fModuleState.idleTime = 0;
if (fModuleState.globalLockRequested )
{ fModuleState.globalLockRequested = false;
fModuleState.isGlobalLocked = true;
}
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPAuthRole, fCurrentModule);
if (NULL == theModule)
continue;
if (__RTSP_AUTH_DEBUG__)
{ theModule->GetValue(qtssModName)->PrintStr("QTSSModule::CallDispatch ENTER module=", "\n");
}
(void)theModule->CallDispatch(QTSS_RTSPAuthorize_Role, &fRoleParams);
fModuleState.isGlobalLocked = false;
if (fModuleState.globalLockRequested) // call this request back locked
return this->CallLocked();
// If this module has requested an event, return and wait for the event to transpire
if (fModuleState.eventRequested)
{
this->ForceSameThread(); // We are holding mutexes, so we need to force
// the same thread to be used for next Run()
return fModuleState.idleTime; // If the module has requested idle time...
}
// allowed != default means a module has set the result
// handled means a module wants to be the primary for this request
// -- example qtaccess says only allow valid user and allowed default is false. So module says handled, hasUser is false, allowed is false
//
allowed = fRequest->GetAllowed();
hasUser = fRequest->GetHasUser();
handled = fRequest->GetAuthHandled();
debug_printf("RTSPSession::Run Role(kAuthorizingRequest) allowedDefault =%d allowed= %d hasUser = %d handled=%d \n",allowedDefault, allowed,hasUser, handled);
if (!allowed && !handled) //old module break on !allowed
{
debug_printf("RTSPSession.cpp::Run(kAuthorizingRequest) skipping other modules fCurrentModule = %lu numModules=%lu\n", fCurrentModule,numModules);
break;
}
if (!allowed && hasUser && handled) //new module break on !allowed
{
debug_printf("RTSPSession.cpp::Run(kAuthorizingRequest) skipping other modules fCurrentModule = %lu numModules=%lu\n", fCurrentModule,numModules);
break;
}
}
this->SaveRequestAuthorizationParams(fRequest);
if (!allowed)
{
if (false == fRequest->HasResponseBeenSent())
{
QTSS_AuthScheme challengeScheme = fRequest->GetAuthScheme();
if( challengeScheme == qtssAuthDigest)
{ debug_printf("RTSPSession.cpp:kAuthorizingRequest scheme == qtssAuthDigest)\n");
}
else if( challengeScheme == qtssAuthBasic)
{ debug_printf("RTSPSession.cpp:kAuthorizingRequest scheme == qtssAuthBasic)\n");
}
if(challengeScheme == qtssAuthBasic) {
fRTPSession->SetAuthScheme(qtssAuthBasic);
theErr = fRequest->SendBasicChallenge();
}
else if(challengeScheme == qtssAuthDigest) {
fRTPSession->UpdateDigestAuthChallengeParams(false, false, RTSPSessionInterface::kNoQop);
theErr = fRequest->SendDigestChallenge(fRTPSession->GetAuthQop(), fRTPSession->GetAuthNonce(), fRTPSession->GetAuthOpaque());
}
else {
// No authentication scheme is given and the request was not allowed,
// so send a 403: Forbidden message
theErr = fRequest->SendForbiddenResponse();
}
if (QTSS_NoErr != theErr) // We had an error so bail on the request quit the session and post process the request.
{
fRequest->SetResponseKeepAlive(false);
fCurrentModule = 0;
fState = kPostProcessingRequest;
break;
}
}
}
fCurrentModule = 0;
if (fRequest->HasResponseBeenSent())
{
fState = kPostProcessingRequest;
break;
}
// Prepare for kPreprocessingRequest state.
fState = kPreprocessingRequest;
if (fRequest->GetMethod() == qtssSetupMethod)
// Make sure to erase the session ID stored in the request at this point.
// If we fail to do so, this same session would be used if another
// SETUP was issued on this same TCP connection.
fLastRTPSessionIDPtr.Len = 0;
else if (fLastRTPSessionIDPtr.Len == 0)
fLastRTPSessionIDPtr.Len = ::strlen(fLastRTPSessionIDPtr.Ptr);
}
case kPreprocessingRequest:
{
// Invoke preprocessor modules
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPPreProcessorRole);
{
// Manipulation of the RTPSession from the point of view of
// a module is guarenteed to be atomic by the API.
Assert(fRTPSession != NULL);
OSMutexLocker locker(fRTPSession->GetSessionMutex());
for (; (fCurrentModule < numModules) && ((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested); fCurrentModule++)
{
fModuleState.eventRequested = false;
fModuleState.idleTime = 0;
if (fModuleState.globalLockRequested )
{ fModuleState.globalLockRequested = false;
fModuleState.isGlobalLocked = true;
}
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPPreProcessorRole, fCurrentModule);
(void)theModule->CallDispatch(QTSS_RTSPPreProcessor_Role, &fRoleParams);
fModuleState.isGlobalLocked = false;
// The way the API is set up currently, the first module that adds a stream
// to the session is responsible for sending RTP packets for the session.
if (fRTPSession->HasAnRTPStream() && (fRTPSession->GetPacketSendingModule() == NULL))
fRTPSession->SetPacketSendingModule(theModule);
if (fModuleState.globalLockRequested) // call this request back locked
return this->CallLocked();
// If this module has requested an event, return and wait for the event to transpire
if (fModuleState.eventRequested)
{
this->ForceSameThread(); // We are holding mutexes, so we need to force
// the same thread to be used for next Run()
return fModuleState.idleTime; // If the module has requested idle time...
}
}
}
fCurrentModule = 0;
if (fRequest->HasResponseBeenSent())
{
fState = kPostProcessingRequest;
break;
}
fState = kProcessingRequest;
}
case kProcessingRequest:
{
// If no preprocessor sends a response, move onto the request processing module. It
// is ALWAYS supposed to send a response, but if it doesn't, we have a canned error
// to send back.
fModuleState.eventRequested = false;
fModuleState.idleTime = 0;
if (QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPRequestRole) > 0)
{
// Manipulation of the RTPSession from the point of view of
// a module is guarenteed to be atomic by the API.
Assert(fRTPSession != NULL);
OSMutexLocker locker(fRTPSession->GetSessionMutex());
if (fModuleState.globalLockRequested )
{ fModuleState.globalLockRequested = false;
fModuleState.isGlobalLocked = true;
}
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPRequestRole, 0);
(void)theModule->CallDispatch(QTSS_RTSPRequest_Role, &fRoleParams);
fModuleState.isGlobalLocked = false;
// Do the same check as above for the preprocessor
if (fRTPSession->HasAnRTPStream() && fRTPSession->GetPacketSendingModule() == NULL)
fRTPSession->SetPacketSendingModule(theModule);
this->Process3GPPData();
if (fModuleState.globalLockRequested) // call this request back locked
return this->CallLocked();
// If this module has requested an event, return and wait for the event to transpire
if (fModuleState.eventRequested)
{
this->ForceSameThread(); // We are holding mutexes, so we need to force
// the same thread to be used for next Run()
return fModuleState.idleTime; // If the module has requested idle time...
}
}
if (!fRequest->HasResponseBeenSent())
{
// no modules took this one so send back a parameter error
if (fRequest->GetMethod() == qtssSetParameterMethod) // keep session
{
QTSS_RTSPStatusCode statusCode = qtssSuccessOK; //qtssClientParameterNotUnderstood;
fRequest->SetValue(qtssRTSPReqStatusCode, 0, &statusCode, sizeof(statusCode));
fRequest->SendHeader();
}
else
{
QTSSModuleUtils::SendErrorResponse(fRequest, qtssServerInternal, qtssMsgNoModuleForRequest);
}
}
fState = kPostProcessingRequest;
}
case kPostProcessingRequest:
{
// Post process the request *before* sending the response. Therefore, we
// will post process regardless of whether the client actually gets our response
// or not.
//if this is not a keepalive request, we should kill the session NOW
fLiveSession = fRequest->GetResponseKeepAlive();
if (fRTPSession != NULL)
{
// Invoke postprocessor modules only if there is an RTP session. We do NOT want
// postprocessors running when filters or syntax errors have occurred in the request!
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPPostProcessorRole);
{
// Manipulation of the RTPSession from the point of view of
// a module is guarenteed to be atomic by the API.
OSMutexLocker locker(fRTPSession->GetSessionMutex());
// Make sure the RTPSession contains a copy of the realStatusCode in this request
UInt32 realStatusCode = RTSPProtocol::GetStatusCode(fRequest->GetStatus());
(void) fRTPSession->SetValue(qtssCliRTSPReqRealStatusCode,(UInt32) 0,(void *) &realStatusCode, sizeof(realStatusCode), QTSSDictionary::kDontObeyReadOnly);
// Make sure the RTPSession contains a copy of the qtssRTSPReqRespMsg in this request
StrPtrLen* theRespMsg = fRequest->GetValue(qtssRTSPReqRespMsg);
if (theRespMsg->Len > 0)
(void)fRTPSession->SetValue(qtssCliRTSPReqRespMsg, 0, theRespMsg->Ptr, theRespMsg->Len, QTSSDictionary::kDontObeyReadOnly);
// Set the current RTSP session for this RTP session.
// We do this here because we need to make sure the SessionMutex
// is grabbed while we do this. Only do this if the RTSP session
// is still alive, of course.
if (this->IsLiveSession())
fRTPSession->UpdateRTSPSession(this);
for (; (fCurrentModule < numModules) || (fModuleState.eventRequested) ; fCurrentModule++)
{
fModuleState.eventRequested = false;
fModuleState.idleTime = 0;
if (fModuleState.globalLockRequested )
{ fModuleState.globalLockRequested = false;
fModuleState.isGlobalLocked = true;
}
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPPostProcessorRole, fCurrentModule);
(void)theModule->CallDispatch(QTSS_RTSPPostProcessor_Role, &fRoleParams);
fModuleState.isGlobalLocked = false;
if (fModuleState.globalLockRequested) // call this request back locked
return this->CallLocked();
// If this module has requested an event, return and wait for the event to transpire
if (fModuleState.eventRequested)
{
this->ForceSameThread(); // We are holding mutexes, so we need to force
// the same thread to be used for next Run()
return fModuleState.idleTime; // If the module has requested idle time...
}
}
}
}
fCurrentModule = 0;
fState = kSendingResponse;
}
case kSendingResponse:
{
// Sending the RTSP response consists of making sure the
// RTSP request output buffer is completely flushed to the socket.
Assert(fRequest != NULL);
// If x-dynamic-rate header is sent with a value of 1, send OPTIONS request
if ((fRequest->GetMethod() == qtssSetupMethod) && (fRequest->GetStatus() == qtssSuccessOK)
&& (fRequest->GetDynamicRateState() == 1) && fRoundTripTimeCalculation)
{
this->SaveOutputStream();
this->ResetOutputStream();
this->SendOptionsRequest();
}
if (fSentOptionsRequest && (fRequest->GetMethod() == qtssIllegalMethod))
{
this->ResetOutputStream();
this->RevertOutputStream();
fSentOptionsRequest = false;
}
err = fOutputStream.Flush();
if (err == EAGAIN)
{
// If we get this error, we are currently flow-controlled and should
// wait for the socket to become writeable again
fSocket.RequestEvent(EV_WR);
this->ForceSameThread(); // We are holding mutexes, so we need to force
// the same thread to be used for next Run()
return 0;
}
else if (err != QTSS_NoErr)
{
// Any other error means that the client has disconnected, right?
Assert(!this->IsLiveSession());
break;
}
fState = kCleaningUp;
}
case kCleaningUp:
{
// Cleaning up consists of making sure we've read all the incoming Request Body
// data off of the socket
if (this->GetRemainingReqBodyLen() > 0)
{
err = this->DumpRequestData();
if (err == EAGAIN)
{
fInputSocketP->RequestEvent(EV_RE);
this->ForceSameThread(); // We are holding mutexes, so we need to force
// the same thread to be used for next Run()
return 0;
}
}
// If we've gotten here, we've flushed all the data. Cleanup,
// and wait for our next request!
this->CleanupRequest();
fState = kReadingRequest;
}
}
}
// Make absolutely sure there are no resources being occupied by the session
// at this point.
this->CleanupRequest();
// Only delete if it is ok to delete!
if (fObjectHolders == 0)
return -1;
// If we are here because of a timeout, but we can't delete because someone
// is holding onto a reference to this session, just reschedule the timeout.
//
// At this point, however, the session is DEAD.
return 0;
}
Bool16 RTSPSession::ParseProxyTunnelHTTP()
{
/*
if it's an HTTP request
parse the interesing parts from the request
- check for GET or POST, set fHTTPMethod
- checck for HTTP protocol, set fWasHTTPRequest
- check for SessionID header, set fProxySessionID char array
- check for accept "application/x-rtsp-tunnelled.
*/
Bool16 isHTTPRequest = false;
StrPtrLen *splRequest;
HTTP_VTRACE( "ParseProxyTunnelHTTP\n" )
splRequest = fInputStream.GetRequestBuffer();
fFoundValidAccept = true;
Assert( splRequest );
if ( splRequest )
{
fHTTPMethod = kHTTPMethodUnknown;
#if __RTSP_HTTP_DEBUG__
{
char buff[1024];
memcpy( buff, splRequest->Ptr, splRequest->Len );
buff[ splRequest->Len] = 0;
HTTP_VTRACE( buff )
}
#endif
StrPtrLen theParsedData;
StringParser parser(splRequest);
parser.ConsumeWord(&theParsedData);
HTTP_VTRACE( "request method: \n" )
HTTP_VTRACE_SPL( &theParsedData )
// does first line begin with POST or GET?
if (theParsedData.EqualIgnoreCase("post", 4 ))
{
fHTTPMethod = kHTTPMethodPost;
}
else if (theParsedData.EqualIgnoreCase("get", 3 ))
{
fHTTPMethod = kHTTPMethodGet;
}
if ( fHTTPMethod != kHTTPMethodUnknown )
{
HTTP_VTRACE( "IsAHTTPProxyTunnelPostRequest found POST or GET\n" )
parser.ConsumeWhitespace(); // skip over ws past method
parser.ConsumeUntilWhitespace( &theParsedData ); // theParsedData now contains the URL and CGI params ( we don't need yet );
parser.ConsumeWhitespace(); // skip over ws past url
parser.ConsumeWord(&theParsedData); // now should contain "HTTP"
HTTP_VTRACE( "should be HTTP/1.* next: \n" )
HTTP_VTRACE_SPL( &theParsedData )
// DMS - why use NumEqualIgnoreCase? Wouldn't EqualIgnoreCase do the trick here?
if (theParsedData.NumEqualIgnoreCase("http", 4 ))
{ HTTP_TRACE( "ParseProxyTunnelHTTP found HTTP\n" )
fWasHTTPRequest = true;
}
}
if ( fWasHTTPRequest )
{
// it's HTTP and one of the methods we like....
// now, find the Session ID and Accept headers
const char* kSessionHeaderName = "X-SessionCookie:";
const int kSessionHeaderNameLen = ::strlen(kSessionHeaderName);
const char* kAcceptHeaderName = "Accept:";
const int kAcceptHeaderNameLen = ::strlen(kAcceptHeaderName);
//const char* kAcceptData = "application/x-rtsp-tunnelled";
//const int kAcceptDataLen = ::strlen(kAcceptData);
while ( parser.GetDataRemaining() > 0 )
{
parser.GetThruEOL( &theParsedData ); // we don't need this, but there is not a ComsumeThru...
parser.ConsumeUntilWhitespace( &theParsedData ); // theParsedData now contains the URL and CGI params ( we don't need yet );
if ( theParsedData.EqualIgnoreCase( kSessionHeaderName, kSessionHeaderNameLen ) )
{
// we got a weener!
if ( parser.GetDataRemaining() > 0 )
parser.ConsumeWhitespace();
if ( parser.GetDataRemaining() > 0 )
{
StrPtrLen sessionID;
parser.ConsumeUntil( &sessionID, StringParser::sEOLMask );
// cache the ID so we can use it to remove ourselves from the map
if ( sessionID.Len < QTSS_MAX_SESSION_ID_LENGTH )
{
::memcpy( fProxySessionID, sessionID.Ptr, sessionID.Len );
fProxySessionID[sessionID.Len] = 0;
fProxySessionIDPtr.Set( fProxySessionID, ::strlen(fProxySessionID) );
HTTP_VTRACE_ONE( "found session id: %s\n", fProxySessionID )
}
}
}
else if ( theParsedData.EqualIgnoreCase( kAcceptHeaderName, kAcceptHeaderNameLen ) )
{
StrPtrLen hTTPAcceptHeader;
// we got another weener!
if ( parser.GetDataRemaining() > 0 )
parser.ConsumeWhitespace();
if ( parser.GetDataRemaining() > 0 )
{
parser.ConsumeUntil( &hTTPAcceptHeader, StringParser::sEOLMask );
#if __RTSP_HTTP_DEBUG__
{
char buff[1024];
memcpy( buff, hTTPAcceptHeader.Ptr, hTTPAcceptHeader.Len );
buff[ hTTPAcceptHeader.Len] = 0;
HTTP_VTRACE_ONE( "client will accept: %s\n", buff )
}
#endif
// we really don't need to check thisif ( theParsedData.EqualIgnoreCase( kAcceptData, kAcceptDataLen ) )
{ fFoundValidAccept = true;
HTTP_VTRACE( "found valid accept\n" )
}
}
}
}
}
}
// we found all that we were looking for
if ( fFoundValidAccept && *fProxySessionID && fWasHTTPRequest )
isHTTPRequest = true;
return isHTTPRequest;
}
/*
"pre" filter the request looking for the HHTP Proxy
tunnel HTTP requests, merge the 2 sessions
into one, let the donor Session die.
*/
QTSS_Error RTSPSession::PreFilterForHTTPProxyTunnel()
{
// returns true if it's an HTTP request that can tunnel
if (!this->ParseProxyTunnelHTTP())
return QTSS_NoErr;
// This is an RTSP / HTTP session, so decrement the total RTSP sessions
// and increment the total HTTP sessions
Assert(fSessionType == qtssRTSPSession);
QTSServerInterface::GetServer()->SwapFromRTSPToHTTP();
// Setup our ProxyTunnel OSRefTable Ref
Assert( fProxySessionIDPtr.Len > 0 );
fProxyRef.Set(fProxySessionIDPtr, this);
// We have to set this here, because IF we are able to put ourselves in the map,
// the GET may arrive immediately after, and the GET checks this state.
fState = kWaitingToBindHTTPTunnel;
QTSS_RTSPSessionType theOtherSessionType = qtssRTSPSession;
if ( fHTTPMethod == kHTTPMethodPost )
{
HTTP_TRACE( "RTSPSession is a POST request.\n" )
fSessionType = qtssRTSPHTTPInputSession;
theOtherSessionType = qtssRTSPHTTPSession;
}
else if ( fHTTPMethod == kHTTPMethodGet )
{
HTTP_TRACE( "RTSPSession is a GET request.\n" )
// we're session O (outptut) the POST half is session 1 ( input )
fSessionType = qtssRTSPHTTPSession;
theOtherSessionType = qtssRTSPHTTPInputSession;
Bool16 showServerInfo = QTSServerInterface::GetServer()->GetPrefs()->GetRTSPServerInfoEnabled();
if (fDoReportHTTPConnectionAddress )
{ // contruct a 200 OK header with an "x-server-ip-address" header
char responseHeaderBuf[kMaxHTTPResponseLen];
char localIPAddrBuf[20] = { 0 };
StrPtrLen localIPAddr(localIPAddrBuf, 19);
// get a copy of the local IP address from the dictionary
this->GetValue(qtssRTSPSesLocalAddrStr, 0, localIPAddr.Ptr, &localIPAddr.Len);
Assert( localIPAddr.Len < sizeof( localIPAddrBuf ) );
localIPAddrBuf[localIPAddr.Len] = 0;
char *headerFieldPtr = "";
if(showServerInfo)
{
headerFieldPtr = QTSServerInterface::GetServerHeader().Ptr;
qtss_sprintf( responseHeaderBuf, sHTTPResponseFormatStr, "X-server-ip-address: ", localIPAddrBuf, "\r\n", headerFieldPtr );
}
else
{
qtss_sprintf( responseHeaderBuf, sHTTPNoServerResponseFormatStr, "X-server-ip-address: ", localIPAddrBuf, "\r\n", headerFieldPtr);
}
Assert(::strlen(responseHeaderBuf) < kMaxHTTPResponseLen);
fOutputStream.Put(responseHeaderBuf);
}
else // use the premade stopck version
{ if (showServerInfo)
fOutputStream.Put(sHTTPResponseHeaderPtr); // 200 OK just means we connected...
else
fOutputStream.Put(sHTTPResponseNoServerHeaderPtr); // 200 OK just means we connected...
}
}
else
Assert(0);
// This function attempts to register our Ref into the map. If there is another
// session with a matching magic number, it resolves it and returns that Ref.
// If it returns NULL, something bad has happened, and we should just kill the session.
OSRef* rtspSessionRef = this->RegisterRTSPSessionIntoHTTPProxyTunnelMap(theOtherSessionType);
// Something went wrong (usually we get here because there is a session with this magic
// number, and that session is currently doing something
if (rtspSessionRef == NULL)
{
HTTP_TRACE("RegisterRTSPSessionIntoHTTPProxyTunnelMap returned NULL. Abort.\n");
return QTSS_RequestFailed;
}
// We registered ourselves into the map (we are the first half), so wait for our other half
if (rtspSessionRef == &fProxyRef)
{
HTTP_TRACE("Registered this session into map. Waiting to bind\n");
return QTSS_NoErr;
}
OSRefReleaser theRefReleaser(sHTTPProxyTunnelMap, rtspSessionRef); // auto release this ref
RTSPSession* theOtherSession = (RTSPSession*)theRefReleaser.GetRef()->GetObject();
// We must lock down this session, for we (may) be manipulating its socket & input
// stream, and therefore it cannot be in the process of reading data or processing a request.
// If it is, well, safest thing to do is probably just deny this attempt to bind.
if (!theOtherSession->fReadMutex.TryLock())
{
HTTP_TRACE("Found another session in map, but couldn't grab fReadMutex. Abort.\n");
return QTSS_RequestFailed;
}
if (fHTTPMethod == kHTTPMethodPost)
{
// take the input session's socket. This also grabs the other session's input stream
theOtherSession->SnarfInputSocket(this);
// Attempt to bind to this GET connection
// this will reset our state on success.
HTTP_TRACE_ONE( "RTSPSession POST snarfed a donor session successfuly (%s).\n", fProxySessionID )
fState = kSocketHasBeenBoundIntoHTTPTunnel;
theOtherSession->fState = kReadingRequest;
theOtherSession->Signal(Task::kReadEvent);
}
else if (fHTTPMethod == kHTTPMethodGet)
{
Assert( theOtherSession->fState == kWaitingToBindHTTPTunnel );
HTTP_TRACE_ONE( "RTSPSession GET snarfed a donor session successfuly (%s).\n", fProxySessionID )
// take the input session's socket. This also grabs the other session's input stream
this->SnarfInputSocket(theOtherSession);
// we assume the donor's place in the map.
sHTTPProxyTunnelMap->Swap( &fProxyRef );
// the 1/2 connections are bound
// the output Session state goes back to reading a request, this time an RTSP request
// the socket donor Session(rtspSessionInput) state goes to kSocketHasBeenBoundIntoHTTPTunnel to die
fState = kReadingRequest;
theOtherSession->fState = kSocketHasBeenBoundIntoHTTPTunnel;
theOtherSession->Signal(Task::kKillEvent);
}
theOtherSession->fReadMutex.Unlock();
return QTSS_NoErr;
}
OSRef* RTSPSession::RegisterRTSPSessionIntoHTTPProxyTunnelMap(QTSS_RTSPSessionType inSessionType)
{
// This function attempts to register the current session's fProxyRef into the map, and
// 1) returns the current session's fProxyRef if registration was successful
// 2) returns another session's fProxyRef if it has the same magic number and is the right sessionType
// 3) returns NULL if there is a session with the same magic # but it couldn't be resolved.
OSMutexLocker locker(sHTTPProxyTunnelMap->GetMutex());
OSRef* theRef = sHTTPProxyTunnelMap->RegisterOrResolve(&fProxyRef);
if (theRef == NULL)
return &fProxyRef;
RTSPSession* rtspSession = (RTSPSession*)theRef->GetObject();
// we can be the only user of the object right now
Assert(theRef->GetRefCount() > 0);
if (theRef->GetRefCount() > 1 || rtspSession->fSessionType != inSessionType)
{
sHTTPProxyTunnelMap->Release(theRef);
theRef = NULL;
}
return theRef;
}
void RTSPSession::CheckAuthentication() {
QTSSUserProfile* profile = fRequest->GetUserProfile();
StrPtrLen* userPassword = profile->GetValue(qtssUserPassword);
QTSS_AuthScheme scheme = fRequest->GetAuthScheme();
Bool16 authenticated = true;
// Check if authorization information returned by the client is for the scheme that the server sent the challenge
if(scheme != (fRTPSession->GetAuthScheme())) {
authenticated = false;
}
else if(scheme == qtssAuthBasic) {
// For basic authentication, the authentication module returns the crypt of the password,
// so compare crypt of qtssRTSPReqUserPassword and the text in qtssUserPassword
StrPtrLen* reqPassword = fRequest->GetValue(qtssRTSPReqUserPassword);
char* userPasswdStr = userPassword->GetAsCString(); // memory allocated
char* reqPasswdStr = reqPassword->GetAsCString(); // memory allocated
if(userPassword->Len == 0)
{
authenticated = false;
}
else
{
#ifdef __Win32__
// The password is md5 encoded for win32
char md5EncodeResult[120];
// no memory is allocated in this function call
MD5Encode(reqPasswdStr, userPasswdStr, md5EncodeResult, sizeof(md5EncodeResult));
if(::strcmp(userPasswdStr, md5EncodeResult) != 0)
authenticated = false;
#else
if(::strcmp(userPasswdStr, (char*)crypt(reqPasswdStr, userPasswdStr)) != 0)
authenticated = false;
#endif
}
delete [] userPasswdStr; // deleting allocated memory
userPasswdStr = NULL;
delete [] reqPasswdStr;
reqPasswdStr = NULL; // deleting allocated memory
}
else if(scheme == qtssAuthDigest) { // For digest authentication, md5 digest comparison
// The text returned by the authentication module in qtssUserPassword is MD5 hash of (username:realm:password)
UInt32 qop = fRequest->GetAuthQop();
StrPtrLen* opaque = fRequest->GetAuthOpaque();
StrPtrLen* sessionOpaque = fRTPSession->GetAuthOpaque();
UInt32 sessionQop = fRTPSession->GetAuthQop();
do {
// The Opaque string should be the same as that sent by the server
// The QoP should be the same as that sent by the server
if((sessionOpaque->Len != 0) && !(sessionOpaque->Equal(*opaque))) {
authenticated = false;
break;
}
if(sessionQop != qop) {
authenticated = false;
break;
}
// All these are just pointers to existing memory... no new memory is allocated
//StrPtrLen* userName = profile->GetValue(qtssUserName);
//StrPtrLen* realm = fRequest->GetAuthRealm();
StrPtrLen* nonce = fRequest->GetAuthNonce();
StrPtrLen method = RTSPProtocol::GetMethodString(fRequest->GetMethod());
StrPtrLen* digestUri = fRequest->GetAuthUri();
StrPtrLen* responseDigest = fRequest->GetAuthResponse();
//StrPtrLen hA1;
StrPtrLen requestDigest;
StrPtrLen emptyStr;
StrPtrLen* cNonce = fRequest->GetAuthCNonce();
// Since qtssUserPassword = md5(username:realm:password)
// Just convert the 16 bit hash to a 32 bit char array to get HA1
//HashToString((unsigned char *)userPassword->Ptr, &hA1);
//CalcHA1(&sAuthAlgorithm, userName, realm, userPassword, nonce, cNonce, &hA1);
// For qop="auth"
if(qop == RTSPSessionInterface::kAuthQop) {
StrPtrLen* nonceCount = fRequest->GetAuthNonceCount();
UInt32 ncValue = 0;
// Convert nounce count (which is a string of 8 hex digits) into a UInt32
if (nonceCount && nonceCount->Len)
{
// Convert nounce count (which is a string of 8 hex digits) into a UInt32
UInt32 bufSize = sizeof(ncValue);
StrPtrLenDel tempString(nonceCount->GetAsCString());
tempString.ToUpper();
QTSSDataConverter::ConvertCHexStringToBytes(tempString.Ptr,
&ncValue,
&bufSize);
ncValue = ntohl(ncValue);
}
// nonce count must not be repeated by the client
if(ncValue < (fRTPSession->GetAuthNonceCount())) {
authenticated = false;
break;
}
// allocates memory for requestDigest.Ptr
CalcRequestDigest(userPassword, nonce, nonceCount, cNonce, &sAuthQop, &method, digestUri, &emptyStr, &requestDigest);
// If they are equal, check if nonce used by client is same as the one sent by the server
} // For No qop
else if(qop == RTSPSessionInterface::kNoQop)
{
// allocates memory for requestDigest->Ptr
CalcRequestDigest(userPassword, nonce, &emptyStr, &emptyStr, &emptyStr, &method, digestUri, &emptyStr, &requestDigest);
}
// hA1 is allocated memory
//delete [] hA1.Ptr;
if(responseDigest->Equal(requestDigest)) {
if(!(nonce->Equal(*(fRTPSession->GetAuthNonce()))))
fRequest->SetStale(true);
authenticated = true;
}
else {
authenticated = false;
}
// delete the memory allocated in CalcRequestDigest above
delete [] requestDigest.Ptr;
requestDigest.Len = 0;
} while(false);
}
// If authenticaton failed, set qtssUserName in the qtssRTSPReqUserProfile attribute
// to NULL and clear out the password and any groups that have been set.
if (!fRequest->GetAuthHandled())
{
if((!authenticated) || (authenticated && (fRequest->GetStale()))) {
debug_printf("erasing username from profile\n");
(void)profile->SetValue(qtssUserName, 0, sEmptyStr.Ptr, sEmptyStr.Len, QTSSDictionary::kDontObeyReadOnly);
(void)profile->SetValue(qtssUserPassword, 0, sEmptyStr.Ptr, sEmptyStr.Len, QTSSDictionary::kDontObeyReadOnly);
(void)profile->SetNumValues(qtssUserGroups, 0);
}
}
}
Bool16 RTSPSession::ParseOptionsResponse()
{
StringParser parser(fRequest->GetValue(qtssRTSPReqFullRequest));
Assert(fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr != NULL);
static StrPtrLen sRTSPStr("RTSP", 4);
StrPtrLen theProtocol;
parser.ConsumeLength(&theProtocol, 4);
return (theProtocol.Equal(sRTSPStr));
}
void RTSPSession::Process3GPPData()
{
if (fRTPSession == NULL)
return;
RTSPRequest3GPP* request3GPPInfoPtr = fRequest->GetRequest3GPPInfo();
if (!request3GPPInfoPtr || !request3GPPInfoPtr->Is3GPP())
return;
fRTPSession->SetIs3GPPSession(true);
if (request3GPPInfoPtr->HasLinkChar())
{
StrPtrLen dataStr;
LinkCharDataFields linkCharFieldsParser;
if(0 == fRequest->GetHeaderDictionary()->GetValuePtr(qtss3GPPLinkCharHeader, 0, (void**) &dataStr.Ptr, &dataStr.Len, true))
{
linkCharFieldsParser.SetData(&dataStr);
fRTPSession->Set3GPPLinkCharData(&linkCharFieldsParser);
}
}
if (request3GPPInfoPtr->HasRateAdaptation())
{
StrPtrLen dataStr;
RateAdapationStreamDataFields fieldsParser;
for (int numValues = 0; request3GPPInfoPtr->GetValuePtr(qtss3GPPRequestRateAdaptationStreamData, numValues, (void**) &dataStr.Ptr, &dataStr.Len) == QTSS_NoErr; numValues++)
{
//dataStr.PrintStr("RTSPSession::Process3GPPData qtss3GPPRequestRateAdaptationStreamData=[","]\n");
fieldsParser.SetData(&dataStr);
fRTPSession->Set3GPPRateAdaptionData(&fieldsParser);
}
}
return;
}
void RTSPSession::SetupRequest()
{
//
// First parse the request
QTSS_Error theErr = fRequest->Parse();
if (theErr != QTSS_NoErr)
return;
// let's also refresh RTP session timeout so that it's kept alive in sync with the RTSP session.
//
// Attempt to find the RTP session for this request.
OSRefTable* theMap = QTSServerInterface::GetServer()->GetRTPSessionMap();
theErr = this->FindRTPSession(theMap);
if (fRTPSession != NULL)
{
OSMutexLocker locker(fRTPSession->GetMutex());
fRTPSession->RefreshTimeout();
UInt32 headerBits = fRequest->GetBandwidthHeaderBits();
if (headerBits != 0)
(void)fRTPSession->SetValue(qtssCliSessLastRTSPBandwidth, (UInt32) 0,&headerBits,sizeof(headerBits), QTSSDictionary::kDontObeyReadOnly );
}
QTSS_RTSPStatusCode statusCode = qtssSuccessOK;
char *body = NULL;
UInt32 bodySizeBytes = 0;
//
// If this is an OPTIONS request, don't even bother letting modules see it. Just
// send a standard OPTIONS response, and be done.
if (fRequest->GetMethod() == qtssOptionsMethod)
{
this->Process3GPPData();
StrPtrLen* cSeqPtr = fRequest->GetHeaderDictionary()->GetValue(qtssCSeqHeader);
if (cSeqPtr == NULL || cSeqPtr->Len == 0)
{
statusCode = qtssClientBadRequest;
fRequest->SetValue(qtssRTSPReqStatusCode, 0, &statusCode, sizeof(statusCode));
fRequest->SendHeader();
return;
}
fRequest->AppendHeader(qtssPublicHeader, QTSServerInterface::GetPublicHeader());
// DJM PROTOTYPE
StrPtrLen* requirePtr = fRequest->GetHeaderDictionary()->GetValue(qtssRequireHeader);
if ( requirePtr && requirePtr->EqualIgnoreCase(RTSPProtocol::GetHeaderString(qtssXRandomDataSizeHeader)) )
{
body = (char*) RTSPSessionInterface::sOptionsRequestBody;
bodySizeBytes = fRequest->GetRandomDataSize();
Assert( bodySizeBytes <= sizeof(RTSPSessionInterface::sOptionsRequestBody) );
fRequest->AppendHeader(qtssContentTypeHeader, &sContentType);
fRequest->AppendContentLength(bodySizeBytes);
}
fRequest->SendHeader();
// now write the body if there is one
if (bodySizeBytes > 0 && body != NULL)
fRequest->Write(body, bodySizeBytes, NULL, 0);
return;
}
//
// If this is a SET_PARAMETER request, don't let modules see it.
if (fRequest->GetMethod() == qtssSetParameterMethod)
{
// Check that it has the CSeq header
StrPtrLen* cSeqPtr = fRequest->GetHeaderDictionary()->GetValue(qtssCSeqHeader);
if (cSeqPtr == NULL || cSeqPtr->Len == 0) // keep session
{
statusCode = qtssClientBadRequest;
fRequest->SetValue(qtssRTSPReqStatusCode, 0, &statusCode, sizeof(statusCode));
fRequest->SendHeader();
return;
}
// If the RTPSession doesn't exist, return error
if (fRTPSession == NULL) // keep session
{
statusCode = qtssClientSessionNotFound;
fRequest->SetValue(qtssRTSPReqStatusCode, 0, &statusCode, sizeof(statusCode));
fRequest->SendHeader();
return;
}
// refresh RTP session timeout so that it's kept alive in sync with the RTSP session.
if (fRequest->GetLateToleranceInSec() != -1)
{
OSMutexLocker locker(fRTPSession->GetMutex());
fRTPSession->SetStreamThinningParams(fRequest->GetLateToleranceInSec());
fRequest->SendHeader();
return;
}
// let modules handle it if they want it.
}
//
// If this is a DESCRIBE request, make sure there is no SessionID. This is not allowed,
// and may screw up modules if we let them see this request.
if (fRequest->GetMethod() == qtssDescribeMethod)
{
if (fRequest->GetHeaderDictionary()->GetValue(qtssSessionHeader)->Len > 0)
{
(void)QTSSModuleUtils::SendErrorResponse(fRequest, qtssClientHeaderFieldNotValid, qtssMsgNoSesIDOnDescribe);
return;
}
}
//
// If we don't have an RTP session yet, create one...
if (fRTPSession == NULL)
{
theErr = this->CreateNewRTPSession(theMap);
if (theErr != QTSS_NoErr)
return;
}
OSMutexLocker locker(fRTPSession->GetMutex());
UInt32 headerBits = fRequest->GetBandwidthHeaderBits();
if (headerBits != 0)
(void)fRTPSession->SetValue(qtssCliSessLastRTSPBandwidth, 0,&headerBits,sizeof(headerBits), QTSSDictionary::kDontObeyReadOnly );
// If it's a play request and the late tolerance is sent in the request use this value
if ((fRequest->GetMethod() == qtssPlayMethod) && (fRequest->GetLateToleranceInSec() != -1))
fRTPSession->SetStreamThinningParams(fRequest->GetLateToleranceInSec());
//
// Check to see if this is a "ping" PLAY request (a PLAY request while already
// playing with no Range header). If so, just send back a 200 OK response and do nothing.
// No need to go to modules to do this, because this is an RFC documented behavior
if ((fRequest->GetMethod() == qtssPlayMethod) && (fRTPSession->GetSessionState() == qtssPlayingState)
&& (fRequest->GetStartTime() == -1) && (fRequest->GetStopTime() == -1))
{
fRequest->SendHeader();
fRTPSession->RefreshTimeout();
return;
}
Assert(fRTPSession != NULL); // At this point, we must have one!
fRoleParams.rtspRequestParams.inClientSession = fRTPSession;
// Setup Authorization params;
fRequest->ParseAuthHeader();
}
void RTSPSession::CleanupRequest()
{
if (fRTPSession != NULL)
{
// Release the ref.
OSRefTable* theMap = QTSServerInterface::GetServer()->GetRTPSessionMap();
theMap->Release(fRTPSession->GetRef());
// NULL out any references to this RTP session
fRTPSession = NULL;
fRoleParams.rtspRequestParams.inClientSession = NULL;
}
if (this->IsLiveSession() == false) //clear out the ID so it can't be re-used.
{ fLastRTPSessionID[0] = 0;
fLastRTPSessionIDPtr.Set( fLastRTPSessionID, 0 );
}
if (fRequest != NULL)
{
// Check to see if a filter module has replaced the request. If so, delete
// their request now.
if (fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr != fInputStream.GetRequestBuffer()->Ptr)
delete [] fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr;
// NULL out any references to the current request
delete fRequest;
fRequest = NULL;
fRoleParams.rtspRequestParams.inRTSPRequest = NULL;
fRoleParams.rtspRequestParams.inRTSPHeaders = NULL;
}
fSessionMutex.Unlock();
fReadMutex.Unlock();
// Clear out our last value for request body length before moving onto the next request
this->SetRequestBodyLength(-1);
}
QTSS_Error RTSPSession::FindRTPSession(OSRefTable* inRefTable)
{
// This function attempts to locate the appropriate RTP session for this RTSP
// Request. It uses an RTSP session ID as a key to finding the correct RTP session,
// and it looks for this session ID in two places. First, the RTSP session ID header
// in the RTSP request, and if there isn't one there, in the RTSP session object itself.
StrPtrLen* theSessionID = fRequest->GetHeaderDictionary()->GetValue(qtssSessionHeader);
if (theSessionID != NULL && theSessionID->Len > 0)
{
OSRef* theRef = inRefTable->Resolve(theSessionID);
if (theRef != NULL)
fRTPSession = (RTPSession*)theRef->GetObject();
}
// If there wasn't a session ID in the headers, look for one in the RTSP session itself
if ( (theSessionID == NULL || theSessionID->Len == 0) && fLastRTPSessionIDPtr.Len > 0)
{
OSRef* theRef = inRefTable->Resolve(&fLastRTPSessionIDPtr);
if (theRef != NULL)
fRTPSession = (RTPSession*)theRef->GetObject();
}
return QTSS_NoErr;
}
QTSS_Error RTSPSession::CreateNewRTPSession(OSRefTable* inRefTable)
{
Assert(fLastRTPSessionIDPtr.Ptr == &fLastRTPSessionID[0]);
// This is a brand spanking new session. At this point, we need to create
// a new RTPSession object that will represent this session until it completes.
// Then, we need to pass the session onto one of the modules
// First of all, ask the server if it's ok to add a new session
QTSS_Error theErr = this->IsOkToAddNewRTPSession();
if (theErr != QTSS_NoErr)
return theErr;
// Create the RTPSession object
Assert(fRTPSession == NULL);
fRTPSession = NEW RTPSession();
{
//
// Lock the RTP session down so that it won't delete itself in the
// unusual event there is a timeout while we are doing this.
OSMutexLocker locker(fRTPSession->GetSessionMutex());
// Because this is a new RTP session, setup some dictionary attributes
// pertaining to RTSP that only need to be set once
this->SetupClientSessionAttrs();
// So, generate a session ID for this session
QTSS_Error activationError = EPERM;
while (activationError == EPERM)
{
fLastRTPSessionIDPtr.Len = this->GenerateNewSessionID(fLastRTPSessionID);
//ok, some module has bound this session, we can activate it.
//At this point, we may find out that this new session ID is a duplicate.
//If that's the case, we'll simply retry until we get a unique ID
activationError = fRTPSession->Activate(fLastRTPSessionID);
}
Assert(activationError == QTSS_NoErr);
}
Assert(fLastRTPSessionIDPtr.Ptr == &fLastRTPSessionID[0]);
// Activate adds this session into the RTP session map. We need to therefore
// make sure to resolve the RTPSession object out of the map, even though
// we don't actually need to pointer.
OSRef* theRef = inRefTable->Resolve(&fLastRTPSessionIDPtr);
Assert(theRef != NULL);
return QTSS_NoErr;
}
void RTSPSession::SetupClientSessionAttrs()
{
// get and pass presentation url
StrPtrLen* theValue = fRequest->GetValue(qtssRTSPReqURI);
Assert(theValue != NULL);
(void)fRTPSession->SetValue(qtssCliSesPresentationURL, 0, theValue->Ptr, theValue->Len, QTSSDictionary::kDontObeyReadOnly);
// get and pass full request url
theValue = fRequest->GetValue(qtssRTSPReqAbsoluteURL);
Assert(theValue != NULL);
(void)fRTPSession->SetValue(qtssCliSesFullURL, 0, theValue->Ptr, theValue->Len, QTSSDictionary::kDontObeyReadOnly);
// get and pass request host name
theValue = fRequest->GetHeaderDictionary()->GetValue(qtssHostHeader);
Assert(theValue != NULL);
(void)fRTPSession->SetValue(qtssCliSesHostName, 0, theValue->Ptr, theValue->Len, QTSSDictionary::kDontObeyReadOnly);
// get and pass user agent header
theValue = fRequest->GetHeaderDictionary()->GetValue(qtssUserAgentHeader);
Assert(theValue != NULL);
(void)fRTPSession->SetValue(qtssCliSesFirstUserAgent, 0, theValue->Ptr, theValue->Len, QTSSDictionary::kDontObeyReadOnly);
// get and pass CGI params
if (fRequest->GetMethod() == qtssDescribeMethod)
{
theValue = fRequest->GetValue(qtssRTSPReqQueryString);
Assert(theValue != NULL);
(void)fRTPSession->SetValue(qtssCliSesReqQueryString, 0, theValue->Ptr, theValue->Len, QTSSDictionary::kDontObeyReadOnly);
}
// store RTSP session info in the RTPSession.
StrPtrLen tempStr;
tempStr.Len = 0;
(void) this->GetValuePtr(qtssRTSPSesRemoteAddrStr, (UInt32) 0, (void **) &tempStr.Ptr, &tempStr.Len);
Assert(tempStr.Len != 0);
(void) fRTPSession->SetValue(qtssCliRTSPSessRemoteAddrStr, (UInt32) 0, tempStr.Ptr, tempStr.Len, QTSSDictionary::kDontObeyReadOnly );
tempStr.Len = 0;
(void) this->GetValuePtr(qtssRTSPSesLocalDNS, (UInt32) 0, (void **) &tempStr.Ptr, &tempStr.Len);
Assert(tempStr.Len != 0);
(void) fRTPSession->SetValue(qtssCliRTSPSessLocalDNS, (UInt32) 0, (void **)tempStr.Ptr, tempStr.Len, QTSSDictionary::kDontObeyReadOnly );
tempStr.Len = 0;
(void) this->GetValuePtr(qtssRTSPSesLocalAddrStr, (UInt32) 0, (void **) &tempStr.Ptr, &tempStr.Len);
Assert(tempStr.Len != 0);
(void) fRTPSession->SetValue(qtssCliRTSPSessLocalAddrStr, (UInt32) 0, tempStr.Ptr, tempStr.Len, QTSSDictionary::kDontObeyReadOnly );
}
UInt32 RTSPSession::GenerateNewSessionID(char* ioBuffer)
{
//RANDOM NUMBER GENERATOR
//We want to make our session IDs as random as possible, so use a bunch of
//current server statistics to generate a random SInt64.
//Generate the random number in two UInt32 parts. The first UInt32 uses
//statistics out of a random RTP session.
SInt64 theMicroseconds = OS::Microseconds();
::srand((unsigned int)theMicroseconds);
UInt32 theFirstRandom = ::rand();
QTSServerInterface* theServer = QTSServerInterface::GetServer();
{
OSMutexLocker locker(theServer->GetRTPSessionMap()->GetMutex());
OSRefHashTable* theHashTable = theServer->GetRTPSessionMap()->GetHashTable();
if (theHashTable->GetNumEntries() > 0)
{
theFirstRandom %= theHashTable->GetNumEntries();
theFirstRandom >>= 2;
OSRefHashTableIter theIter(theHashTable);
//Iterate through the session map, finding a random session
for (UInt32 theCount = 0; theCount < theFirstRandom; theIter.Next(), theCount++)
Assert(!theIter.IsDone());
RTPSession* theSession = (RTPSession*)theIter.GetCurrent()->GetObject();
theFirstRandom += theSession->GetPacketsSent();
theFirstRandom += (UInt32)theSession->GetSessionCreateTime();
theFirstRandom += (UInt32)theSession->GetPlayTime();
theFirstRandom += (UInt32)theSession->GetBytesSent();
}
}
//Generate the first half of the random number
::srand((unsigned int)theFirstRandom);
theFirstRandom = ::rand();
//Now generate the second half
UInt32 theSecondRandom = ::rand();
theSecondRandom += theServer->GetCurBandwidthInBits();
theSecondRandom += theServer->GetAvgBandwidthInBits();
theSecondRandom += theServer->GetRTPPacketsPerSec();
theSecondRandom += (UInt32)theServer->GetTotalRTPBytes();
theSecondRandom += theServer->GetTotalRTPSessions();
::srand((unsigned int)theSecondRandom);
theSecondRandom = ::rand();
SInt64 theSessionID = (SInt64)theFirstRandom;
theSessionID <<= 32;
theSessionID += (SInt64)theSecondRandom;
qtss_sprintf(ioBuffer, "%"_64BITARG_"d", theSessionID);
Assert(::strlen(ioBuffer) < QTSS_MAX_SESSION_ID_LENGTH);
return ::strlen(ioBuffer);
}
Bool16 RTSPSession::OverMaxConnections(UInt32 buffer)
{
QTSServerInterface* theServer = QTSServerInterface::GetServer();
SInt32 maxConns = theServer->GetPrefs()->GetMaxConnections();
Bool16 overLimit = false;
if (maxConns > -1) // limit connections
{
UInt32 maxConnections = (UInt32) maxConns + buffer;
if ( (theServer->GetNumRTPSessions() > maxConnections)
||
( theServer->GetNumRTSPSessions() + theServer->GetNumRTSPHTTPSessions() > maxConnections )
)
{
overLimit = true;
}
}
return overLimit;
}
QTSS_Error RTSPSession::IsOkToAddNewRTPSession()
{
QTSServerInterface* theServer = QTSServerInterface::GetServer();
QTSS_ServerState theServerState = theServer->GetServerState();
//we may want to deny this connection for a couple of different reasons
//if the server is refusing new connections
if ((theServerState == qtssRefusingConnectionsState) ||
(theServerState == qtssIdleState) ||
(theServerState == qtssFatalErrorState) ||
(theServerState == qtssShuttingDownState))
return QTSSModuleUtils::SendErrorResponse(fRequest, qtssServerUnavailable,
qtssMsgRefusingConnections);
//if the max connection limit has been hit
if ( this->OverMaxConnections(0))
return QTSSModuleUtils::SendErrorResponse(fRequest, qtssClientNotEnoughBandwidth,
qtssMsgTooManyClients);
//if the max bandwidth limit has been hit
SInt32 maxKBits = theServer->GetPrefs()->GetMaxKBitsBandwidth();
if ( (maxKBits > -1) && (theServer->GetCurBandwidthInBits() >= ((UInt32)maxKBits*1024)) )
return QTSSModuleUtils::SendErrorResponse(fRequest, qtssClientNotEnoughBandwidth,
qtssMsgTooMuchThruput);
//if the server is too loaded down (CPU too high, whatever)
// --INSERT WORKING CODE HERE--
return QTSS_NoErr;
}
void RTSPSession::SaveRequestAuthorizationParams(RTSPRequest *theRTSPRequest)
{
// Set the RTSP session's copy of the user name
StrPtrLen* tempPtr = theRTSPRequest->GetValue(qtssRTSPReqUserName);
Assert(tempPtr != NULL);
if (tempPtr)
{ (void)this->SetValue(qtssRTSPSesLastUserName, 0, tempPtr->Ptr, tempPtr->Len,QTSSDictionary::kDontObeyReadOnly);
(void)fRTPSession->SetValue(qtssCliRTSPSesUserName, (UInt32) 0, tempPtr->Ptr, tempPtr->Len, QTSSDictionary::kDontObeyReadOnly );
}
// Same thing... user password
tempPtr = theRTSPRequest->GetValue(qtssRTSPReqUserPassword);
Assert(tempPtr != NULL);
if (tempPtr)
{ (void)this->SetValue(qtssRTSPSesLastUserPassword, 0, tempPtr->Ptr, tempPtr->Len,QTSSDictionary::kDontObeyReadOnly);
(void)fRTPSession->SetValue(qtssCliRTSPSesUserPassword, (UInt32) 0, tempPtr->Ptr, tempPtr->Len, QTSSDictionary::kDontObeyReadOnly );
}
tempPtr = theRTSPRequest->GetValue(qtssRTSPReqURLRealm);
if (tempPtr)
{
if (tempPtr->Len == 0)
{
// If there is no realm explicitly specified in the request, then let's get the default out of the prefs
OSCharArrayDeleter theDefaultRealm(QTSServerInterface::GetServer()->GetPrefs()->GetAuthorizationRealm());
char *realm = theDefaultRealm.GetObject();
UInt32 len = ::strlen(theDefaultRealm.GetObject());
(void)this->SetValue(qtssRTSPSesLastURLRealm, 0, realm, len,QTSSDictionary::kDontObeyReadOnly);
(void)fRTPSession->SetValue(qtssCliRTSPSesURLRealm, (UInt32) 0,realm,len, QTSSDictionary::kDontObeyReadOnly );
}
else
{
(void)this->SetValue(qtssRTSPSesLastURLRealm, 0, tempPtr->Ptr, tempPtr->Len,QTSSDictionary::kDontObeyReadOnly);
(void)fRTPSession->SetValue(qtssCliRTSPSesURLRealm, (UInt32) 0,tempPtr->Ptr,tempPtr->Len, QTSSDictionary::kDontObeyReadOnly );
}
}
}
QTSS_Error RTSPSession::DumpRequestData()
{
char theDumpBuffer[2048];
QTSS_Error theErr = QTSS_NoErr;
while (theErr == QTSS_NoErr)
theErr = this->Read(theDumpBuffer, 2048, NULL);
return theErr;
}
void RTSPSession::HandleIncomingDataPacket()
{
// Attempt to find the RTP session for this request.
UInt8 packetChannel = (UInt8)fInputStream.GetRequestBuffer()->Ptr[1];
StrPtrLen* theSessionID = this->GetSessionIDForChannelNum(packetChannel);
if (theSessionID == NULL)
{
Assert(0);
theSessionID = &fLastRTPSessionIDPtr;
}
OSRefTable* theMap = QTSServerInterface::GetServer()->GetRTPSessionMap();
OSRef* theRef = theMap->Resolve(theSessionID);
if (theRef != NULL)
fRTPSession = (RTPSession*)theRef->GetObject();
if (fRTPSession == NULL)
return;
StrPtrLen packetWithoutHeaders(fInputStream.GetRequestBuffer()->Ptr + 4, fInputStream.GetRequestBuffer()->Len - 4);
OSMutexLocker locker(fRTPSession->GetMutex());
fRTPSession->RefreshTimeout();
RTPStream* theStream = fRTPSession->FindRTPStreamForChannelNum(packetChannel);
theStream->ProcessIncomingInterleavedData(packetChannel, this, &packetWithoutHeaders);
//
// We currently don't support async notifications from within this role
QTSS_RoleParams packetParams;
packetParams.rtspIncomingDataParams.inRTSPSession = this;
packetParams.rtspIncomingDataParams.inClientSession = fRTPSession;
packetParams.rtspIncomingDataParams.inPacketData = fInputStream.GetRequestBuffer()->Ptr;
packetParams.rtspIncomingDataParams.inPacketLen = fInputStream.GetRequestBuffer()->Len;
UInt32 numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPIncomingDataRole);
for (; fCurrentModule < numModules; fCurrentModule++)
{
QTSSModule* theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPIncomingDataRole, fCurrentModule);
(void)theModule->CallDispatch(QTSS_RTSPIncomingData_Role, &packetParams);
}
fCurrentModule = 0;
}